1 /*
2 * Copyright (C) 2013-2021 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 *
18 * This code is a complete clean re-write of the stress tool by
19 * Colin Ian King <colin.king@canonical.com> and attempts to be
20 * backwardly compatible with the stress tool by Amos Waterland
21 * <apw@rossby.metr.ou.edu> but has more stress tests and more
22 * functionality.
23 *
24 */
25 #include "stress-ng.h"
26
27 static const stress_help_t help[] = {
28 { NULL, "landlock N", "start N workers stressing landlock file operations" },
29 { NULL, "landlock-ops N", "stop after N landlock bogo operations" },
30 { NULL, NULL, NULL }
31 };
32 #define SHIM_LANDLOCK_CREATE_RULESET_VERSION (1U << 0)
33
34 #define SHIM_LANDLOCK_ACCESS_FS_EXECUTE (1ULL << 0)
35 #define SHIM_LANDLOCK_ACCESS_FS_WRITE_FILE (1ULL << 1)
36 #define SHIM_LANDLOCK_ACCESS_FS_READ_FILE (1ULL << 2)
37 #define SHIM_LANDLOCK_ACCESS_FS_READ_DIR (1ULL << 3)
38 #define SHIM_LANDLOCK_ACCESS_FS_REMOVE_DIR (1ULL << 4)
39 #define SHIM_LANDLOCK_ACCESS_FS_REMOVE_FILE (1ULL << 5)
40 #define SHIM_LANDLOCK_ACCESS_FS_MAKE_CHAR (1ULL << 6)
41 #define SHIM_LANDLOCK_ACCESS_FS_MAKE_DIR (1ULL << 7)
42 #define SHIM_LANDLOCK_ACCESS_FS_MAKE_REG (1ULL << 8)
43 #define SHIM_LANDLOCK_ACCESS_FS_MAKE_SOCK (1ULL << 9)
44 #define SHIM_LANDLOCK_ACCESS_FS_MAKE_FIFO (1ULL << 10)
45 #define SHIM_LANDLOCK_ACCESS_FS_MAKE_BLOCK (1ULL << 11)
46 #define SHIM_LANDLOCK_ACCESS_FS_MAKE_SYM (1ULL << 12)
47
48 #if defined(HAVE_LINUX_LANDLOCK_H) && \
49 defined(HAVE_LANDLOCK_RULE_TYPE) && \
50 defined(HAVE_LANDLOCK_RULESET_ATTR) && \
51 defined(__NR_landlock_create_ruleset) && \
52 defined(__NR_landlock_restrict_self) && \
53 defined(__NR_landlock_add_rule)
54
55 typedef int (*stress_landlock_func)(const stress_args_t *args, void *ctxt);
56
shim_landlock_create_ruleset(struct landlock_ruleset_attr * attr,size_t size,uint32_t flags)57 static int shim_landlock_create_ruleset(
58 struct landlock_ruleset_attr *attr,
59 size_t size,
60 uint32_t flags)
61 {
62 #if defined(__NR_landlock_create_ruleset)
63 return (int)syscall(__NR_landlock_create_ruleset, attr, size, flags);
64 #else
65 (void)attr;
66 (void)size;
67 (void)flags;
68
69 errno = ENOSYS;
70 return -1;
71 #endif
72 }
73
shim_landlock_restrict_self(const int fd,const uint32_t flags)74 static int shim_landlock_restrict_self(const int fd, const uint32_t flags)
75 {
76 #if defined(__NR_landlock_restrict_self)
77 return (int)syscall(__NR_landlock_restrict_self, fd, flags);
78 #else
79 (void)fd;
80 (void)flags;
81
82 errno = ENOSYS;
83 return -1;
84 #endif
85 }
86
shim_landlock_add_rule(const int fd,const enum landlock_rule_type type,const void * const rule_attr,const uint32_t flags)87 static int shim_landlock_add_rule(
88 const int fd,
89 const enum landlock_rule_type type,
90 const void *const rule_attr,
91 const uint32_t flags)
92 {
93 #if defined(__NR_landlock_add_rule)
94 return (int)syscall(__NR_landlock_add_rule, fd, type, rule_attr, flags);
95 #else
96 (void)fd;
97 (void)type;
98 (void)rule_attr;
99 (void)flags;
100
101 errno = ENOSYS;
102 return -1;
103 #endif
104 }
105
stress_landlock_supported(const char * name)106 static int stress_landlock_supported(const char *name)
107 {
108 int ruleset_fd;
109 struct landlock_ruleset_attr ruleset_attr;
110
111 (void)memset(&ruleset_attr, 0, sizeof(ruleset_attr));
112 ruleset_attr.handled_access_fs = SHIM_LANDLOCK_ACCESS_FS_READ_FILE;
113
114 ruleset_fd = shim_landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
115 if (ruleset_fd < 0) {
116 if (errno == ENOSYS) {
117 pr_inf_skip("%s: stressor will be skipped, landlock_create_ruleset system call"
118 "is not supported\n", name);
119 } else {
120 pr_inf_skip("%s: stressor will be skipped, perhaps "
121 "lsm=landlock is not enabled\n", name);
122 }
123 return -1;
124 }
125
126 (void)close(ruleset_fd);
127 return 0;
128 }
129
stress_landlock_flag(const stress_args_t * args,void * ctxt)130 static int stress_landlock_flag(const stress_args_t *args, void *ctxt)
131 {
132 uint32_t flag = *(uint32_t *)ctxt;
133 const char *path = stress_get_temp_path();
134 char filename[PATH_MAX];
135
136 int ruleset_fd, fd, ret, rc = EXIT_SUCCESS;
137 const pid_t pid = getpid();
138 struct landlock_ruleset_attr ruleset_attr;
139 struct landlock_path_beneath_attr path_beneath = {
140 .allowed_access = LANDLOCK_ACCESS_FS_READ_FILE |
141 LANDLOCK_ACCESS_FS_READ_DIR,
142 };
143 struct landlock_path_beneath_attr bad_path_beneath = {
144 .allowed_access = LANDLOCK_ACCESS_FS_READ_FILE |
145 LANDLOCK_ACCESS_FS_READ_DIR,
146 };
147
148 if (!path)
149 return 0;
150
151 (void)memset(&ruleset_attr, 0, sizeof(ruleset_attr));
152 /* Exercise illegal ruleset sizes, EINVAL */
153 ret = shim_landlock_create_ruleset(&ruleset_attr, 0, 0);
154 (void)ret;
155 /* Exercise illegal ruleset sizes, E2BIG */
156 ret = shim_landlock_create_ruleset(&ruleset_attr, 4096, 0);
157 (void)ret;
158 /* Exercise fetch of ruleset API version, ignore return */
159 ret = shim_landlock_create_ruleset(NULL, 0, SHIM_LANDLOCK_CREATE_RULESET_VERSION);
160 (void)ret;
161
162 (void)memset(&ruleset_attr, 0, sizeof(ruleset_attr));
163 ruleset_attr.handled_access_fs = flag;
164
165 ruleset_fd = shim_landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
166 if (ruleset_fd < 0)
167 return 0;
168
169 /* Exercise illegal parent_fd */
170 path_beneath.parent_fd = -1;
171 ret = shim_landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
172 &path_beneath, 0);
173 (void)ret;
174
175 path_beneath.parent_fd = open(path, O_PATH | O_CLOEXEC);
176 if (path_beneath.parent_fd < 0)
177 goto close_ruleset;
178
179 /* Exercise illegal fd */
180 ret = shim_landlock_add_rule(-1, LANDLOCK_RULE_PATH_BENEATH,
181 &path_beneath, 0);
182 (void)ret;
183
184 /* Exercise illegal flags */
185 ret = shim_landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
186 &path_beneath, ~0);
187 (void)ret;
188 /* Exercise illegal rule type */
189 ret = shim_landlock_add_rule(ruleset_fd, ~0,
190 &path_beneath, 0);
191 (void)ret;
192
193 ret = shim_landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
194 &path_beneath, 0);
195 if (ret < 0)
196 goto close_parent;
197
198 /* Exercise illegal parent_fd */
199 bad_path_beneath.parent_fd = ruleset_fd;
200 ret = shim_landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
201 &bad_path_beneath, 0);
202 (void)ret;
203
204 ret = prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
205 if (ret < 0)
206 goto close_parent;
207
208 ret = shim_landlock_restrict_self(ruleset_fd, 0);
209 if (ret < 0)
210 goto close_parent;
211
212 /*
213 * Got a valid landlocked restricted child process,
214 * so now sanity check it on some test files
215 */
216 (void)snprintf(filename, sizeof(filename), "%s/landlock-%d", path, (int)pid);
217
218 fd = open(path, O_PATH | O_CLOEXEC);
219 if (fd > -1)
220 (void)close(fd);
221
222 fd = open(filename, O_CREAT | O_RDWR | O_CLOEXEC, S_IRUSR | S_IWUSR);
223 if (fd > -1) {
224 pr_fail("%s: failed to landlock writable file %s\n",
225 args->name, filename);
226 (void)unlink(filename);
227 (void)close(fd);
228 rc = EXIT_FAILURE;
229 goto close_parent;
230 }
231 if ((fd < 0) && (errno != EACCES)) {
232 pr_fail("%s: landlocked file create should have returned "
233 "errno=%d (%s), got errno=%d (%s) instead\n",
234 args->name,
235 EACCES, strerror(EACCES),
236 errno, strerror(errno));
237 rc = EXIT_FAILURE;
238 goto close_parent;
239 }
240
241 close_parent:
242 (void)close(path_beneath.parent_fd);
243 close_ruleset:
244 (void)close(ruleset_fd);
245
246 return rc;
247 }
248
stress_landlock_test(const stress_args_t * args,stress_landlock_func func,void * ctxt,int * failures)249 static void stress_landlock_test(
250 const stress_args_t *args,
251 stress_landlock_func func,
252 void *ctxt,
253 int *failures)
254 {
255 int status;
256 pid_t pid;
257
258 again:
259 pid = fork();
260 if (pid < 0) {
261 if (stress_redo_fork(errno))
262 goto again;
263 return;
264 } else if (pid == 0) {
265 _exit(func(args, ctxt));
266 } else {
267 if (shim_waitpid(pid, &status, 0) < 0) {
268 if (errno != EINTR) {
269 pr_err("%s: waitpid errno=%d (%s)\n",
270 args->name, errno, strerror(errno));
271 } else {
272 /* Probably an SIGARLM, force reap */
273 (void)kill(pid, SIGKILL);
274 (void)shim_waitpid(pid, &status, 0);
275 return;
276 }
277 }
278 if (WIFEXITED(status)) {
279 int rc = WEXITSTATUS(status);
280
281 if (rc != EXIT_SUCCESS)
282 (*failures)++;
283 return;
284 }
285 }
286 }
287
288 /*
289 * stress_landlock()
290 * stress landlock API
291 */
stress_landlock(const stress_args_t * args)292 static int stress_landlock(const stress_args_t *args)
293 {
294 static const int landlock_access_flags[] = {
295 SHIM_LANDLOCK_ACCESS_FS_EXECUTE,
296 SHIM_LANDLOCK_ACCESS_FS_WRITE_FILE,
297 SHIM_LANDLOCK_ACCESS_FS_READ_FILE,
298 SHIM_LANDLOCK_ACCESS_FS_READ_DIR,
299 SHIM_LANDLOCK_ACCESS_FS_REMOVE_DIR,
300 SHIM_LANDLOCK_ACCESS_FS_REMOVE_FILE,
301 SHIM_LANDLOCK_ACCESS_FS_MAKE_CHAR,
302 SHIM_LANDLOCK_ACCESS_FS_MAKE_DIR,
303 SHIM_LANDLOCK_ACCESS_FS_MAKE_REG,
304 SHIM_LANDLOCK_ACCESS_FS_MAKE_SOCK,
305 SHIM_LANDLOCK_ACCESS_FS_MAKE_FIFO,
306 SHIM_LANDLOCK_ACCESS_FS_MAKE_BLOCK,
307 SHIM_LANDLOCK_ACCESS_FS_MAKE_SYM,
308 0,
309 };
310 int failures = 0;
311
312 stress_set_proc_state(args->name, STRESS_STATE_RUN);
313
314 do {
315 size_t i;
316 uint32_t flags = 0;
317
318 /* Exercise with a mix of valid and invalid flags */
319 for (i = 0; i < SIZEOF_ARRAY(landlock_access_flags); i++) {
320 uint32_t flag = landlock_access_flags[i];
321
322 flags |= flag;
323 stress_landlock_test(args, stress_landlock_flag, &flag, &failures);
324 if (failures >= 5)
325 goto err;
326 }
327 stress_landlock_test(args, stress_landlock_flag, &flags, &failures);
328 if (failures >= 5)
329 goto err;
330 flags = ~flags;
331 stress_landlock_test(args, stress_landlock_flag, &flags, &failures);
332 if (failures >= 5)
333 goto err;
334
335 inc_counter(args);
336 } while (keep_stressing(args));
337
338 err:
339 stress_set_proc_state(args->name, STRESS_STATE_DEINIT);
340
341 return EXIT_SUCCESS;
342 }
343
344 stressor_info_t stress_landlock_info = {
345 .stressor = stress_landlock,
346 .class = CLASS_OS,
347 .supported = stress_landlock_supported,
348 .help = help
349 };
350 #else
351 stressor_info_t stress_landlock_info = {
352 .stressor = stress_not_implemented,
353 .class = CLASS_OS,
354 .help = help
355 };
356 #endif
357