Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-or-later
0002 /*
0003  * Author: Aleksa Sarai <cyphar@cyphar.com>
0004  * Copyright (C) 2018-2019 SUSE LLC.
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 /* Construct a test directory with the following structure:
0029  *
0030  * root/
0031  * |-- a/
0032  * |   `-- c/
0033  * `-- b/
0034  */
0035 int setup_testdir(void)
0036 {
0037     int dfd;
0038     char dirname[] = "/tmp/ksft-openat2-rename-attack.XXXXXX";
0039 
0040     /* Make the top-level directory. */
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 /* Swap @dirfd/@a and @dirfd/@b constantly. Parent must kill this process. */
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     /* If the parent (the test process) dies, kill ourselves too. */
0062     E_prctl(PR_SET_PDEATHSIG, SIGKILL);
0063 
0064     /* Swap @a and @b. */
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++; /* escaped outside and got ENOENT... */
0124             else
0125                 other_errs++; /* unexpected error */
0126         } else {
0127             if (fdequal(fd, afd, NULL))
0128                 successes++;
0129             else
0130                 escapes++; /* we got an unexpected fd */
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     /* Should be killed anyway, but might as well make sure. */
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 }