[PATCH] cdx: Fix atomicity violation in cdx_bus_match() and cdx_probe()

Qiu-ji Chen posted 1 patch 1 month, 1 week ago
drivers/cdx/cdx.c | 37 +++++++++++++++++++++++++++----------
1 file changed, 27 insertions(+), 10 deletions(-)
[PATCH] cdx: Fix atomicity violation in cdx_bus_match() and cdx_probe()
Posted by Qiu-ji Chen 1 month, 1 week ago
An atomicity violation occurs during consecutive reads of the variable 
cdx_dev->driver_override. Imagine a scenario: while evaluating the 
statement if (cdx_dev->driver_override && strcmp(cdx_dev->driver_override, 
drv->name)), the value of cdx_dev->driver_override changes, leading to an 
inconsistency where the value of cdx_dev->driver_override is the old value 
when passing the non-null check, but the new value when evaluated by 
strcmp(). This causes an inconsistency.

The second error occurs during the validation of cdx_dev->driver_override. 
The logic of this error is similar to the first one, as the entire process 
is not protected by a lock, leading to an inconsistency in the values of 
cdx_dev->driver_override before and after the reads.

The third error occurs in driver_override_show() when executing the 
statement return sysfs_emit(buf, "%s\n", cdx_dev->driver_override);. 
Since the string changes byte by byte, it is possible for a partially 
modified cdx_dev->driver_override value to be used in this statement, 
leading to an incorrect return value from the program.

To fix these issues, for the first and second problems, since we need to 
protect the entire process of reading the variable cdx_dev->driver_override
with a lock, we introduced a variable ret and an out block. For each branch
in this section, we replaced the return statements with assignments to the
variable ret, and then used a goto statement to directly execute the out 
block, making the code overall more concise.

For the third problem, we adopted a similar approach to the one used in the
modalias_show() function, protecting the process of reading 
cdx_dev->driver_override with a lock, ensuring that the program runs 
correctly.

This possible bug is found by an experimental static analysis tool
developed by our team. This tool analyzes the locking APIs to extract
function pairs that can be concurrently executed, and then analyzes the
instructions in the paired functions to identify possible concurrency bugs
including data races and atomicity violations.

Fixes: 2959ab247061 ("cdx: add the cdx bus driver")
Fixes: 48a6c7bced2a ("cdx: add device attributes")
Cc: stable@vger.kernel.org
Signed-off-by: Qiu-ji Chen <chenqiuji666@gmail.com>
---
 drivers/cdx/cdx.c | 37 +++++++++++++++++++++++++++----------
 1 file changed, 27 insertions(+), 10 deletions(-)

diff --git a/drivers/cdx/cdx.c b/drivers/cdx/cdx.c
index 07371cb653d3..fae03c89f818 100644
--- a/drivers/cdx/cdx.c
+++ b/drivers/cdx/cdx.c
@@ -268,6 +268,7 @@ static int cdx_bus_match(struct device *dev, const struct device_driver *drv)
 	const struct cdx_driver *cdx_drv = to_cdx_driver(drv);
 	const struct cdx_device_id *found_id = NULL;
 	const struct cdx_device_id *ids;
+	int ret = false;
 
 	if (cdx_dev->is_bus)
 		return false;
@@ -275,28 +276,40 @@ static int cdx_bus_match(struct device *dev, const struct device_driver *drv)
 	ids = cdx_drv->match_id_table;
 
 	/* When driver_override is set, only bind to the matching driver */
-	if (cdx_dev->driver_override && strcmp(cdx_dev->driver_override, drv->name))
-		return false;
+	device_lock(dev);
+	if (cdx_dev->driver_override && strcmp(cdx_dev->driver_override, drv->name)) {
+		ret = false;
+		goto out;
+	}
 
 	found_id = cdx_match_id(ids, cdx_dev);
-	if (!found_id)
-		return false;
+	if (!found_id) {
+		ret = false;
+		goto out;
+	}
 
 	do {
 		/*
 		 * In case override_only was set, enforce driver_override
 		 * matching.
 		 */
-		if (!found_id->override_only)
-			return true;
-		if (cdx_dev->driver_override)
-			return true;
+		if (!found_id->override_only) {
+			ret = true;
+			goto out;
+		}
+		if (cdx_dev->driver_override) {
+			ret = true;
+			goto out;
+		}
 
 		ids = found_id + 1;
 		found_id = cdx_match_id(ids, cdx_dev);
 	} while (found_id);
 
-	return false;
+	ret = false;
+out:
+	device_unlock(dev);
+	return ret;
 }
 
 static int cdx_probe(struct device *dev)
@@ -470,8 +483,12 @@ static ssize_t driver_override_show(struct device *dev,
 				    struct device_attribute *attr, char *buf)
 {
 	struct cdx_device *cdx_dev = to_cdx_device(dev);
+	ssize_t len;
 
-	return sysfs_emit(buf, "%s\n", cdx_dev->driver_override);
+	device_lock(dev);
+	len = sysfs_emit(buf, "%s\n", cdx_dev->driver_override);
+	device_unlock(dev);
+	return len;
 }
 static DEVICE_ATTR_RW(driver_override);
 
-- 
2.34.1