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 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 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 41 static int create_chip(const char *name) 42 { 43 return _create_chip(name, 1); 44 } 45 46 static void remove_chip(const char *name) 47 { 48 _create_chip(name, 0); 49 } 50 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 63 static int create_bank(const char *chip_name, const char *name) 64 { 65 return _create_bank(chip_name, name, 1); 66 } 67 68 static void remove_bank(const char *chip_name, const char *name) 69 { 70 _create_bank(chip_name, name, 0); 71 } 72 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 93 static int enable_chip(const char *name) 94 { 95 return _enable_chip(name, 1); 96 } 97 98 static void disable_chip(const char *name) 99 { 100 _enable_chip(name, 0); 101 } 102 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 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 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 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 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 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