1 // SPDX-License-Identifier: GPL-2.0-or-later 2 /* 3 * Author: Aleksa Sarai <cyphar@cyphar.com> 4 * Copyright (C) 2018-2019 SUSE LLC. 5 */ 6 7 #define _GNU_SOURCE 8 #include <errno.h> 9 #include <fcntl.h> 10 #include <sched.h> 11 #include <sys/stat.h> 12 #include <sys/types.h> 13 #include <sys/mount.h> 14 #include <sys/mman.h> 15 #include <sys/prctl.h> 16 #include <signal.h> 17 #include <stdio.h> 18 #include <stdlib.h> 19 #include <stdbool.h> 20 #include <string.h> 21 #include <syscall.h> 22 #include <limits.h> 23 #include <unistd.h> 24 25 #include "helpers.h" 26 #include "kselftest_harness.h" 27 28 #define ROUNDS 400000 29 30 /* Swap @dirfd/@a and @dirfd/@b constantly. Parent must kill this process. */ 31 pid_t spawn_attack(struct __test_metadata *_metadata, 32 int dirfd, char *a, char *b) 33 { 34 pid_t child = fork(); 35 if (child != 0) 36 return child; 37 38 /* If the parent (the test process) dies, kill ourselves too. */ 39 ASSERT_EQ(prctl(PR_SET_PDEATHSIG, SIGKILL), 0); 40 41 /* Swap @a and @b. */ 42 for (;;) 43 renameat2(dirfd, a, dirfd, b, RENAME_EXCHANGE); 44 exit(1); 45 } 46 47 /* 48 * Construct a test directory with the following structure: 49 * 50 * root/ 51 * |-- a/ 52 * | `-- c/ 53 * `-- b/ 54 */ 55 FIXTURE(rename_attack) { 56 int dfd; 57 int afd; 58 pid_t child; 59 }; 60 61 FIXTURE_SETUP(rename_attack) 62 { 63 char dirname[] = "/tmp/ksft-openat2-rename-attack.XXXXXX"; 64 65 self->dfd = -1; 66 self->afd = -1; 67 self->child = 0; 68 69 /* Make the top-level directory. */ 70 ASSERT_NE(mkdtemp(dirname), NULL); 71 self->dfd = open(dirname, O_PATH | O_DIRECTORY); 72 ASSERT_GE(self->dfd, 0); 73 74 ASSERT_EQ(mkdirat(self->dfd, "a", 0755), 0); 75 ASSERT_EQ(mkdirat(self->dfd, "b", 0755), 0); 76 ASSERT_EQ(mkdirat(self->dfd, "a/c", 0755), 0); 77 78 self->afd = openat(self->dfd, "a", O_PATH); 79 ASSERT_GE(self->afd, 0); 80 81 self->child = spawn_attack(_metadata, self->dfd, "a/c", "b"); 82 ASSERT_GT(self->child, 0); 83 } 84 85 FIXTURE_TEARDOWN(rename_attack) 86 { 87 if (self->child > 0) 88 kill(self->child, SIGKILL); 89 if (self->afd >= 0) 90 close(self->afd); 91 if (self->dfd >= 0) 92 close(self->dfd); 93 } 94 95 FIXTURE_VARIANT(rename_attack) { 96 int resolve; 97 const char *name; 98 }; 99 100 FIXTURE_VARIANT_ADD(rename_attack, resolve_beneath) { 101 .resolve = RESOLVE_BENEATH, 102 .name = "RESOLVE_BENEATH", 103 }; 104 105 FIXTURE_VARIANT_ADD(rename_attack, resolve_in_root) { 106 .resolve = RESOLVE_IN_ROOT, 107 .name = "RESOLVE_IN_ROOT", 108 }; 109 110 TEST_F_TIMEOUT(rename_attack, test, 120) 111 { 112 int escapes = 0, successes = 0, other_errs = 0, exdevs = 0, eagains = 0; 113 char *victim_path = "c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../.."; 114 struct open_how how = { 115 .flags = O_PATH, 116 .resolve = variant->resolve, 117 }; 118 119 if (!openat2_supported) { 120 how.resolve = 0; 121 TH_LOG("openat2(2) unsupported -- using openat(2) instead"); 122 } 123 124 for (int i = 0; i < ROUNDS; i++) { 125 int fd; 126 127 if (openat2_supported) 128 fd = sys_openat2(self->afd, victim_path, &how); 129 else 130 fd = sys_openat(self->afd, victim_path, &how); 131 132 if (fd < 0) { 133 if (fd == -EAGAIN) 134 eagains++; 135 else if (fd == -EXDEV) 136 exdevs++; 137 else if (fd == -ENOENT) 138 escapes++; /* escaped outside and got ENOENT... */ 139 else 140 other_errs++; /* unexpected error */ 141 } else { 142 if (fdequal(_metadata, fd, self->afd, NULL)) 143 successes++; 144 else 145 escapes++; /* we got an unexpected fd */ 146 } 147 if (fd >= 0) 148 close(fd); 149 } 150 151 TH_LOG("non-escapes: EAGAIN=%d EXDEV=%d E<other>=%d success=%d", 152 eagains, exdevs, other_errs, successes); 153 ASSERT_EQ(escapes, 0) { 154 TH_LOG("rename attack with %s (%d runs, got %d escapes)", 155 variant->name, ROUNDS, escapes); 156 } 157 } 158 159 TEST_HARNESS_MAIN 160