1*c7f92042STzung-Bi Shih // SPDX-License-Identifier: GPL-2.0-or-later
2*c7f92042STzung-Bi Shih /*
3*c7f92042STzung-Bi Shih * GPIO character device helper for UAF tests.
4*c7f92042STzung-Bi Shih *
5*c7f92042STzung-Bi Shih * Copyright 2026 Google LLC
6*c7f92042STzung-Bi Shih */
7*c7f92042STzung-Bi Shih
8*c7f92042STzung-Bi Shih #include <errno.h>
9*c7f92042STzung-Bi Shih #include <fcntl.h>
10*c7f92042STzung-Bi Shih #include <linux/gpio.h>
11*c7f92042STzung-Bi Shih #include <poll.h>
12*c7f92042STzung-Bi Shih #include <stdio.h>
13*c7f92042STzung-Bi Shih #include <stdlib.h>
14*c7f92042STzung-Bi Shih #include <string.h>
15*c7f92042STzung-Bi Shih #include <sys/ioctl.h>
16*c7f92042STzung-Bi Shih #include <sys/stat.h>
17*c7f92042STzung-Bi Shih #include <sys/types.h>
18*c7f92042STzung-Bi Shih #include <unistd.h>
19*c7f92042STzung-Bi Shih
20*c7f92042STzung-Bi Shih #define CONFIGFS_DIR "/sys/kernel/config/gpio-sim"
21*c7f92042STzung-Bi Shih #define PROCFS_DIR "/proc"
22*c7f92042STzung-Bi Shih
print_usage(void)23*c7f92042STzung-Bi Shih static void print_usage(void)
24*c7f92042STzung-Bi Shih {
25*c7f92042STzung-Bi Shih printf("usage:\n");
26*c7f92042STzung-Bi Shih printf(" gpio-cdev-uaf [chip|handle|event|req] [poll|read|ioctl]\n");
27*c7f92042STzung-Bi Shih }
28*c7f92042STzung-Bi Shih
_create_chip(const char * name,int create)29*c7f92042STzung-Bi Shih static int _create_chip(const char *name, int create)
30*c7f92042STzung-Bi Shih {
31*c7f92042STzung-Bi Shih char path[64];
32*c7f92042STzung-Bi Shih
33*c7f92042STzung-Bi Shih snprintf(path, sizeof(path), CONFIGFS_DIR "/%s", name);
34*c7f92042STzung-Bi Shih
35*c7f92042STzung-Bi Shih if (create)
36*c7f92042STzung-Bi Shih return mkdir(path, 0755);
37*c7f92042STzung-Bi Shih else
38*c7f92042STzung-Bi Shih return rmdir(path);
39*c7f92042STzung-Bi Shih }
40*c7f92042STzung-Bi Shih
create_chip(const char * name)41*c7f92042STzung-Bi Shih static int create_chip(const char *name)
42*c7f92042STzung-Bi Shih {
43*c7f92042STzung-Bi Shih return _create_chip(name, 1);
44*c7f92042STzung-Bi Shih }
45*c7f92042STzung-Bi Shih
remove_chip(const char * name)46*c7f92042STzung-Bi Shih static void remove_chip(const char *name)
47*c7f92042STzung-Bi Shih {
48*c7f92042STzung-Bi Shih _create_chip(name, 0);
49*c7f92042STzung-Bi Shih }
50*c7f92042STzung-Bi Shih
_create_bank(const char * chip_name,const char * name,int create)51*c7f92042STzung-Bi Shih static int _create_bank(const char *chip_name, const char *name, int create)
52*c7f92042STzung-Bi Shih {
53*c7f92042STzung-Bi Shih char path[64];
54*c7f92042STzung-Bi Shih
55*c7f92042STzung-Bi Shih snprintf(path, sizeof(path), CONFIGFS_DIR "/%s/%s", chip_name, name);
56*c7f92042STzung-Bi Shih
57*c7f92042STzung-Bi Shih if (create)
58*c7f92042STzung-Bi Shih return mkdir(path, 0755);
59*c7f92042STzung-Bi Shih else
60*c7f92042STzung-Bi Shih return rmdir(path);
61*c7f92042STzung-Bi Shih }
62*c7f92042STzung-Bi Shih
create_bank(const char * chip_name,const char * name)63*c7f92042STzung-Bi Shih static int create_bank(const char *chip_name, const char *name)
64*c7f92042STzung-Bi Shih {
65*c7f92042STzung-Bi Shih return _create_bank(chip_name, name, 1);
66*c7f92042STzung-Bi Shih }
67*c7f92042STzung-Bi Shih
remove_bank(const char * chip_name,const char * name)68*c7f92042STzung-Bi Shih static void remove_bank(const char *chip_name, const char *name)
69*c7f92042STzung-Bi Shih {
70*c7f92042STzung-Bi Shih _create_bank(chip_name, name, 0);
71*c7f92042STzung-Bi Shih }
72*c7f92042STzung-Bi Shih
_enable_chip(const char * name,int enable)73*c7f92042STzung-Bi Shih static int _enable_chip(const char *name, int enable)
74*c7f92042STzung-Bi Shih {
75*c7f92042STzung-Bi Shih char path[64];
76*c7f92042STzung-Bi Shih int fd, ret;
77*c7f92042STzung-Bi Shih
78*c7f92042STzung-Bi Shih snprintf(path, sizeof(path), CONFIGFS_DIR "/%s/live", name);
79*c7f92042STzung-Bi Shih
80*c7f92042STzung-Bi Shih fd = open(path, O_WRONLY);
81*c7f92042STzung-Bi Shih if (fd == -1)
82*c7f92042STzung-Bi Shih return fd;
83*c7f92042STzung-Bi Shih
84*c7f92042STzung-Bi Shih if (enable)
85*c7f92042STzung-Bi Shih ret = write(fd, "1", 1);
86*c7f92042STzung-Bi Shih else
87*c7f92042STzung-Bi Shih ret = write(fd, "0", 1);
88*c7f92042STzung-Bi Shih
89*c7f92042STzung-Bi Shih close(fd);
90*c7f92042STzung-Bi Shih return ret == 1 ? 0 : -1;
91*c7f92042STzung-Bi Shih }
92*c7f92042STzung-Bi Shih
enable_chip(const char * name)93*c7f92042STzung-Bi Shih static int enable_chip(const char *name)
94*c7f92042STzung-Bi Shih {
95*c7f92042STzung-Bi Shih return _enable_chip(name, 1);
96*c7f92042STzung-Bi Shih }
97*c7f92042STzung-Bi Shih
disable_chip(const char * name)98*c7f92042STzung-Bi Shih static void disable_chip(const char *name)
99*c7f92042STzung-Bi Shih {
100*c7f92042STzung-Bi Shih _enable_chip(name, 0);
101*c7f92042STzung-Bi Shih }
102*c7f92042STzung-Bi Shih
open_chip(const char * chip_name,const char * bank_name)103*c7f92042STzung-Bi Shih static int open_chip(const char *chip_name, const char *bank_name)
104*c7f92042STzung-Bi Shih {
105*c7f92042STzung-Bi Shih char path[64], dev_name[32];
106*c7f92042STzung-Bi Shih int ret, fd;
107*c7f92042STzung-Bi Shih
108*c7f92042STzung-Bi Shih ret = create_chip(chip_name);
109*c7f92042STzung-Bi Shih if (ret) {
110*c7f92042STzung-Bi Shih fprintf(stderr, "failed to create chip\n");
111*c7f92042STzung-Bi Shih return ret;
112*c7f92042STzung-Bi Shih }
113*c7f92042STzung-Bi Shih
114*c7f92042STzung-Bi Shih ret = create_bank(chip_name, bank_name);
115*c7f92042STzung-Bi Shih if (ret) {
116*c7f92042STzung-Bi Shih fprintf(stderr, "failed to create bank\n");
117*c7f92042STzung-Bi Shih goto err_remove_chip;
118*c7f92042STzung-Bi Shih }
119*c7f92042STzung-Bi Shih
120*c7f92042STzung-Bi Shih ret = enable_chip(chip_name);
121*c7f92042STzung-Bi Shih if (ret) {
122*c7f92042STzung-Bi Shih fprintf(stderr, "failed to enable chip\n");
123*c7f92042STzung-Bi Shih goto err_remove_bank;
124*c7f92042STzung-Bi Shih }
125*c7f92042STzung-Bi Shih
126*c7f92042STzung-Bi Shih snprintf(path, sizeof(path), CONFIGFS_DIR "/%s/%s/chip_name",
127*c7f92042STzung-Bi Shih chip_name, bank_name);
128*c7f92042STzung-Bi Shih
129*c7f92042STzung-Bi Shih fd = open(path, O_RDONLY);
130*c7f92042STzung-Bi Shih if (fd == -1) {
131*c7f92042STzung-Bi Shih ret = fd;
132*c7f92042STzung-Bi Shih fprintf(stderr, "failed to open %s\n", path);
133*c7f92042STzung-Bi Shih goto err_disable_chip;
134*c7f92042STzung-Bi Shih }
135*c7f92042STzung-Bi Shih
136*c7f92042STzung-Bi Shih ret = read(fd, dev_name, sizeof(dev_name) - 1);
137*c7f92042STzung-Bi Shih close(fd);
138*c7f92042STzung-Bi Shih if (ret == -1) {
139*c7f92042STzung-Bi Shih fprintf(stderr, "failed to read %s\n", path);
140*c7f92042STzung-Bi Shih goto err_disable_chip;
141*c7f92042STzung-Bi Shih }
142*c7f92042STzung-Bi Shih dev_name[ret] = '\0';
143*c7f92042STzung-Bi Shih if (ret && dev_name[ret - 1] == '\n')
144*c7f92042STzung-Bi Shih dev_name[ret - 1] = '\0';
145*c7f92042STzung-Bi Shih
146*c7f92042STzung-Bi Shih snprintf(path, sizeof(path), "/dev/%s", dev_name);
147*c7f92042STzung-Bi Shih
148*c7f92042STzung-Bi Shih fd = open(path, O_RDWR);
149*c7f92042STzung-Bi Shih if (fd == -1) {
150*c7f92042STzung-Bi Shih ret = fd;
151*c7f92042STzung-Bi Shih fprintf(stderr, "failed to open %s\n", path);
152*c7f92042STzung-Bi Shih goto err_disable_chip;
153*c7f92042STzung-Bi Shih }
154*c7f92042STzung-Bi Shih
155*c7f92042STzung-Bi Shih return fd;
156*c7f92042STzung-Bi Shih err_disable_chip:
157*c7f92042STzung-Bi Shih disable_chip(chip_name);
158*c7f92042STzung-Bi Shih err_remove_bank:
159*c7f92042STzung-Bi Shih remove_bank(chip_name, bank_name);
160*c7f92042STzung-Bi Shih err_remove_chip:
161*c7f92042STzung-Bi Shih remove_chip(chip_name);
162*c7f92042STzung-Bi Shih return ret;
163*c7f92042STzung-Bi Shih }
164*c7f92042STzung-Bi Shih
close_chip(const char * chip_name,const char * bank_name)165*c7f92042STzung-Bi Shih static void close_chip(const char *chip_name, const char *bank_name)
166*c7f92042STzung-Bi Shih {
167*c7f92042STzung-Bi Shih disable_chip(chip_name);
168*c7f92042STzung-Bi Shih remove_bank(chip_name, bank_name);
169*c7f92042STzung-Bi Shih remove_chip(chip_name);
170*c7f92042STzung-Bi Shih }
171*c7f92042STzung-Bi Shih
test_poll(int fd)172*c7f92042STzung-Bi Shih static int test_poll(int fd)
173*c7f92042STzung-Bi Shih {
174*c7f92042STzung-Bi Shih struct pollfd pfds;
175*c7f92042STzung-Bi Shih
176*c7f92042STzung-Bi Shih pfds.fd = fd;
177*c7f92042STzung-Bi Shih pfds.events = POLLIN;
178*c7f92042STzung-Bi Shih pfds.revents = 0;
179*c7f92042STzung-Bi Shih
180*c7f92042STzung-Bi Shih if (poll(&pfds, 1, 0) == -1)
181*c7f92042STzung-Bi Shih return -1;
182*c7f92042STzung-Bi Shih
183*c7f92042STzung-Bi Shih return (pfds.revents & ~(POLLHUP | POLLERR)) ? -1 : 0;
184*c7f92042STzung-Bi Shih }
185*c7f92042STzung-Bi Shih
test_read(int fd)186*c7f92042STzung-Bi Shih static int test_read(int fd)
187*c7f92042STzung-Bi Shih {
188*c7f92042STzung-Bi Shih char data;
189*c7f92042STzung-Bi Shih
190*c7f92042STzung-Bi Shih if (read(fd, &data, 1) == -1 && errno == ENODEV)
191*c7f92042STzung-Bi Shih return 0;
192*c7f92042STzung-Bi Shih return -1;
193*c7f92042STzung-Bi Shih }
194*c7f92042STzung-Bi Shih
test_ioctl(int fd)195*c7f92042STzung-Bi Shih static int test_ioctl(int fd)
196*c7f92042STzung-Bi Shih {
197*c7f92042STzung-Bi Shih if (ioctl(fd, 0, NULL) == -1 && errno == ENODEV)
198*c7f92042STzung-Bi Shih return 0;
199*c7f92042STzung-Bi Shih return -1;
200*c7f92042STzung-Bi Shih }
201*c7f92042STzung-Bi Shih
main(int argc,char ** argv)202*c7f92042STzung-Bi Shih int main(int argc, char **argv)
203*c7f92042STzung-Bi Shih {
204*c7f92042STzung-Bi Shih int cfd, fd, ret;
205*c7f92042STzung-Bi Shih int (*test_func)(int);
206*c7f92042STzung-Bi Shih
207*c7f92042STzung-Bi Shih if (argc != 3) {
208*c7f92042STzung-Bi Shih print_usage();
209*c7f92042STzung-Bi Shih return EXIT_FAILURE;
210*c7f92042STzung-Bi Shih }
211*c7f92042STzung-Bi Shih
212*c7f92042STzung-Bi Shih if (strcmp(argv[1], "chip") == 0 ||
213*c7f92042STzung-Bi Shih strcmp(argv[1], "event") == 0 ||
214*c7f92042STzung-Bi Shih strcmp(argv[1], "req") == 0) {
215*c7f92042STzung-Bi Shih if (strcmp(argv[2], "poll") &&
216*c7f92042STzung-Bi Shih strcmp(argv[2], "read") &&
217*c7f92042STzung-Bi Shih strcmp(argv[2], "ioctl")) {
218*c7f92042STzung-Bi Shih fprintf(stderr, "unknown command: %s\n", argv[2]);
219*c7f92042STzung-Bi Shih return EXIT_FAILURE;
220*c7f92042STzung-Bi Shih }
221*c7f92042STzung-Bi Shih } else if (strcmp(argv[1], "handle") == 0) {
222*c7f92042STzung-Bi Shih if (strcmp(argv[2], "ioctl")) {
223*c7f92042STzung-Bi Shih fprintf(stderr, "unknown command: %s\n", argv[2]);
224*c7f92042STzung-Bi Shih return EXIT_FAILURE;
225*c7f92042STzung-Bi Shih }
226*c7f92042STzung-Bi Shih } else {
227*c7f92042STzung-Bi Shih fprintf(stderr, "unknown command: %s\n", argv[1]);
228*c7f92042STzung-Bi Shih return EXIT_FAILURE;
229*c7f92042STzung-Bi Shih }
230*c7f92042STzung-Bi Shih
231*c7f92042STzung-Bi Shih if (strcmp(argv[2], "poll") == 0)
232*c7f92042STzung-Bi Shih test_func = test_poll;
233*c7f92042STzung-Bi Shih else if (strcmp(argv[2], "read") == 0)
234*c7f92042STzung-Bi Shih test_func = test_read;
235*c7f92042STzung-Bi Shih else /* strcmp(argv[2], "ioctl") == 0 */
236*c7f92042STzung-Bi Shih test_func = test_ioctl;
237*c7f92042STzung-Bi Shih
238*c7f92042STzung-Bi Shih cfd = open_chip("chip", "bank");
239*c7f92042STzung-Bi Shih if (cfd == -1) {
240*c7f92042STzung-Bi Shih fprintf(stderr, "failed to open chip\n");
241*c7f92042STzung-Bi Shih return EXIT_FAILURE;
242*c7f92042STzung-Bi Shih }
243*c7f92042STzung-Bi Shih
244*c7f92042STzung-Bi Shih /* Step 1: Hold a FD to the test target. */
245*c7f92042STzung-Bi Shih if (strcmp(argv[1], "chip") == 0) {
246*c7f92042STzung-Bi Shih fd = cfd;
247*c7f92042STzung-Bi Shih } else if (strcmp(argv[1], "handle") == 0) {
248*c7f92042STzung-Bi Shih struct gpiohandle_request req = {0};
249*c7f92042STzung-Bi Shih
250*c7f92042STzung-Bi Shih req.lines = 1;
251*c7f92042STzung-Bi Shih if (ioctl(cfd, GPIO_GET_LINEHANDLE_IOCTL, &req) == -1) {
252*c7f92042STzung-Bi Shih fprintf(stderr, "failed to get handle FD\n");
253*c7f92042STzung-Bi Shih goto err_close_chip;
254*c7f92042STzung-Bi Shih }
255*c7f92042STzung-Bi Shih
256*c7f92042STzung-Bi Shih close(cfd);
257*c7f92042STzung-Bi Shih fd = req.fd;
258*c7f92042STzung-Bi Shih } else if (strcmp(argv[1], "event") == 0) {
259*c7f92042STzung-Bi Shih struct gpioevent_request req = {0};
260*c7f92042STzung-Bi Shih
261*c7f92042STzung-Bi Shih if (ioctl(cfd, GPIO_GET_LINEEVENT_IOCTL, &req) == -1) {
262*c7f92042STzung-Bi Shih fprintf(stderr, "failed to get event FD\n");
263*c7f92042STzung-Bi Shih goto err_close_chip;
264*c7f92042STzung-Bi Shih }
265*c7f92042STzung-Bi Shih
266*c7f92042STzung-Bi Shih close(cfd);
267*c7f92042STzung-Bi Shih fd = req.fd;
268*c7f92042STzung-Bi Shih } else { /* strcmp(argv[1], "req") == 0 */
269*c7f92042STzung-Bi Shih struct gpio_v2_line_request req = {0};
270*c7f92042STzung-Bi Shih
271*c7f92042STzung-Bi Shih req.num_lines = 1;
272*c7f92042STzung-Bi Shih if (ioctl(cfd, GPIO_V2_GET_LINE_IOCTL, &req) == -1) {
273*c7f92042STzung-Bi Shih fprintf(stderr, "failed to get req FD\n");
274*c7f92042STzung-Bi Shih goto err_close_chip;
275*c7f92042STzung-Bi Shih }
276*c7f92042STzung-Bi Shih
277*c7f92042STzung-Bi Shih close(cfd);
278*c7f92042STzung-Bi Shih fd = req.fd;
279*c7f92042STzung-Bi Shih }
280*c7f92042STzung-Bi Shih
281*c7f92042STzung-Bi Shih /* Step 2: Free the chip. */
282*c7f92042STzung-Bi Shih close_chip("chip", "bank");
283*c7f92042STzung-Bi Shih
284*c7f92042STzung-Bi Shih /* Step 3: Access the dangling FD to trigger UAF. */
285*c7f92042STzung-Bi Shih ret = test_func(fd);
286*c7f92042STzung-Bi Shih close(fd);
287*c7f92042STzung-Bi Shih return ret ? EXIT_FAILURE : EXIT_SUCCESS;
288*c7f92042STzung-Bi Shih err_close_chip:
289*c7f92042STzung-Bi Shih close(cfd);
290*c7f92042STzung-Bi Shih close_chip("chip", "bank");
291*c7f92042STzung-Bi Shih return EXIT_FAILURE;
292*c7f92042STzung-Bi Shih }
293