From: Dan Williams <dan.j.willi...@intel.com>

commit 7018c897c2f243d4b5f1b94bc6b4831a7eab80fb upstream

Richard reports that the following test:

(while true; do
     cat /sys/bus/nd/devices/nmem*/available_slots 2>&1 > /dev/null
 done) &

while true; do
     for i in $(seq 0 4); do
         echo nmem$i > /sys/bus/nd/drivers/nvdimm/bind
     done
     for i in $(seq 0 4); do
         echo nmem$i > /sys/bus/nd/drivers/nvdimm/unbind
     done
 done

...fails with a crash signature like:

    divide error: 0000 [#1] SMP KASAN PTI
    RIP: 0010:nd_label_nfree+0x134/0x1a0 [libnvdimm]
    [..]
    Call Trace:
     available_slots_show+0x4e/0x120 [libnvdimm]
     dev_attr_show+0x42/0x80
     ? memset+0x20/0x40
     sysfs_kf_seq_show+0x218/0x410

The root cause is that available_slots_show() consults driver-data, but
fails to synchronize against device-unbind setting up a TOCTOU race to
access uninitialized memory.

Validate driver-data under the device-lock.

Fixes: 4d88a97aa9e8 ("libnvdimm, nvdimm: dimm driver and base libnvdimm 
device-driver infrastructure")
Cc: <sta...@vger.kernel.org>
Cc: Vishal Verma <vishal.l.ve...@intel.com>
Cc: Dave Jiang <dave.ji...@intel.com>
Cc: Ira Weiny <ira.we...@intel.com>
Cc: Coly Li <col...@suse.com>
Reported-by: Richard Palethorpe <rpaletho...@suse.com>
Acked-by: Richard Palethorpe <rpaletho...@suse.com>
Signed-off-by: Dan Williams <dan.j.willi...@intel.com>
[sudip: use device_lock()]
Signed-off-by: Sudip Mukherjee <sudipm.mukher...@gmail.com>
Signed-off-by: Greg Kroah-Hartman <gre...@linuxfoundation.org>
---
 drivers/nvdimm/dimm_devs.c |   18 +++++++++++++++---
 1 file changed, 15 insertions(+), 3 deletions(-)

--- a/drivers/nvdimm/dimm_devs.c
+++ b/drivers/nvdimm/dimm_devs.c
@@ -317,16 +317,16 @@ static ssize_t state_show(struct device
 }
 static DEVICE_ATTR_RO(state);
 
-static ssize_t available_slots_show(struct device *dev,
-               struct device_attribute *attr, char *buf)
+static ssize_t __available_slots_show(struct nvdimm_drvdata *ndd, char *buf)
 {
-       struct nvdimm_drvdata *ndd = dev_get_drvdata(dev);
+       struct device *dev;
        ssize_t rc;
        u32 nfree;
 
        if (!ndd)
                return -ENXIO;
 
+       dev = ndd->dev;
        nvdimm_bus_lock(dev);
        nfree = nd_label_nfree(ndd);
        if (nfree - 1 > nfree) {
@@ -338,6 +338,18 @@ static ssize_t available_slots_show(stru
        nvdimm_bus_unlock(dev);
        return rc;
 }
+
+static ssize_t available_slots_show(struct device *dev,
+                                   struct device_attribute *attr, char *buf)
+{
+       ssize_t rc;
+
+       device_lock(dev);
+       rc = __available_slots_show(dev_get_drvdata(dev), buf);
+       device_unlock(dev);
+
+       return rc;
+}
 static DEVICE_ATTR_RO(available_slots);
 
 static struct attribute *nvdimm_attributes[] = {


Reply via email to