| // SPDX-License-Identifier: GPL-2.0-or-later | |
| /* | |
| * GPIO character device helper for UAF tests. | |
| * | |
| * Copyright 2026 Google LLC | |
| */ | |
| #include <errno.h> | |
| #include <fcntl.h> | |
| #include <linux/gpio.h> | |
| #include <poll.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <sys/ioctl.h> | |
| #include <sys/stat.h> | |
| #include <sys/types.h> | |
| #include <unistd.h> | |
| #define CONFIGFS_DIR "/sys/kernel/config/gpio-sim" | |
| #define PROCFS_DIR "/proc" | |
| static void print_usage(void) | |
| { | |
| printf("usage:\n"); | |
| printf(" gpio-cdev-uaf [chip|handle|event|req] [poll|read|ioctl]\n"); | |
| } | |
| static int _create_chip(const char *name, int create) | |
| { | |
| char path[64]; | |
| snprintf(path, sizeof(path), CONFIGFS_DIR "/%s", name); | |
| if (create) | |
| return mkdir(path, 0755); | |
| else | |
| return rmdir(path); | |
| } | |
| static int create_chip(const char *name) | |
| { | |
| return _create_chip(name, 1); | |
| } | |
| static void remove_chip(const char *name) | |
| { | |
| _create_chip(name, 0); | |
| } | |
| static int _create_bank(const char *chip_name, const char *name, int create) | |
| { | |
| char path[64]; | |
| snprintf(path, sizeof(path), CONFIGFS_DIR "/%s/%s", chip_name, name); | |
| if (create) | |
| return mkdir(path, 0755); | |
| else | |
| return rmdir(path); | |
| } | |
| static int create_bank(const char *chip_name, const char *name) | |
| { | |
| return _create_bank(chip_name, name, 1); | |
| } | |
| static void remove_bank(const char *chip_name, const char *name) | |
| { | |
| _create_bank(chip_name, name, 0); | |
| } | |
| static int _enable_chip(const char *name, int enable) | |
| { | |
| char path[64]; | |
| int fd, ret; | |
| snprintf(path, sizeof(path), CONFIGFS_DIR "/%s/live", name); | |
| fd = open(path, O_WRONLY); | |
| if (fd == -1) | |
| return fd; | |
| if (enable) | |
| ret = write(fd, "1", 1); | |
| else | |
| ret = write(fd, "0", 1); | |
| close(fd); | |
| return ret == 1 ? 0 : -1; | |
| } | |
| static int enable_chip(const char *name) | |
| { | |
| return _enable_chip(name, 1); | |
| } | |
| static void disable_chip(const char *name) | |
| { | |
| _enable_chip(name, 0); | |
| } | |
| static int open_chip(const char *chip_name, const char *bank_name) | |
| { | |
| char path[64], dev_name[32]; | |
| int ret, fd; | |
| ret = create_chip(chip_name); | |
| if (ret) { | |
| fprintf(stderr, "failed to create chip\n"); | |
| return ret; | |
| } | |
| ret = create_bank(chip_name, bank_name); | |
| if (ret) { | |
| fprintf(stderr, "failed to create bank\n"); | |
| goto err_remove_chip; | |
| } | |
| ret = enable_chip(chip_name); | |
| if (ret) { | |
| fprintf(stderr, "failed to enable chip\n"); | |
| goto err_remove_bank; | |
| } | |
| snprintf(path, sizeof(path), CONFIGFS_DIR "/%s/%s/chip_name", | |
| chip_name, bank_name); | |
| fd = open(path, O_RDONLY); | |
| if (fd == -1) { | |
| ret = fd; | |
| fprintf(stderr, "failed to open %s\n", path); | |
| goto err_disable_chip; | |
| } | |
| ret = read(fd, dev_name, sizeof(dev_name) - 1); | |
| close(fd); | |
| if (ret == -1) { | |
| fprintf(stderr, "failed to read %s\n", path); | |
| goto err_disable_chip; | |
| } | |
| dev_name[ret] = '\0'; | |
| if (ret && dev_name[ret - 1] == '\n') | |
| dev_name[ret - 1] = '\0'; | |
| snprintf(path, sizeof(path), "/dev/%s", dev_name); | |
| fd = open(path, O_RDWR); | |
| if (fd == -1) { | |
| ret = fd; | |
| fprintf(stderr, "failed to open %s\n", path); | |
| goto err_disable_chip; | |
| } | |
| return fd; | |
| err_disable_chip: | |
| disable_chip(chip_name); | |
| err_remove_bank: | |
| remove_bank(chip_name, bank_name); | |
| err_remove_chip: | |
| remove_chip(chip_name); | |
| return ret; | |
| } | |
| static void close_chip(const char *chip_name, const char *bank_name) | |
| { | |
| disable_chip(chip_name); | |
| remove_bank(chip_name, bank_name); | |
| remove_chip(chip_name); | |
| } | |
| static int test_poll(int fd) | |
| { | |
| struct pollfd pfds; | |
| pfds.fd = fd; | |
| pfds.events = POLLIN; | |
| pfds.revents = 0; | |
| if (poll(&pfds, 1, 0) == -1) | |
| return -1; | |
| return (pfds.revents & ~(POLLHUP | POLLERR)) ? -1 : 0; | |
| } | |
| static int test_read(int fd) | |
| { | |
| char data; | |
| if (read(fd, &data, 1) == -1 && errno == ENODEV) | |
| return 0; | |
| return -1; | |
| } | |
| static int test_ioctl(int fd) | |
| { | |
| if (ioctl(fd, 0, NULL) == -1 && errno == ENODEV) | |
| return 0; | |
| return -1; | |
| } | |
| int main(int argc, char **argv) | |
| { | |
| int cfd, fd, ret; | |
| int (*test_func)(int); | |
| if (argc != 3) { | |
| print_usage(); | |
| return EXIT_FAILURE; | |
| } | |
| if (strcmp(argv[1], "chip") == 0 || | |
| strcmp(argv[1], "event") == 0 || | |
| strcmp(argv[1], "req") == 0) { | |
| if (strcmp(argv[2], "poll") && | |
| strcmp(argv[2], "read") && | |
| strcmp(argv[2], "ioctl")) { | |
| fprintf(stderr, "unknown command: %s\n", argv[2]); | |
| return EXIT_FAILURE; | |
| } | |
| } else if (strcmp(argv[1], "handle") == 0) { | |
| if (strcmp(argv[2], "ioctl")) { | |
| fprintf(stderr, "unknown command: %s\n", argv[2]); | |
| return EXIT_FAILURE; | |
| } | |
| } else { | |
| fprintf(stderr, "unknown command: %s\n", argv[1]); | |
| return EXIT_FAILURE; | |
| } | |
| if (strcmp(argv[2], "poll") == 0) | |
| test_func = test_poll; | |
| else if (strcmp(argv[2], "read") == 0) | |
| test_func = test_read; | |
| else /* strcmp(argv[2], "ioctl") == 0 */ | |
| test_func = test_ioctl; | |
| cfd = open_chip("chip", "bank"); | |
| if (cfd == -1) { | |
| fprintf(stderr, "failed to open chip\n"); | |
| return EXIT_FAILURE; | |
| } | |
| /* Step 1: Hold a FD to the test target. */ | |
| if (strcmp(argv[1], "chip") == 0) { | |
| fd = cfd; | |
| } else if (strcmp(argv[1], "handle") == 0) { | |
| struct gpiohandle_request req = {0}; | |
| req.lines = 1; | |
| if (ioctl(cfd, GPIO_GET_LINEHANDLE_IOCTL, &req) == -1) { | |
| fprintf(stderr, "failed to get handle FD\n"); | |
| goto err_close_chip; | |
| } | |
| close(cfd); | |
| fd = req.fd; | |
| } else if (strcmp(argv[1], "event") == 0) { | |
| struct gpioevent_request req = {0}; | |
| if (ioctl(cfd, GPIO_GET_LINEEVENT_IOCTL, &req) == -1) { | |
| fprintf(stderr, "failed to get event FD\n"); | |
| goto err_close_chip; | |
| } | |
| close(cfd); | |
| fd = req.fd; | |
| } else { /* strcmp(argv[1], "req") == 0 */ | |
| struct gpio_v2_line_request req = {0}; | |
| req.num_lines = 1; | |
| if (ioctl(cfd, GPIO_V2_GET_LINE_IOCTL, &req) == -1) { | |
| fprintf(stderr, "failed to get req FD\n"); | |
| goto err_close_chip; | |
| } | |
| close(cfd); | |
| fd = req.fd; | |
| } | |
| /* Step 2: Free the chip. */ | |
| close_chip("chip", "bank"); | |
| /* Step 3: Access the dangling FD to trigger UAF. */ | |
| ret = test_func(fd); | |
| close(fd); | |
| return ret ? EXIT_FAILURE : EXIT_SUCCESS; | |
| err_close_chip: | |
| close(cfd); | |
| close_chip("chip", "bank"); | |
| return EXIT_FAILURE; | |
| } |