1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
|
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <sys/eventfd.h>
#include "fd-util.h"
#include "main-func.h"
#include "missing_mount.h"
#include "missing_syscall.h"
#include "namespace-util.h"
#include "process-util.h"
#include "rm-rf.h"
#include "tmpfile-util.h"
#include "userns-restrict.h"
static int make_tmpfs_fsmount(void) {
_cleanup_close_ int fsfd = -EBADF, mntfd = -EBADF;
fsfd = fsopen("tmpfs", FSOPEN_CLOEXEC);
assert_se(fsfd >= 0);
assert_se(fsconfig(fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0) >= 0);
mntfd = fsmount(fsfd, FSMOUNT_CLOEXEC, 0);
assert_se(mntfd >= 0);
return TAKE_FD(mntfd);
}
static void test_works_reg(int parent_fd, const char *fname) {
_cleanup_close_ int fd = -EBADF;
fd = openat(parent_fd, fname, O_RDWR|O_CREAT|O_CLOEXEC, 0666);
assert_se(fd >= 0);
}
static void test_fails_reg(int parent_fd, const char *fname) {
errno = 0;
assert_se(openat(parent_fd, fname, O_RDWR|O_CREAT|O_CLOEXEC, 0666) < 0);
assert_se(errno == EPERM);
}
static void test_works_dir(int parent_fd, const char *fname) {
assert_se(mkdirat(parent_fd, fname, 0666) >= 0);
}
static void test_fails_dir(int parent_fd, const char *fname) {
errno = 0;
assert_se(mkdirat(parent_fd, fname, 0666) < 0);
assert_se(errno == EPERM);
}
static int run(int argc, char *argv[]) {
_cleanup_(userns_restrict_bpf_freep) struct userns_restrict_bpf *obj = NULL;
_cleanup_close_ int userns_fd = -EBADF, host_fd1 = -EBADF, host_tmpfs = -EBADF, afd = -EBADF, bfd = -EBADF;
_cleanup_(rm_rf_physical_and_freep) char *t = NULL;
_cleanup_(sigkill_waitp) pid_t pid = 0;
int r;
log_set_max_level(LOG_DEBUG);
log_setup();
r = userns_restrict_install(/* pin= */ false, &obj);
if (ERRNO_IS_NOT_SUPPORTED(r)) {
log_notice("Skipping test, LSM-BPF logic not supported.");
return EXIT_TEST_SKIP;
}
if (ERRNO_IS_PRIVILEGE(r)) {
log_notice("Skipping test, lacking privileges.");
return EXIT_TEST_SKIP;
}
if (r < 0)
return r;
assert_se(mkdtemp_malloc(NULL, &t) >= 0);
host_fd1 = open(t, O_DIRECTORY|O_CLOEXEC);
assert_se(host_fd1 >= 0);
host_tmpfs = make_tmpfs_fsmount();
assert_se(host_tmpfs >= 0);
userns_fd = userns_acquire("0 0 1", "0 0 1");
if (userns_fd < 0)
return log_error_errno(userns_fd, "Failed to make user namespace: %m");
r = userns_restrict_put_by_fd(
obj,
userns_fd,
/* replace= */ true,
/* mount_fds= */ NULL,
/* n_mount_fds= */ 0);
if (r < 0)
return log_error_errno(r, "Failed to restrict user namespace: %m");
afd = eventfd(0, EFD_CLOEXEC);
bfd = eventfd(0, EFD_CLOEXEC);
assert_se(afd >= 0 && bfd >= 0);
r = safe_fork("(test)", FORK_DEATHSIG_SIGKILL, &pid);
assert_se(r >= 0);
if (r == 0) {
_cleanup_close_ int private_tmpfs = -EBADF;
assert_se(setns(userns_fd, CLONE_NEWUSER) >= 0);
assert_se(unshare(CLONE_NEWNS) >= 0);
/* Allocate tmpfs locally */
private_tmpfs = make_tmpfs_fsmount();
/* These two host mounts should be inaccessible */
test_fails_reg(host_fd1, "test");
test_fails_reg(host_tmpfs, "xxx");
test_fails_dir(host_fd1, "test2");
test_fails_dir(host_tmpfs, "xxx2");
/* But this mount created locally should be fine */
test_works_reg(private_tmpfs, "yyy");
test_works_dir(private_tmpfs, "yyy2");
/* Let's sync with the parent, so that it allowlists more stuff for us */
assert_se(eventfd_write(afd, 1) >= 0);
uint64_t x;
assert_se(eventfd_read(bfd, &x) >= 0);
/* And now we should also have access to the host tmpfs */
test_works_reg(host_tmpfs, "zzz");
test_works_reg(private_tmpfs, "aaa");
test_works_dir(host_tmpfs, "zzz2");
test_works_dir(private_tmpfs, "aaa2");
/* But this one should still fail */
test_fails_reg(host_fd1, "bbb");
test_fails_dir(host_fd1, "bbb2");
/* Sync again, to get more stuff allowlisted */
assert_se(eventfd_write(afd, 1) >= 0);
assert_se(eventfd_read(bfd, &x) >= 0);
/* Everything should now be allowed */
test_works_reg(host_tmpfs, "ccc");
test_works_reg(host_fd1, "ddd");
test_works_reg(private_tmpfs, "eee");
test_works_dir(host_tmpfs, "ccc2");
test_works_reg(host_fd1, "ddd2");
test_works_dir(private_tmpfs, "eee2");
_exit(EXIT_SUCCESS);
}
uint64_t x;
assert_se(eventfd_read(afd, &x) >= 0);
r = userns_restrict_put_by_fd(
obj,
userns_fd,
/* replace= */ false,
&host_tmpfs,
1);
if (r < 0)
return log_error_errno(r, "Failed to loosen user namespace: %m");
assert_se(eventfd_write(bfd, 1) >= 0);
assert_se(eventfd_read(afd, &x) >= 0);
r = userns_restrict_put_by_fd(
obj,
userns_fd,
/* replace= */ false,
&host_fd1,
1);
if (r < 0)
return log_error_errno(r, "Failed to loosen user namespace: %m");
assert_se(eventfd_write(bfd, 1) >= 0);
assert_se(wait_for_terminate_and_check("(test)", pid, WAIT_LOG) >= 0);
return 0;
}
DEFINE_MAIN_FUNCTION(run);
|