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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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