0001
0002
0003
0004
0005
0006
0007 #define _GNU_SOURCE
0008 #include <errno.h>
0009 #include <fcntl.h>
0010 #include <sched.h>
0011 #include <sys/stat.h>
0012 #include <sys/types.h>
0013 #include <sys/mount.h>
0014 #include <sys/mman.h>
0015 #include <sys/prctl.h>
0016 #include <signal.h>
0017 #include <stdio.h>
0018 #include <stdlib.h>
0019 #include <stdbool.h>
0020 #include <string.h>
0021 #include <syscall.h>
0022 #include <limits.h>
0023 #include <unistd.h>
0024
0025 #include "../kselftest.h"
0026 #include "helpers.h"
0027
0028
0029
0030
0031
0032
0033
0034
0035 int setup_testdir(void)
0036 {
0037 int dfd;
0038 char dirname[] = "/tmp/ksft-openat2-rename-attack.XXXXXX";
0039
0040
0041 if (!mkdtemp(dirname))
0042 ksft_exit_fail_msg("setup_testdir: failed to create tmpdir\n");
0043 dfd = open(dirname, O_PATH | O_DIRECTORY);
0044 if (dfd < 0)
0045 ksft_exit_fail_msg("setup_testdir: failed to open tmpdir\n");
0046
0047 E_mkdirat(dfd, "a", 0755);
0048 E_mkdirat(dfd, "b", 0755);
0049 E_mkdirat(dfd, "a/c", 0755);
0050
0051 return dfd;
0052 }
0053
0054
0055 pid_t spawn_attack(int dirfd, char *a, char *b)
0056 {
0057 pid_t child = fork();
0058 if (child != 0)
0059 return child;
0060
0061
0062 E_prctl(PR_SET_PDEATHSIG, SIGKILL);
0063
0064
0065 for (;;)
0066 renameat2(dirfd, a, dirfd, b, RENAME_EXCHANGE);
0067 exit(1);
0068 }
0069
0070 #define NUM_RENAME_TESTS 2
0071 #define ROUNDS 400000
0072
0073 const char *flagname(int resolve)
0074 {
0075 switch (resolve) {
0076 case RESOLVE_IN_ROOT:
0077 return "RESOLVE_IN_ROOT";
0078 case RESOLVE_BENEATH:
0079 return "RESOLVE_BENEATH";
0080 }
0081 return "(unknown)";
0082 }
0083
0084 void test_rename_attack(int resolve)
0085 {
0086 int dfd, afd;
0087 pid_t child;
0088 void (*resultfn)(const char *msg, ...) = ksft_test_result_pass;
0089 int escapes = 0, other_errs = 0, exdevs = 0, eagains = 0, successes = 0;
0090
0091 struct open_how how = {
0092 .flags = O_PATH,
0093 .resolve = resolve,
0094 };
0095
0096 if (!openat2_supported) {
0097 how.resolve = 0;
0098 ksft_print_msg("openat2(2) unsupported -- using openat(2) instead\n");
0099 }
0100
0101 dfd = setup_testdir();
0102 afd = openat(dfd, "a", O_PATH);
0103 if (afd < 0)
0104 ksft_exit_fail_msg("test_rename_attack: failed to open 'a'\n");
0105
0106 child = spawn_attack(dfd, "a/c", "b");
0107
0108 for (int i = 0; i < ROUNDS; i++) {
0109 int fd;
0110 char *victim_path = "c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../..";
0111
0112 if (openat2_supported)
0113 fd = sys_openat2(afd, victim_path, &how);
0114 else
0115 fd = sys_openat(afd, victim_path, &how);
0116
0117 if (fd < 0) {
0118 if (fd == -EAGAIN)
0119 eagains++;
0120 else if (fd == -EXDEV)
0121 exdevs++;
0122 else if (fd == -ENOENT)
0123 escapes++;
0124 else
0125 other_errs++;
0126 } else {
0127 if (fdequal(fd, afd, NULL))
0128 successes++;
0129 else
0130 escapes++;
0131 }
0132 close(fd);
0133 }
0134
0135 if (escapes > 0)
0136 resultfn = ksft_test_result_fail;
0137 ksft_print_msg("non-escapes: EAGAIN=%d EXDEV=%d E<other>=%d success=%d\n",
0138 eagains, exdevs, other_errs, successes);
0139 resultfn("rename attack with %s (%d runs, got %d escapes)\n",
0140 flagname(resolve), ROUNDS, escapes);
0141
0142
0143 E_kill(child, SIGKILL);
0144 }
0145
0146 #define NUM_TESTS NUM_RENAME_TESTS
0147
0148 int main(int argc, char **argv)
0149 {
0150 ksft_print_header();
0151 ksft_set_plan(NUM_TESTS);
0152
0153 test_rename_attack(RESOLVE_BENEATH);
0154 test_rename_attack(RESOLVE_IN_ROOT);
0155
0156 if (ksft_get_fail_cnt() + ksft_get_error_cnt() > 0)
0157 ksft_exit_fail();
0158 else
0159 ksft_exit_pass();
0160 }