162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Author: Aleksa Sarai <cyphar@cyphar.com> 462306a36Sopenharmony_ci * Copyright (C) 2018-2019 SUSE LLC. 562306a36Sopenharmony_ci */ 662306a36Sopenharmony_ci 762306a36Sopenharmony_ci#define _GNU_SOURCE 862306a36Sopenharmony_ci#include <errno.h> 962306a36Sopenharmony_ci#include <fcntl.h> 1062306a36Sopenharmony_ci#include <sched.h> 1162306a36Sopenharmony_ci#include <sys/stat.h> 1262306a36Sopenharmony_ci#include <sys/types.h> 1362306a36Sopenharmony_ci#include <sys/mount.h> 1462306a36Sopenharmony_ci#include <sys/mman.h> 1562306a36Sopenharmony_ci#include <sys/prctl.h> 1662306a36Sopenharmony_ci#include <signal.h> 1762306a36Sopenharmony_ci#include <stdio.h> 1862306a36Sopenharmony_ci#include <stdlib.h> 1962306a36Sopenharmony_ci#include <stdbool.h> 2062306a36Sopenharmony_ci#include <string.h> 2162306a36Sopenharmony_ci#include <syscall.h> 2262306a36Sopenharmony_ci#include <limits.h> 2362306a36Sopenharmony_ci#include <unistd.h> 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_ci#include "../kselftest.h" 2662306a36Sopenharmony_ci#include "helpers.h" 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_ci/* Construct a test directory with the following structure: 2962306a36Sopenharmony_ci * 3062306a36Sopenharmony_ci * root/ 3162306a36Sopenharmony_ci * |-- a/ 3262306a36Sopenharmony_ci * | `-- c/ 3362306a36Sopenharmony_ci * `-- b/ 3462306a36Sopenharmony_ci */ 3562306a36Sopenharmony_ciint setup_testdir(void) 3662306a36Sopenharmony_ci{ 3762306a36Sopenharmony_ci int dfd; 3862306a36Sopenharmony_ci char dirname[] = "/tmp/ksft-openat2-rename-attack.XXXXXX"; 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_ci /* Make the top-level directory. */ 4162306a36Sopenharmony_ci if (!mkdtemp(dirname)) 4262306a36Sopenharmony_ci ksft_exit_fail_msg("setup_testdir: failed to create tmpdir\n"); 4362306a36Sopenharmony_ci dfd = open(dirname, O_PATH | O_DIRECTORY); 4462306a36Sopenharmony_ci if (dfd < 0) 4562306a36Sopenharmony_ci ksft_exit_fail_msg("setup_testdir: failed to open tmpdir\n"); 4662306a36Sopenharmony_ci 4762306a36Sopenharmony_ci E_mkdirat(dfd, "a", 0755); 4862306a36Sopenharmony_ci E_mkdirat(dfd, "b", 0755); 4962306a36Sopenharmony_ci E_mkdirat(dfd, "a/c", 0755); 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_ci return dfd; 5262306a36Sopenharmony_ci} 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci/* Swap @dirfd/@a and @dirfd/@b constantly. Parent must kill this process. */ 5562306a36Sopenharmony_cipid_t spawn_attack(int dirfd, char *a, char *b) 5662306a36Sopenharmony_ci{ 5762306a36Sopenharmony_ci pid_t child = fork(); 5862306a36Sopenharmony_ci if (child != 0) 5962306a36Sopenharmony_ci return child; 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ci /* If the parent (the test process) dies, kill ourselves too. */ 6262306a36Sopenharmony_ci E_prctl(PR_SET_PDEATHSIG, SIGKILL); 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci /* Swap @a and @b. */ 6562306a36Sopenharmony_ci for (;;) 6662306a36Sopenharmony_ci renameat2(dirfd, a, dirfd, b, RENAME_EXCHANGE); 6762306a36Sopenharmony_ci exit(1); 6862306a36Sopenharmony_ci} 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_ci#define NUM_RENAME_TESTS 2 7162306a36Sopenharmony_ci#define ROUNDS 400000 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_ciconst char *flagname(int resolve) 7462306a36Sopenharmony_ci{ 7562306a36Sopenharmony_ci switch (resolve) { 7662306a36Sopenharmony_ci case RESOLVE_IN_ROOT: 7762306a36Sopenharmony_ci return "RESOLVE_IN_ROOT"; 7862306a36Sopenharmony_ci case RESOLVE_BENEATH: 7962306a36Sopenharmony_ci return "RESOLVE_BENEATH"; 8062306a36Sopenharmony_ci } 8162306a36Sopenharmony_ci return "(unknown)"; 8262306a36Sopenharmony_ci} 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_civoid test_rename_attack(int resolve) 8562306a36Sopenharmony_ci{ 8662306a36Sopenharmony_ci int dfd, afd; 8762306a36Sopenharmony_ci pid_t child; 8862306a36Sopenharmony_ci void (*resultfn)(const char *msg, ...) = ksft_test_result_pass; 8962306a36Sopenharmony_ci int escapes = 0, other_errs = 0, exdevs = 0, eagains = 0, successes = 0; 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_ci struct open_how how = { 9262306a36Sopenharmony_ci .flags = O_PATH, 9362306a36Sopenharmony_ci .resolve = resolve, 9462306a36Sopenharmony_ci }; 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci if (!openat2_supported) { 9762306a36Sopenharmony_ci how.resolve = 0; 9862306a36Sopenharmony_ci ksft_print_msg("openat2(2) unsupported -- using openat(2) instead\n"); 9962306a36Sopenharmony_ci } 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci dfd = setup_testdir(); 10262306a36Sopenharmony_ci afd = openat(dfd, "a", O_PATH); 10362306a36Sopenharmony_ci if (afd < 0) 10462306a36Sopenharmony_ci ksft_exit_fail_msg("test_rename_attack: failed to open 'a'\n"); 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci child = spawn_attack(dfd, "a/c", "b"); 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci for (int i = 0; i < ROUNDS; i++) { 10962306a36Sopenharmony_ci int fd; 11062306a36Sopenharmony_ci char *victim_path = "c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../.."; 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_ci if (openat2_supported) 11362306a36Sopenharmony_ci fd = sys_openat2(afd, victim_path, &how); 11462306a36Sopenharmony_ci else 11562306a36Sopenharmony_ci fd = sys_openat(afd, victim_path, &how); 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_ci if (fd < 0) { 11862306a36Sopenharmony_ci if (fd == -EAGAIN) 11962306a36Sopenharmony_ci eagains++; 12062306a36Sopenharmony_ci else if (fd == -EXDEV) 12162306a36Sopenharmony_ci exdevs++; 12262306a36Sopenharmony_ci else if (fd == -ENOENT) 12362306a36Sopenharmony_ci escapes++; /* escaped outside and got ENOENT... */ 12462306a36Sopenharmony_ci else 12562306a36Sopenharmony_ci other_errs++; /* unexpected error */ 12662306a36Sopenharmony_ci } else { 12762306a36Sopenharmony_ci if (fdequal(fd, afd, NULL)) 12862306a36Sopenharmony_ci successes++; 12962306a36Sopenharmony_ci else 13062306a36Sopenharmony_ci escapes++; /* we got an unexpected fd */ 13162306a36Sopenharmony_ci } 13262306a36Sopenharmony_ci close(fd); 13362306a36Sopenharmony_ci } 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_ci if (escapes > 0) 13662306a36Sopenharmony_ci resultfn = ksft_test_result_fail; 13762306a36Sopenharmony_ci ksft_print_msg("non-escapes: EAGAIN=%d EXDEV=%d E<other>=%d success=%d\n", 13862306a36Sopenharmony_ci eagains, exdevs, other_errs, successes); 13962306a36Sopenharmony_ci resultfn("rename attack with %s (%d runs, got %d escapes)\n", 14062306a36Sopenharmony_ci flagname(resolve), ROUNDS, escapes); 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_ci /* Should be killed anyway, but might as well make sure. */ 14362306a36Sopenharmony_ci E_kill(child, SIGKILL); 14462306a36Sopenharmony_ci} 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_ci#define NUM_TESTS NUM_RENAME_TESTS 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_ciint main(int argc, char **argv) 14962306a36Sopenharmony_ci{ 15062306a36Sopenharmony_ci ksft_print_header(); 15162306a36Sopenharmony_ci ksft_set_plan(NUM_TESTS); 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci test_rename_attack(RESOLVE_BENEATH); 15462306a36Sopenharmony_ci test_rename_attack(RESOLVE_IN_ROOT); 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_ci if (ksft_get_fail_cnt() + ksft_get_error_cnt() > 0) 15762306a36Sopenharmony_ci ksft_exit_fail(); 15862306a36Sopenharmony_ci else 15962306a36Sopenharmony_ci ksft_exit_pass(); 16062306a36Sopenharmony_ci} 161