[PATCH/RFC] pci: dynids.use_driver_data considered harmful

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



The driver flag dynids.use_driver_data is almost consistently not set, and
causes more problems than it solves.  The only use of this flag is to
prevent the system administrator from telling a pci driver additional data
when adding a dynamic match table entry via sysfs.

We do not as a policy protect the kernel from bad acts by root.

If we are worried about drivers doing the wrong thing due the match data, we
should be setting a taint flag, not attempting to cut off the admin.

[1] Searching the next-20080709 tree shows the bit is set by exactly 3 pci
drivers.  However, the use of per-match-entry driver data is much more
prevalent: A boot of allyesconfig on a powerpc64 pseries with a debug patch
shows 27 drivers apparently use the field for a pointer, 14 use it for
setting flags, and 98 use it as a table index.  (Pointers are defined as
>PAGE_OFFSET, aka in the 64 bit kernel linear mapping.  Flags are defined as
the maximum value exceeds the number of entries in the match table.  Any
other nonzero value is classified as an index).


Signed-off-by: Milton Miller <miltonm@xxxxxxx>
---
This behavior actually caused me several hours of debug recently.

I was working working with a cxgb3 card on a 2.6.24 driver.  The card was
not recognized by the system, because the subsystem device id (owned by the
subsystem vendor, not the silicon manufacturer) did not match the driver
specified value of 1.   I inspected the driver expected to handle the card,
found the mismatch, and attempted to add a dynamic id through sysfs to get
the driver to match.   I inspected the existing table in the code and noted
that the driver needed to be told "this is card type 2" via the additional
data, and added the appropriate number of zeros to place a 2 in the driver
data column.   However, the kernel quickly oopsed and crashed due to a bad
function call address.   Great, now the kernel is crashed, what is wrong
with the driver.  After coming back and trying another card, I had to start
tracing manually trying to find where the driver went astray.   I was able
to quickly determine the bad address was a text string, but that did not
help me find the problem.   The call site was in the middle of a large init
function, and it was not obvious even with the listing which function
pointer was corrupted.   While I was looking for some data dependent
overwrite, vpd formating or parsing problem, or endian issue, it turned out
not to be the case.   I eventually determined the following sequence of
events:

The driver took the driver data and performed a range check.   It then
indexed into a table, and (since it was given 0 when I specified 2), it
incorrectly determined the card had two ports instead of one.  It then
copied the vpd into an array, and indexed into the array based on a template
of what it thought the vpd would contain (as opposed to parsing the tags in
the vpd with their length fields).  The code then searched for descriptions
of the phy on each port.   It found port 0, indexed into another table and
filled out its pointers.   It then searched for the non-existent port 1,
searching for a non-zero value.   Because the card actually only had one
port, it went off the end of the array and found some random number in
memory.   It then used that random value as the type to index into its
second array to fill out the function pointers to be used for the second
port.  It proceeded on, in the same function after inlining, to iterate over
the ports on the card to initialize each port.   It calls the function for
the first port, all goes well.  It advances a pointer to point to the next
port, calls the first function, and oopses.


Of course, when debugging, one sees some function pointer is garbage.   But
which one?   (This was on PowerPC64, so the function pointer is a descriptor
pointer.  The pointer itself looks valid, but instead of pointing to a
descriptor with a code address and a constant pool, it points to random
kernel data.

Only after careful analysis of the driver data structure did I find it was
looking for more ports.   After all, I had told the driver to use slot two,
and only expected the card to have one port described in adapter info table>

While there is could certainly be more defensive coding in the driver (eg:
parse the vpd instead of relying on knowing the tag layout, only search
until the end of the vpd data, range check the port type before indexing
into an array), the whole session should have been avoided once I looked at
the driver and carefully set the driver data to entry 2, to match the other
users of the silicon.



diff --git a/drivers/i2c/busses/i2c-amd756.c b/drivers/i2c/busses/i2c-amd756.c
index 1ea3925..a3542b0 100644
--- a/drivers/i2c/busses/i2c-amd756.c
+++ b/drivers/i2c/busses/i2c-amd756.c
@@ -412,7 +412,6 @@ static struct pci_driver amd756_driver = {
 	.id_table	= amd756_ids,
 	.probe		= amd756_probe,
 	.remove		= __devexit_p(amd756_remove),
-	.dynids.use_driver_data = 1,
 };
 
 static int __init amd756_init(void)
diff --git a/drivers/i2c/busses/i2c-viapro.c b/drivers/i2c/busses/i2c-viapro.c
index 862eb35..87a04ea 100644
--- a/drivers/i2c/busses/i2c-viapro.c
+++ b/drivers/i2c/busses/i2c-viapro.c
@@ -468,7 +468,6 @@ static struct pci_driver vt596_driver = {
 	.name		= "vt596_smbus",
 	.id_table	= vt596_ids,
 	.probe		= vt596_probe,
-	.dynids.use_driver_data = 1,
 };
 
 static int __init i2c_vt596_init(void)
diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c
index a13f534..4940a53 100644
--- a/drivers/pci/pci-driver.c
+++ b/drivers/pci/pci-driver.c
@@ -65,8 +65,7 @@ store_new_id(struct device_driver *driver, const char *buf, size_t count)
 	dynid->id.subdevice = subdevice;
 	dynid->id.class = class;
 	dynid->id.class_mask = class_mask;
-	dynid->id.driver_data = pdrv->dynids.use_driver_data ?
-		driver_data : 0UL;
+	dynid->id.driver_data = driver_data;
 
 	spin_lock(&pdrv->dynids.lock);
 	list_add_tail(&dynid->node, &pdrv->dynids.list);
diff --git a/drivers/scsi/ipr.c b/drivers/scsi/ipr.c
index 999e91e..42ad25a 100644
--- a/drivers/scsi/ipr.c
+++ b/drivers/scsi/ipr.c
@@ -7854,7 +7854,6 @@ static struct pci_driver ipr_driver = {
 	.remove = ipr_remove,
 	.shutdown = ipr_shutdown,
 	.err_handler = &ipr_err_handler,
-	.dynids.use_driver_data = 1
 };
 
 /**
diff --git a/include/linux/pci.h b/include/linux/pci.h
index a6a088e..b217a37 100644
--- a/include/linux/pci.h
+++ b/include/linux/pci.h
@@ -345,7 +345,6 @@ struct pci_bus_region {
 struct pci_dynids {
 	spinlock_t lock;            /* protects list, index */
 	struct list_head list;      /* for IDs added at runtime */
-	unsigned int use_driver_data:1; /* pci_device_id->driver_data is used */
 };
 
 /* ---------------------------------------------------------------- */
--
To unsubscribe from this list: send the line "unsubscribe linux-pci" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Index of Archives]     [DMA Engine]     [Linux Coverity]     [Linux USB]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Greybus]

  Powered by Linux