xref: /linux/tools/testing/selftests/gpio/gpio-cdev-uaf.c (revision 1334d2a3b3235d062e5e1f51aebe7a64ed57cf72)
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