libnvdimm/dimm: Avoid race between probe and available_slots_show()
authorDan Williams <dan.j.williams@intel.com>
Tue, 2 Feb 2021 00:20:40 +0000 (16:20 -0800)
committerDan Williams <dan.j.williams@intel.com>
Tue, 2 Feb 2021 00:20:40 +0000 (16:20 -0800)
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: <stable@vger.kernel.org>
Cc: Vishal Verma <vishal.l.verma@intel.com>
Cc: Dave Jiang <dave.jiang@intel.com>
Cc: Ira Weiny <ira.weiny@intel.com>
Cc: Coly Li <colyli@suse.com>
Reported-by: Richard Palethorpe <rpalethorpe@suse.com>
Acked-by: Richard Palethorpe <rpalethorpe@suse.com>
Signed-off-by: Dan Williams <dan.j.williams@intel.com>
drivers/nvdimm/dimm_devs.c

index b59032e..9d20857 100644 (file)
@@ -335,16 +335,16 @@ static ssize_t state_show(struct device *dev, struct device_attribute *attr,
 }
 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) {
@@ -356,6 +356,18 @@ static ssize_t available_slots_show(struct device *dev,
        nvdimm_bus_unlock(dev);
        return rc;
 }
+
+static ssize_t available_slots_show(struct device *dev,
+                                   struct device_attribute *attr, char *buf)
+{
+       ssize_t rc;
+
+       nd_device_lock(dev);
+       rc = __available_slots_show(dev_get_drvdata(dev), buf);
+       nd_device_unlock(dev);
+
+       return rc;
+}
 static DEVICE_ATTR_RO(available_slots);
 
 __weak ssize_t security_show(struct device *dev,