Skip to content

Commit bdbbae2

Browse files
Bartosz GolaszewskiPolynomialDivision
andcommitted
gpiolib: protect the GPIO device against being dropped while in use by user-space
While any of the GPIO cdev syscalls is in progress, the kernel can call gpiochip_remove() (for instance, when a USB GPIO expander is disconnected) which will set gdev->chip to NULL after which any subsequent access will cause a crash. To avoid that: use an RW-semaphore in which the syscalls take it for reading (so that we don't needlessly prohibit the user-space from calling syscalls simultaneously) while gpiochip_remove() takes it for writing so that it can only happen once all syscalls return. Fixes: d7c51b4 ("gpio: userspace ABI for reading/writing GPIO lines") Fixes: 3c0d9c6 ("gpiolib: cdev: support GPIO_V2_GET_LINE_IOCTL and GPIO_V2_LINE_GET_VALUES_IOCTL") Fixes: aad9558 ("gpiolib: cdev: support GPIO_V2_GET_LINEINFO_IOCTL and GPIO_V2_GET_LINEINFO_WATCH_IOCTL") Fixes: a54756c ("gpiolib: cdev: support GPIO_V2_LINE_SET_CONFIG_IOCTL") Fixes: 7b8e00d ("gpiolib: cdev: support GPIO_V2_LINE_SET_VALUES_IOCTL") Signed-off-by: Bartosz Golaszewski <[email protected]> [Nick: fixed a build failure with CDEV_V1 disabled] Co-authored-by: Nick Hainke <[email protected]> Reviewed-by: Kent Gibson <[email protected]> Reviewed-by: Andy Shevchenko <[email protected]> Reviewed-by: Linus Walleij <[email protected]>
1 parent 533aae7 commit bdbbae2

File tree

3 files changed

+161
-25
lines changed

3 files changed

+161
-25
lines changed

drivers/gpio/gpiolib-cdev.c

Lines changed: 152 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,50 @@ static_assert(IS_ALIGNED(sizeof(struct gpio_v2_line_values), 8));
5555
* interface to gpiolib GPIOs via ioctl()s.
5656
*/
5757

58+
typedef __poll_t (*poll_fn)(struct file *, struct poll_table_struct *);
59+
typedef long (*ioctl_fn)(struct file *, unsigned int, unsigned long);
60+
typedef ssize_t (*read_fn)(struct file *, char __user *,
61+
size_t count, loff_t *);
62+
63+
static __poll_t call_poll_locked(struct file *file,
64+
struct poll_table_struct *wait,
65+
struct gpio_device *gdev, poll_fn func)
66+
{
67+
__poll_t ret;
68+
69+
down_read(&gdev->sem);
70+
ret = func(file, wait);
71+
up_read(&gdev->sem);
72+
73+
return ret;
74+
}
75+
76+
static long call_ioctl_locked(struct file *file, unsigned int cmd,
77+
unsigned long arg, struct gpio_device *gdev,
78+
ioctl_fn func)
79+
{
80+
long ret;
81+
82+
down_read(&gdev->sem);
83+
ret = func(file, cmd, arg);
84+
up_read(&gdev->sem);
85+
86+
return ret;
87+
}
88+
89+
static ssize_t call_read_locked(struct file *file, char __user *buf,
90+
size_t count, loff_t *f_ps,
91+
struct gpio_device *gdev, read_fn func)
92+
{
93+
ssize_t ret;
94+
95+
down_read(&gdev->sem);
96+
ret = func(file, buf, count, f_ps);
97+
up_read(&gdev->sem);
98+
99+
return ret;
100+
}
101+
58102
/*
59103
* GPIO line handle management
60104
*/
@@ -191,8 +235,8 @@ static long linehandle_set_config(struct linehandle_state *lh,
191235
return 0;
192236
}
193237

194-
static long linehandle_ioctl(struct file *file, unsigned int cmd,
195-
unsigned long arg)
238+
static long linehandle_ioctl_unlocked(struct file *file, unsigned int cmd,
239+
unsigned long arg)
196240
{
197241
struct linehandle_state *lh = file->private_data;
198242
void __user *ip = (void __user *)arg;
@@ -250,6 +294,15 @@ static long linehandle_ioctl(struct file *file, unsigned int cmd,
250294
}
251295
}
252296

297+
static long linehandle_ioctl(struct file *file, unsigned int cmd,
298+
unsigned long arg)
299+
{
300+
struct linehandle_state *lh = file->private_data;
301+
302+
return call_ioctl_locked(file, cmd, arg, lh->gdev,
303+
linehandle_ioctl_unlocked);
304+
}
305+
253306
#ifdef CONFIG_COMPAT
254307
static long linehandle_ioctl_compat(struct file *file, unsigned int cmd,
255308
unsigned long arg)
@@ -1381,8 +1434,8 @@ static long linereq_set_config(struct linereq *lr, void __user *ip)
13811434
return ret;
13821435
}
13831436

1384-
static long linereq_ioctl(struct file *file, unsigned int cmd,
1385-
unsigned long arg)
1437+
static long linereq_ioctl_unlocked(struct file *file, unsigned int cmd,
1438+
unsigned long arg)
13861439
{
13871440
struct linereq *lr = file->private_data;
13881441
void __user *ip = (void __user *)arg;
@@ -1402,6 +1455,15 @@ static long linereq_ioctl(struct file *file, unsigned int cmd,
14021455
}
14031456
}
14041457

1458+
static long linereq_ioctl(struct file *file, unsigned int cmd,
1459+
unsigned long arg)
1460+
{
1461+
struct linereq *lr = file->private_data;
1462+
1463+
return call_ioctl_locked(file, cmd, arg, lr->gdev,
1464+
linereq_ioctl_unlocked);
1465+
}
1466+
14051467
#ifdef CONFIG_COMPAT
14061468
static long linereq_ioctl_compat(struct file *file, unsigned int cmd,
14071469
unsigned long arg)
@@ -1410,8 +1472,8 @@ static long linereq_ioctl_compat(struct file *file, unsigned int cmd,
14101472
}
14111473
#endif
14121474

1413-
static __poll_t linereq_poll(struct file *file,
1414-
struct poll_table_struct *wait)
1475+
static __poll_t linereq_poll_unlocked(struct file *file,
1476+
struct poll_table_struct *wait)
14151477
{
14161478
struct linereq *lr = file->private_data;
14171479
__poll_t events = 0;
@@ -1428,10 +1490,16 @@ static __poll_t linereq_poll(struct file *file,
14281490
return events;
14291491
}
14301492

1431-
static ssize_t linereq_read(struct file *file,
1432-
char __user *buf,
1433-
size_t count,
1434-
loff_t *f_ps)
1493+
static __poll_t linereq_poll(struct file *file,
1494+
struct poll_table_struct *wait)
1495+
{
1496+
struct linereq *lr = file->private_data;
1497+
1498+
return call_poll_locked(file, wait, lr->gdev, linereq_poll_unlocked);
1499+
}
1500+
1501+
static ssize_t linereq_read_unlocked(struct file *file, char __user *buf,
1502+
size_t count, loff_t *f_ps)
14351503
{
14361504
struct linereq *lr = file->private_data;
14371505
struct gpio_v2_line_event le;
@@ -1485,6 +1553,15 @@ static ssize_t linereq_read(struct file *file,
14851553
return bytes_read;
14861554
}
14871555

1556+
static ssize_t linereq_read(struct file *file, char __user *buf,
1557+
size_t count, loff_t *f_ps)
1558+
{
1559+
struct linereq *lr = file->private_data;
1560+
1561+
return call_read_locked(file, buf, count, f_ps, lr->gdev,
1562+
linereq_read_unlocked);
1563+
}
1564+
14881565
static void linereq_free(struct linereq *lr)
14891566
{
14901567
unsigned int i;
@@ -1722,8 +1799,8 @@ struct lineevent_state {
17221799
(GPIOEVENT_REQUEST_RISING_EDGE | \
17231800
GPIOEVENT_REQUEST_FALLING_EDGE)
17241801

1725-
static __poll_t lineevent_poll(struct file *file,
1726-
struct poll_table_struct *wait)
1802+
static __poll_t lineevent_poll_unlocked(struct file *file,
1803+
struct poll_table_struct *wait)
17271804
{
17281805
struct lineevent_state *le = file->private_data;
17291806
__poll_t events = 0;
@@ -1739,15 +1816,21 @@ static __poll_t lineevent_poll(struct file *file,
17391816
return events;
17401817
}
17411818

1819+
static __poll_t lineevent_poll(struct file *file,
1820+
struct poll_table_struct *wait)
1821+
{
1822+
struct lineevent_state *le = file->private_data;
1823+
1824+
return call_poll_locked(file, wait, le->gdev, lineevent_poll_unlocked);
1825+
}
1826+
17421827
struct compat_gpioeevent_data {
17431828
compat_u64 timestamp;
17441829
u32 id;
17451830
};
17461831

1747-
static ssize_t lineevent_read(struct file *file,
1748-
char __user *buf,
1749-
size_t count,
1750-
loff_t *f_ps)
1832+
static ssize_t lineevent_read_unlocked(struct file *file, char __user *buf,
1833+
size_t count, loff_t *f_ps)
17511834
{
17521835
struct lineevent_state *le = file->private_data;
17531836
struct gpioevent_data ge;
@@ -1815,6 +1898,15 @@ static ssize_t lineevent_read(struct file *file,
18151898
return bytes_read;
18161899
}
18171900

1901+
static ssize_t lineevent_read(struct file *file, char __user *buf,
1902+
size_t count, loff_t *f_ps)
1903+
{
1904+
struct lineevent_state *le = file->private_data;
1905+
1906+
return call_read_locked(file, buf, count, f_ps, le->gdev,
1907+
lineevent_read_unlocked);
1908+
}
1909+
18181910
static void lineevent_free(struct lineevent_state *le)
18191911
{
18201912
if (le->irq)
@@ -1832,8 +1924,8 @@ static int lineevent_release(struct inode *inode, struct file *file)
18321924
return 0;
18331925
}
18341926

1835-
static long lineevent_ioctl(struct file *file, unsigned int cmd,
1836-
unsigned long arg)
1927+
static long lineevent_ioctl_unlocked(struct file *file, unsigned int cmd,
1928+
unsigned long arg)
18371929
{
18381930
struct lineevent_state *le = file->private_data;
18391931
void __user *ip = (void __user *)arg;
@@ -1864,6 +1956,15 @@ static long lineevent_ioctl(struct file *file, unsigned int cmd,
18641956
return -EINVAL;
18651957
}
18661958

1959+
static long lineevent_ioctl(struct file *file, unsigned int cmd,
1960+
unsigned long arg)
1961+
{
1962+
struct lineevent_state *le = file->private_data;
1963+
1964+
return call_ioctl_locked(file, cmd, arg, le->gdev,
1965+
lineevent_ioctl_unlocked);
1966+
}
1967+
18671968
#ifdef CONFIG_COMPAT
18681969
static long lineevent_ioctl_compat(struct file *file, unsigned int cmd,
18691970
unsigned long arg)
@@ -2422,8 +2523,8 @@ static int lineinfo_changed_notify(struct notifier_block *nb,
24222523
return NOTIFY_OK;
24232524
}
24242525

2425-
static __poll_t lineinfo_watch_poll(struct file *file,
2426-
struct poll_table_struct *pollt)
2526+
static __poll_t lineinfo_watch_poll_unlocked(struct file *file,
2527+
struct poll_table_struct *pollt)
24272528
{
24282529
struct gpio_chardev_data *cdev = file->private_data;
24292530
__poll_t events = 0;
@@ -2440,8 +2541,17 @@ static __poll_t lineinfo_watch_poll(struct file *file,
24402541
return events;
24412542
}
24422543

2443-
static ssize_t lineinfo_watch_read(struct file *file, char __user *buf,
2444-
size_t count, loff_t *off)
2544+
static __poll_t lineinfo_watch_poll(struct file *file,
2545+
struct poll_table_struct *pollt)
2546+
{
2547+
struct gpio_chardev_data *cdev = file->private_data;
2548+
2549+
return call_poll_locked(file, pollt, cdev->gdev,
2550+
lineinfo_watch_poll_unlocked);
2551+
}
2552+
2553+
static ssize_t lineinfo_watch_read_unlocked(struct file *file, char __user *buf,
2554+
size_t count, loff_t *off)
24452555
{
24462556
struct gpio_chardev_data *cdev = file->private_data;
24472557
struct gpio_v2_line_info_changed event;
@@ -2519,6 +2629,15 @@ static ssize_t lineinfo_watch_read(struct file *file, char __user *buf,
25192629
return bytes_read;
25202630
}
25212631

2632+
static ssize_t lineinfo_watch_read(struct file *file, char __user *buf,
2633+
size_t count, loff_t *off)
2634+
{
2635+
struct gpio_chardev_data *cdev = file->private_data;
2636+
2637+
return call_read_locked(file, buf, count, off, cdev->gdev,
2638+
lineinfo_watch_read_unlocked);
2639+
}
2640+
25222641
/**
25232642
* gpio_chrdev_open() - open the chardev for ioctl operations
25242643
* @inode: inode for this chardev
@@ -2532,13 +2651,17 @@ static int gpio_chrdev_open(struct inode *inode, struct file *file)
25322651
struct gpio_chardev_data *cdev;
25332652
int ret = -ENOMEM;
25342653

2654+
down_read(&gdev->sem);
2655+
25352656
/* Fail on open if the backing gpiochip is gone */
2536-
if (!gdev->chip)
2537-
return -ENODEV;
2657+
if (!gdev->chip) {
2658+
ret = -ENODEV;
2659+
goto out_unlock;
2660+
}
25382661

25392662
cdev = kzalloc(sizeof(*cdev), GFP_KERNEL);
25402663
if (!cdev)
2541-
return -ENOMEM;
2664+
goto out_unlock;
25422665

25432666
cdev->watched_lines = bitmap_zalloc(gdev->chip->ngpio, GFP_KERNEL);
25442667
if (!cdev->watched_lines)
@@ -2561,6 +2684,8 @@ static int gpio_chrdev_open(struct inode *inode, struct file *file)
25612684
if (ret)
25622685
goto out_unregister_notifier;
25632686

2687+
up_read(&gdev->sem);
2688+
25642689
return ret;
25652690

25662691
out_unregister_notifier:
@@ -2570,6 +2695,8 @@ static int gpio_chrdev_open(struct inode *inode, struct file *file)
25702695
bitmap_free(cdev->watched_lines);
25712696
out_free_cdev:
25722697
kfree(cdev);
2698+
out_unlock:
2699+
up_read(&gdev->sem);
25732700
return ret;
25742701
}
25752702

drivers/gpio/gpiolib.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -790,6 +790,7 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc, void *data,
790790
spin_unlock_irqrestore(&gpio_lock, flags);
791791

792792
BLOCKING_INIT_NOTIFIER_HEAD(&gdev->notifier);
793+
init_rwsem(&gdev->sem);
793794

794795
#ifdef CONFIG_PINCTRL
795796
INIT_LIST_HEAD(&gdev->pin_ranges);
@@ -924,6 +925,8 @@ void gpiochip_remove(struct gpio_chip *gc)
924925
unsigned long flags;
925926
unsigned int i;
926927

928+
down_write(&gdev->sem);
929+
927930
/* FIXME: should the legacy sysfs handling be moved to gpio_device? */
928931
gpiochip_sysfs_unregister(gdev);
929932
gpiochip_free_hogs(gc);
@@ -958,6 +961,7 @@ void gpiochip_remove(struct gpio_chip *gc)
958961
* gone.
959962
*/
960963
gcdev_unregister(gdev);
964+
up_write(&gdev->sem);
961965
put_device(&gdev->dev);
962966
}
963967
EXPORT_SYMBOL_GPL(gpiochip_remove);

drivers/gpio/gpiolib.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include <linux/device.h>
1616
#include <linux/module.h>
1717
#include <linux/cdev.h>
18+
#include <linux/rwsem.h>
1819

1920
#define GPIOCHIP_NAME "gpiochip"
2021

@@ -39,6 +40,9 @@
3940
* @list: links gpio_device:s together for traversal
4041
* @notifier: used to notify subscribers about lines being requested, released
4142
* or reconfigured
43+
* @sem: protects the structure from a NULL-pointer dereference of @chip by
44+
* user-space operations when the device gets unregistered during
45+
* a hot-unplug event
4246
* @pin_ranges: range of pins served by the GPIO driver
4347
*
4448
* This state container holds most of the runtime variable data
@@ -60,6 +64,7 @@ struct gpio_device {
6064
void *data;
6165
struct list_head list;
6266
struct blocking_notifier_head notifier;
67+
struct rw_semaphore sem;
6368

6469
#ifdef CONFIG_PINCTRL
6570
/*

0 commit comments

Comments
 (0)