Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

selftests: gpio: Add gpio-cdev-uaf tests

Add tests for gpiolib-cdev to make sure accessing to dangling resources
via the opening file descriptor won't crash the system after the
underlying resource providers have gone.

Reviewed-by: Linus Walleij <linusw@kernel.org>
Signed-off-by: Tzung-Bi Shih <tzungbi@kernel.org>
Link: https://patch.msgid.link/20260223061726.82161-7-tzungbi@kernel.org
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>

authored by

Tzung-Bi Shih and committed by
Bartosz Golaszewski
c7f92042 ee68f18d

+358 -2
+3 -2
tools/testing/selftests/gpio/Makefile
··· 1 1 # SPDX-License-Identifier: GPL-2.0 2 2 3 - TEST_PROGS := gpio-mockup.sh gpio-sim.sh gpio-aggregator.sh 3 + TEST_PROGS := gpio-mockup.sh gpio-sim.sh gpio-aggregator.sh gpio-cdev-uaf.sh 4 4 TEST_FILES := gpio-mockup-sysfs.sh 5 - TEST_GEN_PROGS_EXTENDED := gpio-mockup-cdev gpio-chip-info gpio-line-name 5 + TEST_GEN_PROGS_EXTENDED := gpio-mockup-cdev gpio-chip-info gpio-line-name \ 6 + gpio-cdev-uaf 6 7 CFLAGS += -O2 -g -Wall $(KHDR_INCLUDES) 7 8 8 9 include ../lib.mk
+292
tools/testing/selftests/gpio/gpio-cdev-uaf.c
··· 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 + }
+63
tools/testing/selftests/gpio/gpio-cdev-uaf.sh
··· 1 + #!/bin/sh 2 + # SPDX-License-Identifier: GPL-2.0 3 + # Copyright 2026 Google LLC 4 + 5 + BASE_DIR=`dirname $0` 6 + MODULE="gpio-cdev-uaf" 7 + 8 + fail() { 9 + echo "$*" >&2 10 + echo "GPIO $MODULE test FAIL" 11 + exit 1 12 + } 13 + 14 + skip() { 15 + echo "$*" >&2 16 + echo "GPIO $MODULE test SKIP" 17 + exit 4 18 + } 19 + 20 + # Load the gpio-sim module. This will pull in configfs if needed too. 21 + modprobe gpio-sim || skip "unable to load the gpio-sim module" 22 + # Make sure configfs is mounted at /sys/kernel/config. Wait a bit if needed. 23 + for _ in `seq 5`; do 24 + mountpoint -q /sys/kernel/config && break 25 + mount -t configfs none /sys/kernel/config 26 + sleep 0.1 27 + done 28 + mountpoint -q /sys/kernel/config || \ 29 + skip "configfs not mounted at /sys/kernel/config" 30 + 31 + echo "1. GPIO" 32 + 33 + echo "1.1. poll" 34 + $BASE_DIR/gpio-cdev-uaf chip poll || fail "failed to test chip poll" 35 + echo "1.2. read" 36 + $BASE_DIR/gpio-cdev-uaf chip read || fail "failed to test chip read" 37 + echo "1.3. ioctl" 38 + $BASE_DIR/gpio-cdev-uaf chip ioctl || fail "failed to test chip ioctl" 39 + 40 + echo "2. linehandle" 41 + 42 + echo "2.1. ioctl" 43 + $BASE_DIR/gpio-cdev-uaf handle ioctl || fail "failed to test handle ioctl" 44 + 45 + echo "3. lineevent" 46 + 47 + echo "3.1. read" 48 + $BASE_DIR/gpio-cdev-uaf event read || fail "failed to test event read" 49 + echo "3.2. poll" 50 + $BASE_DIR/gpio-cdev-uaf event poll || fail "failed to test event poll" 51 + echo "3.3. ioctl" 52 + $BASE_DIR/gpio-cdev-uaf event ioctl || fail "failed to test event ioctl" 53 + 54 + echo "4. linereq" 55 + 56 + echo "4.1. read" 57 + $BASE_DIR/gpio-cdev-uaf req read || fail "failed to test req read" 58 + echo "4.2. poll" 59 + $BASE_DIR/gpio-cdev-uaf req poll || fail "failed to test req poll" 60 + echo "4.3. ioctl" 61 + $BASE_DIR/gpio-cdev-uaf req ioctl || fail "failed to test req ioctl" 62 + 63 + echo "GPIO $MODULE test PASS"