// SPDX-License-Identifier: GPL-2.0+ /* * Author: Aleksa Sarai * Copyright (C) 2018 SUSE LLC. */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include /* These come from */ #ifndef O_BENEATH # define O_BENEATH 00040000000 # define O_XDEV 00100000000 # define O_NOPROCLINKS 00200000000 # define O_NOSYMLINKS 01000000000 # define O_THISROOT 02000000000 #endif #ifndef AT_BENEATH # define AT_BENEATH 0x8000 # define AT_XDEV 0x10000 # define AT_NO_PROCLINKS 0x20000 # define AT_NO_SYMLINKS 0x40000 # define AT_THIS_ROOT 0x80000 #endif #define bail(...) \ do { \ fprintf(stderr, __VA_ARGS__); \ fputs("\n", stderr); \ exit(1); \ } while (0) int renameat2(int olddirfd, const char *oldpath, int newdirfd, const char *newpath, unsigned int flags) { errno = syscall(__NR_renameat2, olddirfd, oldpath, newdirfd, newpath, flags); return errno < 0 ? -1 : 0; } char *sprint_path(int fd) { char *fdpath = NULL, *fullpath; if (asprintf(&fdpath, "/proc/self/fd/%d", fd) < 0) return NULL; fullpath = malloc(PATH_MAX+1); if (!fullpath) goto out1; memset(fullpath, '\0', PATH_MAX+1); if (readlink(fdpath, fullpath, PATH_MAX) < 0) goto out2; free(fdpath); return fullpath; out2: free(fullpath); out1: free(fdpath); return NULL; } int rename_attacker(int dirfd, char *namea, char *nameb) { for (;;) { if (renameat2(dirfd, namea, dirfd, nameb, RENAME_EXCHANGE) < 0) fprintf(stderr, "rename a<=>b failed: %m\n"); } return 1; } int openat_victim(int dirfd, char *path, unsigned int flags) { char *last_path = NULL; int last_errno = 0; for (;;) { int fd = openat(dirfd, path, flags); if (fd < 0) { if (errno != last_errno) printf("errno=%m\n"); last_errno = errno; } else { char *path = sprint_path(fd); if (!strcmp(path, "/")) { puts("[[ BREAKOUT ]]"); printf("fd=%d\n", fd); for (;;) ; } if (!last_path || strcmp(last_path, path)) { printf("path=%s\n", path); free(last_path); last_path = strdup(path); } free(path); close(fd); } } free(last_path); return 1; } int main(void) { char tmppath[] = "__TEST_rename_attack.XXXXXX"; if (!mkdtemp(tmppath)) bail("mkdtemp: %m"); int tmpfd = openat(AT_FDCWD, tmppath, O_PATH|O_DIRECTORY); if (tmpfd < 0) bail("open tmppath: %m"); // Make dira and dirb. if (mkdirat(tmpfd, "a", 0755) < 0) bail("mkdir dira: %m"); if (mkdirat(tmpfd, "b", 0755) < 0) bail("mkdir dirb: %m"); if (mkdirat(tmpfd, "a/c", 0755) < 0) bail("mkdir dira/c: %m"); // Open "a". int afd = openat(tmpfd, "a", O_PATH|O_DIRECTORY); if (afd < 0) bail("open dira: %m"); pid_t child = fork(); if (child < 0) bail("fork child: %m"); else if (!child) return rename_attacker(tmpfd, "a/c", "b"); else return openat_victim(afd, "c/../../../../../../../../../../../../../../../../../../../../../../../../../", O_THISROOT|O_RDONLY); return 1; }