[PATCH] gpiolib: cdev: Fix use after free in lineinfo_changed_notify

From: Zhongqiu Han
Date: Tue Apr 30 2024 - 22:27:30 EST


The use-after-free issue occurs when userspace closes the GPIO chip device
file (e.g., "/dev/gpiochip0") by invoking gpio_chrdev_release(), while one
of its GPIO lines is simultaneously being released. In a stress test
scenario, stress-ng tool starts multi stress-ng-dev threads to continuously
open and close device file in the /dev, the use-after-free issue will occur
with a low reproducibility.

Here is the typical stack when issue happened:

BUG: KASAN: slab-use-after-free in lineinfo_changed_notify+0x84/0x1bc
Read of size 8 at addr ffffff803c06e840 by task stress-ng-dev/5437
Call trace:
dump_backtrace
show_stack
dump_stack_lvl
print_report
kasan_report
__asan_load8
lineinfo_changed_notify
notifier_call_chain
blocking_notifier_call_chain
gpiod_free_commit
gpiod_free
gpio_free
st54spi_gpio_dev_release
__fput
__fput_sync
__arm64_sys_close
invoke_syscall
el0_svc_common
do_el0_svc
el0_svc
el0t_64_sync_handler
el0t_64_sync
Allocated by task 5425:
kasan_set_track
kasan_save_alloc_info
__kasan_kmalloc
__kmalloc
bitmap_zalloc
gpio_chrdev_open
chrdev_open
do_dentry_open
vfs_open
path_openat
do_filp_open
do_sys_openat2
__arm64_sys_openat
invoke_syscall
el0_svc_common
do_el0_svc
el0_svc
el0t_64_sync_handler
el0t_64_sync
Freed by task 5425:
kasan_set_track
kasan_save_free_info
____kasan_slab_free
__kasan_slab_free
slab_free_freelist_hook
__kmem_cache_free
kfree
bitmap_free
gpio_chrdev_release
__fput
__fput_sync
__arm64_sys_close
invoke_syscall
el0_svc_common
do_el0_svc
el0_svc
el0t_64_sync_handler
el0t_64_sync

The use-after-free issue occurs as follows: watched_lines is freed by
bitmap_free(), but the lineinfo_changed_nb notifier chain cannot be
successfully unregistered due to waiting write rwsem when closing the
GPIO chip device file. Additionally, one of the GPIO chip's lines is
also in the release process and holds the notifier chain's read rwsem.
Consequently, a race condition leads to the use-after-free of
watched_lines.

[free]
gpio_chrdev_release()
--> bitmap_free(cdev->watched_lines) <-- freed
--> blocking_notifier_chain_unregister()
--> down_write(&nh->rwsem) <-- waiting rwsem
--> __down_write_common()
--> rwsem_down_write_slowpath()
--> schedule_preempt_disabled()
--> schedule()

[use]
st54spi_gpio_dev_release()
--> gpio_free()
--> gpiod_free()
--> gpiod_free_commit()
--> gpiod_line_state_notify()
--> blocking_notifier_call_chain()
--> down_read(&nh->rwsem); <-- held rwsem
--> notifier_call_chain()
--> lineinfo_changed_notify()
--> test_bit(xxxx, cdev->watched_lines) <-- use after free

To fix this issue, call the bitmap_free() function after successfully
unregistering the notifier chain. This prevents lineinfo_changed_notify()
from being called, thus avoiding the use-after-free race condition.

Fixes: 51c1064e82e7 ("gpiolib: add new ioctl() for monitoring changes in line info")
Signed-off-by: Zhongqiu Han <quic_zhonhan@xxxxxxxxxxx>
---
drivers/gpio/gpiolib-cdev.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/gpio/gpiolib-cdev.c b/drivers/gpio/gpiolib-cdev.c
index d09c7d728365..6b3a43e3ba47 100644
--- a/drivers/gpio/gpiolib-cdev.c
+++ b/drivers/gpio/gpiolib-cdev.c
@@ -2799,11 +2799,11 @@ static int gpio_chrdev_release(struct inode *inode, struct file *file)
struct gpio_chardev_data *cdev = file->private_data;
struct gpio_device *gdev = cdev->gdev;

- bitmap_free(cdev->watched_lines);
blocking_notifier_chain_unregister(&gdev->device_notifier,
&cdev->device_unregistered_nb);
blocking_notifier_chain_unregister(&gdev->line_state_notifier,
&cdev->lineinfo_changed_nb);
+ bitmap_free(cdev->watched_lines);
gpio_device_put(gdev);
kfree(cdev);

--
2.25.1