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