]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
greybus: raw: fix use-after-free if write is called after disconnect
authorDamien Riégel <damien.riegel@silabs.com>
Tue, 24 Mar 2026 14:00:39 +0000 (10:00 -0400)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Tue, 31 Mar 2026 08:33:48 +0000 (10:33 +0200)
If a user writes to the chardev after disconnect has been called, the
kernel panics with the following trace (with
CONFIG_INIT_ON_FREE_DEFAULT_ON=y):

        BUG: kernel NULL pointer dereference, address: 0000000000000218
         ...
        Call Trace:
         <TASK>
         gb_operation_create_common+0x61/0x180
         gb_operation_create_flags+0x28/0xa0
         gb_operation_sync_timeout+0x6f/0x100
         raw_write+0x7b/0xc7 [gb_raw]
         vfs_write+0xcf/0x420
         ? task_mm_cid_work+0x136/0x220
         ksys_write+0x63/0xe0
         do_syscall_64+0xa4/0x290
         entry_SYSCALL_64_after_hwframe+0x77/0x7f

Disconnect calls gb_connection_destroy, which ends up freeing the
connection object. When gb_operation_sync is called in the write file
operations, its gets a freed connection as parameter and the kernel
panics.

The gb_connection_destroy cannot be moved out of the disconnect
function, as the Greybus subsystem expect all connections belonging to a
bundle to be destroyed when disconnect returns.

To prevent this bug, use a rw lock to synchronize access between write
and disconnect. This guarantees that the write function doesn't try
to use a disconnected connection.

Fixes: e806c7fb8e9b ("greybus: raw: add raw greybus kernel driver")
Reviewed-by: Johan Hovold <johan@kernel.org>
Signed-off-by: Damien Riégel <damien.riegel@silabs.com>
Link: https://patch.msgid.link/20260324140039.40001-2-damien.riegel@silabs.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/staging/greybus/raw.c

index 47a9845546811519356fd8f3549dec515eefb534..459aed0f12401dbbe65c9c97810b84a54222052b 100644 (file)
@@ -21,6 +21,8 @@ struct gb_raw {
        struct list_head list;
        int list_data;
        struct mutex list_lock;
+       struct rw_semaphore disconnect_lock;
+       bool disconnected;
        struct cdev cdev;
        struct device dev;
 };
@@ -200,6 +202,7 @@ static int gb_raw_probe(struct gb_bundle *bundle,
 
        INIT_LIST_HEAD(&raw->list);
        mutex_init(&raw->list_lock);
+       init_rwsem(&raw->disconnect_lock);
 
        raw->connection = connection;
        greybus_set_drvdata(bundle, raw);
@@ -235,6 +238,11 @@ static void gb_raw_disconnect(struct gb_bundle *bundle)
        struct raw_data *temp;
 
        cdev_device_del(&raw->cdev, &raw->dev);
+
+       down_write(&raw->disconnect_lock);
+       raw->disconnected = true;
+       up_write(&raw->disconnect_lock);
+
        gb_connection_disable(connection);
        gb_connection_destroy(connection);
 
@@ -277,11 +285,22 @@ static ssize_t raw_write(struct file *file, const char __user *buf,
        if (count > MAX_PACKET_SIZE)
                return -E2BIG;
 
+       down_read(&raw->disconnect_lock);
+
+       if (raw->disconnected) {
+               retval = -ENODEV;
+               goto exit;
+       }
+
        retval = gb_raw_send(raw, count, buf);
        if (retval)
-               return retval;
+               goto exit;
 
-       return count;
+       retval = count;
+exit:
+       up_read(&raw->disconnect_lock);
+
+       return retval;
 }
 
 static ssize_t raw_read(struct file *file, char __user *buf, size_t count,