Skip to content

Commit b28a10a

Browse files
cypharAl Viro
authored andcommitted
selftests: add openat2(2) selftests
Test all of the various openat2(2) flags. A small stress-test of a symlink-rename attack is included to show that the protections against ".."-based attacks are sufficient. 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). Cc: Shuah Khan <[email protected]> Signed-off-by: Aleksa Sarai <[email protected]> Signed-off-by: Al Viro <[email protected]>
1 parent fddb5d4 commit b28a10a

File tree

8 files changed

+1220
-0
lines changed

8 files changed

+1220
-0
lines changed

tools/testing/selftests/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ TARGETS += powerpc
4040
TARGETS += proc
4141
TARGETS += pstore
4242
TARGETS += ptrace
43+
TARGETS += openat2
4344
TARGETS += rseq
4445
TARGETS += rtc
4546
TARGETS += seccomp
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/*_test
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# SPDX-License-Identifier: GPL-2.0-or-later
2+
3+
CFLAGS += -Wall -O2 -g -fsanitize=address -fsanitize=undefined
4+
TEST_GEN_PROGS := openat2_test resolve_test rename_attack_test
5+
6+
include ../lib.mk
7+
8+
$(TEST_GEN_PROGS): helpers.c
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// SPDX-License-Identifier: GPL-2.0-or-later
2+
/*
3+
* Author: Aleksa Sarai <[email protected]>
4+
* Copyright (C) 2018-2019 SUSE LLC.
5+
*/
6+
7+
#define _GNU_SOURCE
8+
#include <errno.h>
9+
#include <fcntl.h>
10+
#include <stdbool.h>
11+
#include <string.h>
12+
#include <syscall.h>
13+
#include <limits.h>
14+
15+
#include "helpers.h"
16+
17+
bool needs_openat2(const struct open_how *how)
18+
{
19+
return how->resolve != 0;
20+
}
21+
22+
int raw_openat2(int dfd, const char *path, void *how, size_t size)
23+
{
24+
int ret = syscall(__NR_openat2, dfd, path, how, size);
25+
return ret >= 0 ? ret : -errno;
26+
}
27+
28+
int sys_openat2(int dfd, const char *path, struct open_how *how)
29+
{
30+
return raw_openat2(dfd, path, how, sizeof(*how));
31+
}
32+
33+
int sys_openat(int dfd, const char *path, struct open_how *how)
34+
{
35+
int ret = openat(dfd, path, how->flags, how->mode);
36+
return ret >= 0 ? ret : -errno;
37+
}
38+
39+
int sys_renameat2(int olddirfd, const char *oldpath,
40+
int newdirfd, const char *newpath, unsigned int flags)
41+
{
42+
int ret = syscall(__NR_renameat2, olddirfd, oldpath,
43+
newdirfd, newpath, flags);
44+
return ret >= 0 ? ret : -errno;
45+
}
46+
47+
int touchat(int dfd, const char *path)
48+
{
49+
int fd = openat(dfd, path, O_CREAT);
50+
if (fd >= 0)
51+
close(fd);
52+
return fd;
53+
}
54+
55+
char *fdreadlink(int fd)
56+
{
57+
char *target, *tmp;
58+
59+
E_asprintf(&tmp, "/proc/self/fd/%d", fd);
60+
61+
target = malloc(PATH_MAX);
62+
if (!target)
63+
ksft_exit_fail_msg("fdreadlink: malloc failed\n");
64+
memset(target, 0, PATH_MAX);
65+
66+
E_readlink(tmp, target, PATH_MAX);
67+
free(tmp);
68+
return target;
69+
}
70+
71+
bool fdequal(int fd, int dfd, const char *path)
72+
{
73+
char *fdpath, *dfdpath, *other;
74+
bool cmp;
75+
76+
fdpath = fdreadlink(fd);
77+
dfdpath = fdreadlink(dfd);
78+
79+
if (!path)
80+
E_asprintf(&other, "%s", dfdpath);
81+
else if (*path == '/')
82+
E_asprintf(&other, "%s", path);
83+
else
84+
E_asprintf(&other, "%s/%s", dfdpath, path);
85+
86+
cmp = !strcmp(fdpath, other);
87+
88+
free(fdpath);
89+
free(dfdpath);
90+
free(other);
91+
return cmp;
92+
}
93+
94+
bool openat2_supported = false;
95+
96+
void __attribute__((constructor)) init(void)
97+
{
98+
struct open_how how = {};
99+
int fd;
100+
101+
BUILD_BUG_ON(sizeof(struct open_how) != OPEN_HOW_SIZE_VER0);
102+
103+
/* Check openat2(2) support. */
104+
fd = sys_openat2(AT_FDCWD, ".", &how);
105+
openat2_supported = (fd >= 0);
106+
107+
if (fd >= 0)
108+
close(fd);
109+
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// SPDX-License-Identifier: GPL-2.0-or-later
2+
/*
3+
* Author: Aleksa Sarai <[email protected]>
4+
* Copyright (C) 2018-2019 SUSE LLC.
5+
*/
6+
7+
#ifndef __RESOLVEAT_H__
8+
#define __RESOLVEAT_H__
9+
10+
#define _GNU_SOURCE
11+
#include <stdint.h>
12+
#include <errno.h>
13+
#include <linux/types.h>
14+
#include "../kselftest.h"
15+
16+
#define ARRAY_LEN(X) (sizeof (X) / sizeof (*(X)))
17+
#define BUILD_BUG_ON(e) ((void)(sizeof(struct { int:(-!!(e)); })))
18+
19+
#ifndef SYS_openat2
20+
#ifndef __NR_openat2
21+
#define __NR_openat2 437
22+
#endif /* __NR_openat2 */
23+
#define SYS_openat2 __NR_openat2
24+
#endif /* SYS_openat2 */
25+
26+
/*
27+
* Arguments for how openat2(2) should open the target path. If @resolve is
28+
* zero, then openat2(2) operates very similarly to openat(2).
29+
*
30+
* However, unlike openat(2), unknown bits in @flags result in -EINVAL rather
31+
* than being silently ignored. @mode must be zero unless one of {O_CREAT,
32+
* O_TMPFILE} are set.
33+
*
34+
* @flags: O_* flags.
35+
* @mode: O_CREAT/O_TMPFILE file mode.
36+
* @resolve: RESOLVE_* flags.
37+
*/
38+
struct open_how {
39+
__u64 flags;
40+
__u64 mode;
41+
__u64 resolve;
42+
};
43+
44+
#define OPEN_HOW_SIZE_VER0 24 /* sizeof first published struct */
45+
#define OPEN_HOW_SIZE_LATEST OPEN_HOW_SIZE_VER0
46+
47+
bool needs_openat2(const struct open_how *how);
48+
49+
#ifndef RESOLVE_IN_ROOT
50+
/* how->resolve flags for openat2(2). */
51+
#define RESOLVE_NO_XDEV 0x01 /* Block mount-point crossings
52+
(includes bind-mounts). */
53+
#define RESOLVE_NO_MAGICLINKS 0x02 /* Block traversal through procfs-style
54+
"magic-links". */
55+
#define RESOLVE_NO_SYMLINKS 0x04 /* Block traversal through all symlinks
56+
(implies OEXT_NO_MAGICLINKS) */
57+
#define RESOLVE_BENEATH 0x08 /* Block "lexical" trickery like
58+
"..", symlinks, and absolute
59+
paths which escape the dirfd. */
60+
#define RESOLVE_IN_ROOT 0x10 /* Make all jumps to "/" and ".."
61+
be scoped inside the dirfd
62+
(similar to chroot(2)). */
63+
#endif /* RESOLVE_IN_ROOT */
64+
65+
#define E_func(func, ...) \
66+
do { \
67+
if (func(__VA_ARGS__) < 0) \
68+
ksft_exit_fail_msg("%s:%d %s failed\n", \
69+
__FILE__, __LINE__, #func);\
70+
} while (0)
71+
72+
#define E_asprintf(...) E_func(asprintf, __VA_ARGS__)
73+
#define E_chmod(...) E_func(chmod, __VA_ARGS__)
74+
#define E_dup2(...) E_func(dup2, __VA_ARGS__)
75+
#define E_fchdir(...) E_func(fchdir, __VA_ARGS__)
76+
#define E_fstatat(...) E_func(fstatat, __VA_ARGS__)
77+
#define E_kill(...) E_func(kill, __VA_ARGS__)
78+
#define E_mkdirat(...) E_func(mkdirat, __VA_ARGS__)
79+
#define E_mount(...) E_func(mount, __VA_ARGS__)
80+
#define E_prctl(...) E_func(prctl, __VA_ARGS__)
81+
#define E_readlink(...) E_func(readlink, __VA_ARGS__)
82+
#define E_setresuid(...) E_func(setresuid, __VA_ARGS__)
83+
#define E_symlinkat(...) E_func(symlinkat, __VA_ARGS__)
84+
#define E_touchat(...) E_func(touchat, __VA_ARGS__)
85+
#define E_unshare(...) E_func(unshare, __VA_ARGS__)
86+
87+
#define E_assert(expr, msg, ...) \
88+
do { \
89+
if (!(expr)) \
90+
ksft_exit_fail_msg("ASSERT(%s:%d) failed (%s): " msg "\n", \
91+
__FILE__, __LINE__, #expr, ##__VA_ARGS__); \
92+
} while (0)
93+
94+
int raw_openat2(int dfd, const char *path, void *how, size_t size);
95+
int sys_openat2(int dfd, const char *path, struct open_how *how);
96+
int sys_openat(int dfd, const char *path, struct open_how *how);
97+
int sys_renameat2(int olddirfd, const char *oldpath,
98+
int newdirfd, const char *newpath, unsigned int flags);
99+
100+
int touchat(int dfd, const char *path);
101+
char *fdreadlink(int fd);
102+
bool fdequal(int fd, int dfd, const char *path);
103+
104+
extern bool openat2_supported;
105+
106+
#endif /* __RESOLVEAT_H__ */

0 commit comments

Comments
 (0)