forked from torvalds/linux
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Test all of the various openat2(2) flags, as well as how file descriptor re-opening works. A small stress-test of a symlink-rename attack is included to show that the protections against ".."-based attacks are sufficient. In addition, the memfd selftest is fixed to no longer depend on the now-disallowed functionality of upgrading an O_RDONLY descriptor to O_RDWR. The main things these self-tests are enforcing are: * The struct+usize ABI for openat2(2) and copy_struct_from_user() to ensure that upgrades will be handled gracefully (in addition, ensuring that misaligned structures are also handled correctly). * The -EINVAL checks for openat2(2) are all correctly handled to avoid userspace passing unknown or conflicting flag sets (most importantly, ensuring that invalid flag combinations are checked). * All of the RESOLVE_* semantics (including errno values) are correctly handled with various combinations of paths and flags. * RESOLVE_IN_ROOT correctly protects against the symlink rename(2) attack that has been responsible for several CVEs (and likely will be responsible for several more). * The magic-link trailing mode semantics correctly block re-opens in all of the relevant cases, as well as checking that the "flip-flop" attack is correctly protected against. * O_PATH has the correct semantics (the mode is g+rwx for ordinary files, but for trailing magic-links the mode gets inherited). Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>
- Loading branch information
Showing
10 changed files
with
1,883 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
/*_test |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
# SPDX-License-Identifier: GPL-2.0-or-later | ||
|
||
CFLAGS += -Wall -O2 -g #-fsanitize=address -fsanitize=undefined | ||
TEST_GEN_PROGS := linkmode_test openat2_test resolve_test rename_attack_test | ||
|
||
include ../lib.mk | ||
|
||
$(TEST_GEN_PROGS): helpers.c |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
// SPDX-License-Identifier: GPL-2.0-or-later | ||
/* | ||
* Author: Aleksa Sarai <cyphar@cyphar.com> | ||
* Copyright (C) 2018-2019 SUSE LLC. | ||
*/ | ||
|
||
#define _GNU_SOURCE | ||
#include <errno.h> | ||
#include <fcntl.h> | ||
#include <stdbool.h> | ||
#include <string.h> | ||
#include <syscall.h> | ||
#include <limits.h> | ||
|
||
#include "helpers.h" | ||
|
||
bool needs_openat2(const struct open_how *how) | ||
{ | ||
return how->upgrade_mask != 0 || how->resolve != 0; | ||
} | ||
|
||
int raw_openat2(int dfd, const char *path, void *how, size_t size) | ||
{ | ||
int ret = syscall(__NR_openat2, dfd, path, how, size); | ||
return ret >= 0 ? ret : -errno; | ||
} | ||
|
||
int sys_openat2(int dfd, const char *path, struct open_how *how) | ||
{ | ||
return raw_openat2(dfd, path, how, sizeof(*how)); | ||
} | ||
|
||
int sys_openat(int dfd, const char *path, struct open_how *how) | ||
{ | ||
int ret = openat(dfd, path, how->flags, how->mode); | ||
return ret >= 0 ? ret : -errno; | ||
} | ||
|
||
int sys_renameat2(int olddirfd, const char *oldpath, | ||
int newdirfd, const char *newpath, unsigned int flags) | ||
{ | ||
int ret = syscall(__NR_renameat2, olddirfd, oldpath, | ||
newdirfd, newpath, flags); | ||
return ret >= 0 ? ret : -errno; | ||
} | ||
|
||
int touchat(int dfd, const char *path) | ||
{ | ||
int fd = openat(dfd, path, O_CREAT); | ||
if (fd >= 0) | ||
close(fd); | ||
return fd; | ||
} | ||
|
||
char *fdreadlink(int fd) | ||
{ | ||
char *target, *tmp; | ||
|
||
E_asprintf(&tmp, "/proc/self/fd/%d", fd); | ||
|
||
target = malloc(PATH_MAX); | ||
if (!target) | ||
ksft_exit_fail_msg("fdreadlink: malloc failed\n"); | ||
memset(target, 0, PATH_MAX); | ||
|
||
E_readlink(tmp, target, PATH_MAX); | ||
free(tmp); | ||
return target; | ||
} | ||
|
||
bool fdequal(int fd, int dfd, const char *path) | ||
{ | ||
char *fdpath, *dfdpath, *other; | ||
bool cmp; | ||
|
||
fdpath = fdreadlink(fd); | ||
dfdpath = fdreadlink(dfd); | ||
|
||
if (!path) | ||
E_asprintf(&other, "%s", dfdpath); | ||
else if (*path == '/') | ||
E_asprintf(&other, "%s", path); | ||
else | ||
E_asprintf(&other, "%s/%s", dfdpath, path); | ||
|
||
cmp = !strcmp(fdpath, other); | ||
|
||
free(fdpath); | ||
free(dfdpath); | ||
free(other); | ||
return cmp; | ||
} | ||
|
||
bool openat2_supported = false; | ||
bool oemptypath_supported = false; | ||
|
||
void __attribute__((constructor)) init(void) | ||
{ | ||
struct open_how how = {}; | ||
int fd, fd1, fd2; | ||
|
||
BUILD_BUG_ON(sizeof(struct open_how) != OPEN_HOW_SIZE_VER0); | ||
|
||
/* Check openat2(2) support. */ | ||
fd = sys_openat2(AT_FDCWD, ".", &how); | ||
openat2_supported = (fd >= 0); | ||
|
||
if (fd >= 0) | ||
close(fd); | ||
|
||
/* Check O_EMPTYPATH support. */ | ||
fd1 = sys_openat(AT_FDCWD, ".", &how); | ||
E_assert(fd1 >= 0, "open cwd failed: %m\n"); | ||
|
||
how.flags = O_EMPTYPATH; | ||
fd2 = sys_openat(fd1, "", &how); | ||
oemptypath_supported = (fd >= 0); | ||
|
||
if (fd2 >= 0) | ||
close(fd2); | ||
close(fd1); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
// SPDX-License-Identifier: GPL-2.0-or-later | ||
/* | ||
* Author: Aleksa Sarai <cyphar@cyphar.com> | ||
* Copyright (C) 2018-2019 SUSE LLC. | ||
*/ | ||
|
||
#ifndef __RESOLVEAT_H__ | ||
#define __RESOLVEAT_H__ | ||
|
||
#define _GNU_SOURCE | ||
#include <stdint.h> | ||
#include <errno.h> | ||
#include <linux/types.h> | ||
#include "../kselftest.h" | ||
|
||
#define ARRAY_LEN(X) (sizeof (X) / sizeof (*(X))) | ||
#define BUILD_BUG_ON(e) ((void)(sizeof(struct { int:(-!!(e)); }))) | ||
|
||
#ifndef SYS_openat2 | ||
#ifndef __NR_openat2 | ||
#define __NR_openat2 437 | ||
#endif /* __NR_openat2 */ | ||
#define SYS_openat2 __NR_openat2 | ||
#endif /* SYS_openat2 */ | ||
|
||
/* | ||
* Arguments for how openat2(2) should open the target path. If @resolve is | ||
* zero, then openat2(2) operates very similarly to openat(2). | ||
* | ||
* However, unlike openat(2), unknown bits in @flags result in -EINVAL rather | ||
* than being silently ignored. @mode must be zero unless one of {O_CREAT, | ||
* O_TMPFILE} are set, and @upgrade_mask must be zero unless O_PATH is set. | ||
* | ||
* @flags: O_* flags. | ||
* @mode: O_CREAT/O_TMPFILE file mode. | ||
* @upgrade_mask: UPGRADE_* flags (to restrict O_PATH re-opening). | ||
* @resolve: RESOLVE_* flags. | ||
*/ | ||
struct open_how { | ||
__aligned_u64 flags; | ||
__u16 mode; | ||
__u16 upgrade_mask; | ||
__u16 __padding[2]; /* must be zeroed */ | ||
__aligned_u64 resolve; | ||
}; | ||
|
||
#define OPEN_HOW_SIZE_VER0 24 /* sizeof first published struct */ | ||
#define OPEN_HOW_SIZE_LATEST OPEN_HOW_SIZE_VER0 | ||
|
||
bool needs_openat2(const struct open_how *how); | ||
|
||
#ifndef RESOLVE_IN_ROOT | ||
/* how->resolve flags for openat2(2). */ | ||
#define RESOLVE_NO_XDEV 0x01 /* Block mount-point crossings | ||
(includes bind-mounts). */ | ||
#define RESOLVE_NO_MAGICLINKS 0x02 /* Block traversal through procfs-style | ||
"magic-links". */ | ||
#define RESOLVE_NO_SYMLINKS 0x04 /* Block traversal through all symlinks | ||
(implies OEXT_NO_MAGICLINKS) */ | ||
#define RESOLVE_BENEATH 0x08 /* Block "lexical" trickery like | ||
"..", symlinks, and absolute | ||
paths which escape the dirfd. */ | ||
#define RESOLVE_IN_ROOT 0x10 /* Make all jumps to "/" and ".." | ||
be scoped inside the dirfd | ||
(similar to chroot(2)). */ | ||
#endif /* RESOLVE_IN_ROOT */ | ||
|
||
#ifndef UPGRADE_NOREAD | ||
/* how->upgrade flags for openat2(2). */ | ||
/* First bit is reserved for a future UPGRADE_NOEXEC flag. */ | ||
#define UPGRADE_NOREAD 0x02 /* Block re-opening with MAY_READ. */ | ||
#define UPGRADE_NOWRITE 0x04 /* Block re-opening with MAY_WRITE. */ | ||
#endif /* UPGRADE_NOREAD */ | ||
|
||
#ifndef O_EMPTYPATH | ||
#define O_EMPTYPATH 040000000 | ||
#endif /* O_EMPTYPATH */ | ||
|
||
#define E_func(func, ...) \ | ||
do { \ | ||
if (func(__VA_ARGS__) < 0) \ | ||
ksft_exit_fail_msg("%s:%d %s failed\n", \ | ||
__FILE__, __LINE__, #func);\ | ||
} while (0) | ||
|
||
#define E_asprintf(...) E_func(asprintf, __VA_ARGS__) | ||
#define E_chmod(...) E_func(chmod, __VA_ARGS__) | ||
#define E_dup2(...) E_func(dup2, __VA_ARGS__) | ||
#define E_fchdir(...) E_func(fchdir, __VA_ARGS__) | ||
#define E_fstatat(...) E_func(fstatat, __VA_ARGS__) | ||
#define E_kill(...) E_func(kill, __VA_ARGS__) | ||
#define E_mkdirat(...) E_func(mkdirat, __VA_ARGS__) | ||
#define E_mount(...) E_func(mount, __VA_ARGS__) | ||
#define E_prctl(...) E_func(prctl, __VA_ARGS__) | ||
#define E_readlink(...) E_func(readlink, __VA_ARGS__) | ||
#define E_setresuid(...) E_func(setresuid, __VA_ARGS__) | ||
#define E_symlinkat(...) E_func(symlinkat, __VA_ARGS__) | ||
#define E_touchat(...) E_func(touchat, __VA_ARGS__) | ||
#define E_unshare(...) E_func(unshare, __VA_ARGS__) | ||
|
||
#define E_assert(expr, msg, ...) \ | ||
do { \ | ||
if (!(expr)) \ | ||
ksft_exit_fail_msg("ASSERT(%s:%d) failed (%s): " msg "\n", \ | ||
__FILE__, __LINE__, #expr, ##__VA_ARGS__); \ | ||
} while (0) | ||
|
||
int raw_openat2(int dfd, const char *path, void *how, size_t size); | ||
int sys_openat2(int dfd, const char *path, struct open_how *how); | ||
int sys_openat(int dfd, const char *path, struct open_how *how); | ||
int sys_renameat2(int olddirfd, const char *oldpath, | ||
int newdirfd, const char *newpath, unsigned int flags); | ||
|
||
int touchat(int dfd, const char *path); | ||
char *fdreadlink(int fd); | ||
bool fdequal(int fd, int dfd, const char *path); | ||
|
||
extern bool openat2_supported; | ||
extern bool oemptypath_supported; | ||
|
||
#endif /* __RESOLVEAT_H__ */ |
Oops, something went wrong.