| From 12c823a71adff1bb45ff4e1615762413309ef743 Mon Sep 17 00:00:00 2001 |
| From: Sasha Levin <sashal@kernel.org> |
| Date: Mon, 5 Dec 2022 13:39:02 +0100 |
| Subject: gpiolib: cdev: fix NULL-pointer dereferences |
| |
| From: Bartosz Golaszewski <bartosz.golaszewski@linaro.org> |
| |
| [ Upstream commit 533aae7c94dbc2b14301cfd68ae7e0e90f0c8438 ] |
| |
| There are several places where we can crash the kernel by requesting |
| lines, unbinding the GPIO device, then calling any of the system calls |
| relevant to the GPIO character device's annonymous file descriptors: |
| ioctl(), read(), poll(). |
| |
| While I observed it with the GPIO simulator, it will also happen for any |
| of the GPIO devices that can be hot-unplugged - for instance any HID GPIO |
| expander (e.g. CP2112). |
| |
| This affects both v1 and v2 uAPI. |
| |
| This fixes it partially by checking if gdev->chip is not NULL but it |
| doesn't entirely remedy the situation as we still have a race condition |
| in which another thread can remove the device after the check. |
| |
| Fixes: d7c51b47ac11 ("gpio: userspace ABI for reading/writing GPIO lines") |
| Fixes: 3c0d9c635ae2 ("gpiolib: cdev: support GPIO_V2_GET_LINE_IOCTL and GPIO_V2_LINE_GET_VALUES_IOCTL") |
| Fixes: aad955842d1c ("gpiolib: cdev: support GPIO_V2_GET_LINEINFO_IOCTL and GPIO_V2_GET_LINEINFO_WATCH_IOCTL") |
| Fixes: a54756cb24ea ("gpiolib: cdev: support GPIO_V2_LINE_SET_CONFIG_IOCTL") |
| Fixes: 7b8e00d98168 ("gpiolib: cdev: support GPIO_V2_LINE_SET_VALUES_IOCTL") |
| Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@linaro.org> |
| Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com> |
| Reviewed-by: Linus Walleij <linus.walleij@linaro.org> |
| Signed-off-by: Sasha Levin <sashal@kernel.org> |
| --- |
| drivers/gpio/gpiolib-cdev.c | 27 +++++++++++++++++++++++++++ |
| 1 file changed, 27 insertions(+) |
| |
| diff --git a/drivers/gpio/gpiolib-cdev.c b/drivers/gpio/gpiolib-cdev.c |
| index a20396a6448b..64e37618216f 100644 |
| --- a/drivers/gpio/gpiolib-cdev.c |
| +++ b/drivers/gpio/gpiolib-cdev.c |
| @@ -200,6 +200,9 @@ static long linehandle_ioctl(struct file *file, unsigned int cmd, |
| unsigned int i; |
| int ret; |
| |
| + if (!lh->gdev->chip) |
| + return -ENODEV; |
| + |
| switch (cmd) { |
| case GPIOHANDLE_GET_LINE_VALUES_IOCTL: |
| /* NOTE: It's okay to read values of output lines */ |
| @@ -1188,6 +1191,9 @@ static long linereq_ioctl(struct file *file, unsigned int cmd, |
| struct linereq *lr = file->private_data; |
| void __user *ip = (void __user *)arg; |
| |
| + if (!lr->gdev->chip) |
| + return -ENODEV; |
| + |
| switch (cmd) { |
| case GPIO_V2_LINE_GET_VALUES_IOCTL: |
| return linereq_get_values(lr, ip); |
| @@ -1214,6 +1220,9 @@ static __poll_t linereq_poll(struct file *file, |
| struct linereq *lr = file->private_data; |
| __poll_t events = 0; |
| |
| + if (!lr->gdev->chip) |
| + return EPOLLHUP | EPOLLERR; |
| + |
| poll_wait(file, &lr->wait, wait); |
| |
| if (!kfifo_is_empty_spinlocked_noirqsave(&lr->events, |
| @@ -1233,6 +1242,9 @@ static ssize_t linereq_read(struct file *file, |
| ssize_t bytes_read = 0; |
| int ret; |
| |
| + if (!lr->gdev->chip) |
| + return -ENODEV; |
| + |
| if (count < sizeof(le)) |
| return -EINVAL; |
| |
| @@ -1498,6 +1510,9 @@ static __poll_t lineevent_poll(struct file *file, |
| struct lineevent_state *le = file->private_data; |
| __poll_t events = 0; |
| |
| + if (!le->gdev->chip) |
| + return EPOLLHUP | EPOLLERR; |
| + |
| poll_wait(file, &le->wait, wait); |
| |
| if (!kfifo_is_empty_spinlocked_noirqsave(&le->events, &le->wait.lock)) |
| @@ -1522,6 +1537,9 @@ static ssize_t lineevent_read(struct file *file, |
| ssize_t ge_size; |
| int ret; |
| |
| + if (!le->gdev->chip) |
| + return -ENODEV; |
| + |
| /* |
| * When compatible system call is being used the struct gpioevent_data, |
| * in case of at least ia32, has different size due to the alignment |
| @@ -1603,6 +1621,9 @@ static long lineevent_ioctl(struct file *file, unsigned int cmd, |
| void __user *ip = (void __user *)arg; |
| struct gpiohandle_data ghd; |
| |
| + if (!le->gdev->chip) |
| + return -ENODEV; |
| + |
| /* |
| * We can get the value for an event line but not set it, |
| * because it is input by definition. |
| @@ -2187,6 +2208,9 @@ static __poll_t lineinfo_watch_poll(struct file *file, |
| struct gpio_chardev_data *cdev = file->private_data; |
| __poll_t events = 0; |
| |
| + if (!cdev->gdev->chip) |
| + return EPOLLHUP | EPOLLERR; |
| + |
| poll_wait(file, &cdev->wait, pollt); |
| |
| if (!kfifo_is_empty_spinlocked_noirqsave(&cdev->events, |
| @@ -2205,6 +2229,9 @@ static ssize_t lineinfo_watch_read(struct file *file, char __user *buf, |
| int ret; |
| size_t event_size; |
| |
| + if (!cdev->gdev->chip) |
| + return -ENODEV; |
| + |
| #ifndef CONFIG_GPIO_CDEV_V1 |
| event_size = sizeof(struct gpio_v2_line_info_changed); |
| if (count < event_size) |
| -- |
| 2.35.1 |
| |