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>
This commit is contained in:
parent
3b0a6d1a1b
commit
7e8fbdc628
|
@ -188,7 +188,9 @@ static int aer_inj_read_config(struct pci_bus *bus, unsigned int devfn,
|
|||
struct aer_error *err;
|
||||
unsigned long flags;
|
||||
struct pci_ops *ops;
|
||||
struct pci_ops *my_ops;
|
||||
int domain;
|
||||
int rv;
|
||||
|
||||
spin_lock_irqsave(&inject_lock, flags);
|
||||
if (size != sizeof(u32))
|
||||
|
@ -208,8 +210,19 @@ static int aer_inj_read_config(struct pci_bus *bus, unsigned int devfn,
|
|||
}
|
||||
out:
|
||||
ops = __find_pci_bus_ops(bus);
|
||||
/*
|
||||
* pci_lock must already be held, so we can directly
|
||||
* manipulate bus->ops. Many config access functions,
|
||||
* including pci_generic_config_read() require the original
|
||||
* bus->ops be installed to function, so temporarily put them
|
||||
* back.
|
||||
*/
|
||||
my_ops = bus->ops;
|
||||
bus->ops = ops;
|
||||
rv = ops->read(bus, devfn, where, size, val);
|
||||
bus->ops = my_ops;
|
||||
spin_unlock_irqrestore(&inject_lock, flags);
|
||||
return ops->read(bus, devfn, where, size, val);
|
||||
return rv;
|
||||
}
|
||||
|
||||
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,
|
|||
unsigned long flags;
|
||||
int rw1cs;
|
||||
struct pci_ops *ops;
|
||||
struct pci_ops *my_ops;
|
||||
int domain;
|
||||
int rv;
|
||||
|
||||
spin_lock_irqsave(&inject_lock, flags);
|
||||
if (size != sizeof(u32))
|
||||
|
@ -243,8 +258,19 @@ static int aer_inj_write_config(struct pci_bus *bus, unsigned int devfn,
|
|||
}
|
||||
out:
|
||||
ops = __find_pci_bus_ops(bus);
|
||||
/*
|
||||
* pci_lock must already be held, so we can directly
|
||||
* manipulate bus->ops. Many config access functions,
|
||||
* including pci_generic_config_write() require the original
|
||||
* bus->ops be installed to function, so temporarily put them
|
||||
* back.
|
||||
*/
|
||||
my_ops = bus->ops;
|
||||
bus->ops = ops;
|
||||
rv = ops->write(bus, devfn, where, size, val);
|
||||
bus->ops = my_ops;
|
||||
spin_unlock_irqrestore(&inject_lock, flags);
|
||||
return ops->write(bus, devfn, where, size, val);
|
||||
return rv;
|
||||
}
|
||||
|
||||
static struct pci_ops aer_inj_pci_ops = {
|
||||
|
|
Loading…
Reference in a new issue