diff options
author | David Daney <david.daney@cavium.com> | 2015-12-22 16:44:51 -0500 |
---|---|---|
committer | Bjorn Helgaas <bhelgaas@google.com> | 2016-02-04 15:26:27 -0500 |
commit | 7e8fbdc628760857369af05636ed4ddc4fc8569b (patch) | |
tree | ff25b6c3c10cad84a2b441c9532846756a4a7dc1 /drivers/pci/pcie | |
parent | 3b0a6d1a1b3335fde9cbbc659568ed619b07d24a (diff) |
PCI/AER: Restore pci_ops pointer while calling original pci_ops
The aer_inject module intercepts config space accesses by replacing the
bus->ops pointer. If it forwards accesses to the original pci_ops, and
those original ops use bus->ops, they see the aer_pci_ops instead of their
own pci_ops, which can cause a crash.
For example, pci_generic_config_read() uses the bus->ops->map_bus pointer.
If bus->ops is set to aer_pci_ops, which doesn't supply .map_bus,
pci_generic_config_read() will dereference an invalid pointer and cause a
crash.
Temporarily restore the original bus->ops pointer while calling ops->read()
or ops->write(). Callers of these functions already hold pci_lock, which
prevents other users of bus->ops until we're finished.
[bhelgaas: changelog]
Signed-off-by: David Daney <david.daney@cavium.com>
Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
Diffstat (limited to 'drivers/pci/pcie')
-rw-r--r-- | drivers/pci/pcie/aer/aer_inject.c | 30 |
1 files changed, 28 insertions, 2 deletions
diff --git a/drivers/pci/pcie/aer/aer_inject.c b/drivers/pci/pcie/aer/aer_inject.c index 148a301a6b3c..79a5e112711a 100644 --- a/drivers/pci/pcie/aer/aer_inject.c +++ b/drivers/pci/pcie/aer/aer_inject.c | |||
@@ -188,7 +188,9 @@ static int aer_inj_read_config(struct pci_bus *bus, unsigned int devfn, | |||
188 | struct aer_error *err; | 188 | struct aer_error *err; |
189 | unsigned long flags; | 189 | unsigned long flags; |
190 | struct pci_ops *ops; | 190 | struct pci_ops *ops; |
191 | struct pci_ops *my_ops; | ||
191 | int domain; | 192 | int domain; |
193 | int rv; | ||
192 | 194 | ||
193 | spin_lock_irqsave(&inject_lock, flags); | 195 | spin_lock_irqsave(&inject_lock, flags); |
194 | if (size != sizeof(u32)) | 196 | if (size != sizeof(u32)) |
@@ -208,8 +210,19 @@ static int aer_inj_read_config(struct pci_bus *bus, unsigned int devfn, | |||
208 | } | 210 | } |
209 | out: | 211 | out: |
210 | ops = __find_pci_bus_ops(bus); | 212 | ops = __find_pci_bus_ops(bus); |
213 | /* | ||
214 | * pci_lock must already be held, so we can directly | ||
215 | * manipulate bus->ops. Many config access functions, | ||
216 | * including pci_generic_config_read() require the original | ||
217 | * bus->ops be installed to function, so temporarily put them | ||
218 | * back. | ||
219 | */ | ||
220 | my_ops = bus->ops; | ||
221 | bus->ops = ops; | ||
222 | rv = ops->read(bus, devfn, where, size, val); | ||
223 | bus->ops = my_ops; | ||
211 | spin_unlock_irqrestore(&inject_lock, flags); | 224 | spin_unlock_irqrestore(&inject_lock, flags); |
212 | return ops->read(bus, devfn, where, size, val); | 225 | return rv; |
213 | } | 226 | } |
214 | 227 | ||
215 | static int aer_inj_write_config(struct pci_bus *bus, unsigned int devfn, | 228 | static int aer_inj_write_config(struct pci_bus *bus, unsigned int devfn, |
@@ -220,7 +233,9 @@ static int aer_inj_write_config(struct pci_bus *bus, unsigned int devfn, | |||
220 | unsigned long flags; | 233 | unsigned long flags; |
221 | int rw1cs; | 234 | int rw1cs; |
222 | struct pci_ops *ops; | 235 | struct pci_ops *ops; |
236 | struct pci_ops *my_ops; | ||
223 | int domain; | 237 | int domain; |
238 | int rv; | ||
224 | 239 | ||
225 | spin_lock_irqsave(&inject_lock, flags); | 240 | spin_lock_irqsave(&inject_lock, flags); |
226 | if (size != sizeof(u32)) | 241 | if (size != sizeof(u32)) |
@@ -243,8 +258,19 @@ static int aer_inj_write_config(struct pci_bus *bus, unsigned int devfn, | |||
243 | } | 258 | } |
244 | out: | 259 | out: |
245 | ops = __find_pci_bus_ops(bus); | 260 | ops = __find_pci_bus_ops(bus); |
261 | /* | ||
262 | * pci_lock must already be held, so we can directly | ||
263 | * manipulate bus->ops. Many config access functions, | ||
264 | * including pci_generic_config_write() require the original | ||
265 | * bus->ops be installed to function, so temporarily put them | ||
266 | * back. | ||
267 | */ | ||
268 | my_ops = bus->ops; | ||
269 | bus->ops = ops; | ||
270 | rv = ops->write(bus, devfn, where, size, val); | ||
271 | bus->ops = my_ops; | ||
246 | spin_unlock_irqrestore(&inject_lock, flags); | 272 | spin_unlock_irqrestore(&inject_lock, flags); |
247 | return ops->write(bus, devfn, where, size, val); | 273 | return rv; |
248 | } | 274 | } |
249 | 275 | ||
250 | static struct pci_ops aer_inj_pci_ops = { | 276 | static struct pci_ops aer_inj_pci_ops = { |