GNU Linux-libre 4.19.304-gnu1
[releases.git] / tools / testing / selftests / cgroup / test_core.c
1 /* SPDX-License-Identifier: GPL-2.0 */
2
3 #define _GNU_SOURCE
4 #include <linux/limits.h>
5 #include <linux/sched.h>
6 #include <sys/types.h>
7 #include <sys/wait.h>
8 #include <unistd.h>
9 #include <fcntl.h>
10 #include <sched.h>
11 #include <stdio.h>
12 #include <errno.h>
13
14 #include "../kselftest.h"
15 #include "cgroup_util.h"
16
17 /*
18  * A(0) - B(0) - C(1)
19  *        \ D(0)
20  *
21  * A, B and C's "populated" fields would be 1 while D's 0.
22  * test that after the one process in C is moved to root,
23  * A,B and C's "populated" fields would flip to "0" and file
24  * modified events will be generated on the
25  * "cgroup.events" files of both cgroups.
26  */
27 static int test_cgcore_populated(const char *root)
28 {
29         int ret = KSFT_FAIL;
30         char *cg_test_a = NULL, *cg_test_b = NULL;
31         char *cg_test_c = NULL, *cg_test_d = NULL;
32
33         cg_test_a = cg_name(root, "cg_test_a");
34         cg_test_b = cg_name(root, "cg_test_a/cg_test_b");
35         cg_test_c = cg_name(root, "cg_test_a/cg_test_b/cg_test_c");
36         cg_test_d = cg_name(root, "cg_test_a/cg_test_b/cg_test_d");
37
38         if (!cg_test_a || !cg_test_b || !cg_test_c || !cg_test_d)
39                 goto cleanup;
40
41         if (cg_create(cg_test_a))
42                 goto cleanup;
43
44         if (cg_create(cg_test_b))
45                 goto cleanup;
46
47         if (cg_create(cg_test_c))
48                 goto cleanup;
49
50         if (cg_create(cg_test_d))
51                 goto cleanup;
52
53         if (cg_enter_current(cg_test_c))
54                 goto cleanup;
55
56         if (cg_read_strcmp(cg_test_a, "cgroup.events", "populated 1\n"))
57                 goto cleanup;
58
59         if (cg_read_strcmp(cg_test_b, "cgroup.events", "populated 1\n"))
60                 goto cleanup;
61
62         if (cg_read_strcmp(cg_test_c, "cgroup.events", "populated 1\n"))
63                 goto cleanup;
64
65         if (cg_read_strcmp(cg_test_d, "cgroup.events", "populated 0\n"))
66                 goto cleanup;
67
68         if (cg_enter_current(root))
69                 goto cleanup;
70
71         if (cg_read_strcmp(cg_test_a, "cgroup.events", "populated 0\n"))
72                 goto cleanup;
73
74         if (cg_read_strcmp(cg_test_b, "cgroup.events", "populated 0\n"))
75                 goto cleanup;
76
77         if (cg_read_strcmp(cg_test_c, "cgroup.events", "populated 0\n"))
78                 goto cleanup;
79
80         if (cg_read_strcmp(cg_test_d, "cgroup.events", "populated 0\n"))
81                 goto cleanup;
82
83         ret = KSFT_PASS;
84
85 cleanup:
86         if (cg_test_d)
87                 cg_destroy(cg_test_d);
88         if (cg_test_c)
89                 cg_destroy(cg_test_c);
90         if (cg_test_b)
91                 cg_destroy(cg_test_b);
92         if (cg_test_a)
93                 cg_destroy(cg_test_a);
94         free(cg_test_d);
95         free(cg_test_c);
96         free(cg_test_b);
97         free(cg_test_a);
98         return ret;
99 }
100
101 /*
102  * A (domain threaded) - B (threaded) - C (domain)
103  *
104  * test that C can't be used until it is turned into a
105  * threaded cgroup.  "cgroup.type" file will report "domain (invalid)" in
106  * these cases. Operations which fail due to invalid topology use
107  * EOPNOTSUPP as the errno.
108  */
109 static int test_cgcore_invalid_domain(const char *root)
110 {
111         int ret = KSFT_FAIL;
112         char *grandparent = NULL, *parent = NULL, *child = NULL;
113
114         grandparent = cg_name(root, "cg_test_grandparent");
115         parent = cg_name(root, "cg_test_grandparent/cg_test_parent");
116         child = cg_name(root, "cg_test_grandparent/cg_test_parent/cg_test_child");
117         if (!parent || !child || !grandparent)
118                 goto cleanup;
119
120         if (cg_create(grandparent))
121                 goto cleanup;
122
123         if (cg_create(parent))
124                 goto cleanup;
125
126         if (cg_create(child))
127                 goto cleanup;
128
129         if (cg_write(parent, "cgroup.type", "threaded"))
130                 goto cleanup;
131
132         if (cg_read_strcmp(child, "cgroup.type", "domain invalid\n"))
133                 goto cleanup;
134
135         if (!cg_enter_current(child))
136                 goto cleanup;
137
138         if (errno != EOPNOTSUPP)
139                 goto cleanup;
140
141         ret = KSFT_PASS;
142
143 cleanup:
144         cg_enter_current(root);
145         if (child)
146                 cg_destroy(child);
147         if (parent)
148                 cg_destroy(parent);
149         if (grandparent)
150                 cg_destroy(grandparent);
151         free(child);
152         free(parent);
153         free(grandparent);
154         return ret;
155 }
156
157 /*
158  * Test that when a child becomes threaded
159  * the parent type becomes domain threaded.
160  */
161 static int test_cgcore_parent_becomes_threaded(const char *root)
162 {
163         int ret = KSFT_FAIL;
164         char *parent = NULL, *child = NULL;
165
166         parent = cg_name(root, "cg_test_parent");
167         child = cg_name(root, "cg_test_parent/cg_test_child");
168         if (!parent || !child)
169                 goto cleanup;
170
171         if (cg_create(parent))
172                 goto cleanup;
173
174         if (cg_create(child))
175                 goto cleanup;
176
177         if (cg_write(child, "cgroup.type", "threaded"))
178                 goto cleanup;
179
180         if (cg_read_strcmp(parent, "cgroup.type", "domain threaded\n"))
181                 goto cleanup;
182
183         ret = KSFT_PASS;
184
185 cleanup:
186         if (child)
187                 cg_destroy(child);
188         if (parent)
189                 cg_destroy(parent);
190         free(child);
191         free(parent);
192         return ret;
193
194 }
195
196 /*
197  * Test that there's no internal process constrain on threaded cgroups.
198  * You can add threads/processes on a parent with a controller enabled.
199  */
200 static int test_cgcore_no_internal_process_constraint_on_threads(const char *root)
201 {
202         int ret = KSFT_FAIL;
203         char *parent = NULL, *child = NULL;
204
205         if (cg_read_strstr(root, "cgroup.controllers", "cpu") ||
206             cg_write(root, "cgroup.subtree_control", "+cpu")) {
207                 ret = KSFT_SKIP;
208                 goto cleanup;
209         }
210
211         parent = cg_name(root, "cg_test_parent");
212         child = cg_name(root, "cg_test_parent/cg_test_child");
213         if (!parent || !child)
214                 goto cleanup;
215
216         if (cg_create(parent))
217                 goto cleanup;
218
219         if (cg_create(child))
220                 goto cleanup;
221
222         if (cg_write(parent, "cgroup.type", "threaded"))
223                 goto cleanup;
224
225         if (cg_write(child, "cgroup.type", "threaded"))
226                 goto cleanup;
227
228         if (cg_write(parent, "cgroup.subtree_control", "+cpu"))
229                 goto cleanup;
230
231         if (cg_enter_current(parent))
232                 goto cleanup;
233
234         ret = KSFT_PASS;
235
236 cleanup:
237         cg_enter_current(root);
238         cg_enter_current(root);
239         if (child)
240                 cg_destroy(child);
241         if (parent)
242                 cg_destroy(parent);
243         free(child);
244         free(parent);
245         return ret;
246 }
247
248 /*
249  * Test that you can't enable a controller on a child if it's not enabled
250  * on the parent.
251  */
252 static int test_cgcore_top_down_constraint_enable(const char *root)
253 {
254         int ret = KSFT_FAIL;
255         char *parent = NULL, *child = NULL;
256
257         parent = cg_name(root, "cg_test_parent");
258         child = cg_name(root, "cg_test_parent/cg_test_child");
259         if (!parent || !child)
260                 goto cleanup;
261
262         if (cg_create(parent))
263                 goto cleanup;
264
265         if (cg_create(child))
266                 goto cleanup;
267
268         if (!cg_write(child, "cgroup.subtree_control", "+memory"))
269                 goto cleanup;
270
271         ret = KSFT_PASS;
272
273 cleanup:
274         if (child)
275                 cg_destroy(child);
276         if (parent)
277                 cg_destroy(parent);
278         free(child);
279         free(parent);
280         return ret;
281 }
282
283 /*
284  * Test that you can't disable a controller on a parent
285  * if it's enabled in a child.
286  */
287 static int test_cgcore_top_down_constraint_disable(const char *root)
288 {
289         int ret = KSFT_FAIL;
290         char *parent = NULL, *child = NULL;
291
292         parent = cg_name(root, "cg_test_parent");
293         child = cg_name(root, "cg_test_parent/cg_test_child");
294         if (!parent || !child)
295                 goto cleanup;
296
297         if (cg_create(parent))
298                 goto cleanup;
299
300         if (cg_create(child))
301                 goto cleanup;
302
303         if (cg_write(parent, "cgroup.subtree_control", "+memory"))
304                 goto cleanup;
305
306         if (cg_write(child, "cgroup.subtree_control", "+memory"))
307                 goto cleanup;
308
309         if (!cg_write(parent, "cgroup.subtree_control", "-memory"))
310                 goto cleanup;
311
312         ret = KSFT_PASS;
313
314 cleanup:
315         if (child)
316                 cg_destroy(child);
317         if (parent)
318                 cg_destroy(parent);
319         free(child);
320         free(parent);
321         return ret;
322 }
323
324 /*
325  * Test internal process constraint.
326  * You can't add a pid to a domain parent if a controller is enabled.
327  */
328 static int test_cgcore_internal_process_constraint(const char *root)
329 {
330         int ret = KSFT_FAIL;
331         char *parent = NULL, *child = NULL;
332
333         parent = cg_name(root, "cg_test_parent");
334         child = cg_name(root, "cg_test_parent/cg_test_child");
335         if (!parent || !child)
336                 goto cleanup;
337
338         if (cg_create(parent))
339                 goto cleanup;
340
341         if (cg_create(child))
342                 goto cleanup;
343
344         if (cg_write(parent, "cgroup.subtree_control", "+memory"))
345                 goto cleanup;
346
347         if (!cg_enter_current(parent))
348                 goto cleanup;
349
350         ret = KSFT_PASS;
351
352 cleanup:
353         if (child)
354                 cg_destroy(child);
355         if (parent)
356                 cg_destroy(parent);
357         free(child);
358         free(parent);
359         return ret;
360 }
361
362 /*
363  * cgroup migration permission check should be performed based on the
364  * credentials at the time of open instead of write.
365  */
366 static int test_cgcore_lesser_euid_open(const char *root)
367 {
368         const uid_t test_euid = 65534;  /* usually nobody, any !root is fine */
369         int ret = KSFT_FAIL;
370         char *cg_test_a = NULL, *cg_test_b = NULL;
371         char *cg_test_a_procs = NULL, *cg_test_b_procs = NULL;
372         int cg_test_b_procs_fd = -1;
373         uid_t saved_uid;
374
375         cg_test_a = cg_name(root, "cg_test_a");
376         cg_test_b = cg_name(root, "cg_test_b");
377
378         if (!cg_test_a || !cg_test_b)
379                 goto cleanup;
380
381         cg_test_a_procs = cg_name(cg_test_a, "cgroup.procs");
382         cg_test_b_procs = cg_name(cg_test_b, "cgroup.procs");
383
384         if (!cg_test_a_procs || !cg_test_b_procs)
385                 goto cleanup;
386
387         if (cg_create(cg_test_a) || cg_create(cg_test_b))
388                 goto cleanup;
389
390         if (cg_enter_current(cg_test_a))
391                 goto cleanup;
392
393         if (chown(cg_test_a_procs, test_euid, -1) ||
394             chown(cg_test_b_procs, test_euid, -1))
395                 goto cleanup;
396
397         saved_uid = geteuid();
398         if (seteuid(test_euid))
399                 goto cleanup;
400
401         cg_test_b_procs_fd = open(cg_test_b_procs, O_RDWR);
402
403         if (seteuid(saved_uid))
404                 goto cleanup;
405
406         if (cg_test_b_procs_fd < 0)
407                 goto cleanup;
408
409         if (write(cg_test_b_procs_fd, "0", 1) >= 0 || errno != EACCES)
410                 goto cleanup;
411
412         ret = KSFT_PASS;
413
414 cleanup:
415         cg_enter_current(root);
416         if (cg_test_b_procs_fd >= 0)
417                 close(cg_test_b_procs_fd);
418         if (cg_test_b)
419                 cg_destroy(cg_test_b);
420         if (cg_test_a)
421                 cg_destroy(cg_test_a);
422         free(cg_test_b_procs);
423         free(cg_test_a_procs);
424         free(cg_test_b);
425         free(cg_test_a);
426         return ret;
427 }
428
429 struct lesser_ns_open_thread_arg {
430         const char      *path;
431         int             fd;
432         int             err;
433 };
434
435 static int lesser_ns_open_thread_fn(void *arg)
436 {
437         struct lesser_ns_open_thread_arg *targ = arg;
438
439         targ->fd = open(targ->path, O_RDWR);
440         targ->err = errno;
441         return 0;
442 }
443
444 /*
445  * cgroup migration permission check should be performed based on the cgroup
446  * namespace at the time of open instead of write.
447  */
448 static int test_cgcore_lesser_ns_open(const char *root)
449 {
450         static char stack[65536];
451         const uid_t test_euid = 65534;  /* usually nobody, any !root is fine */
452         int ret = KSFT_FAIL;
453         char *cg_test_a = NULL, *cg_test_b = NULL;
454         char *cg_test_a_procs = NULL, *cg_test_b_procs = NULL;
455         int cg_test_b_procs_fd = -1;
456         struct lesser_ns_open_thread_arg targ = { .fd = -1 };
457         pid_t pid;
458         int status;
459
460         cg_test_a = cg_name(root, "cg_test_a");
461         cg_test_b = cg_name(root, "cg_test_b");
462
463         if (!cg_test_a || !cg_test_b)
464                 goto cleanup;
465
466         cg_test_a_procs = cg_name(cg_test_a, "cgroup.procs");
467         cg_test_b_procs = cg_name(cg_test_b, "cgroup.procs");
468
469         if (!cg_test_a_procs || !cg_test_b_procs)
470                 goto cleanup;
471
472         if (cg_create(cg_test_a) || cg_create(cg_test_b))
473                 goto cleanup;
474
475         if (cg_enter_current(cg_test_b))
476                 goto cleanup;
477
478         if (chown(cg_test_a_procs, test_euid, -1) ||
479             chown(cg_test_b_procs, test_euid, -1))
480                 goto cleanup;
481
482         targ.path = cg_test_b_procs;
483         pid = clone(lesser_ns_open_thread_fn, stack + sizeof(stack),
484                     CLONE_NEWCGROUP | CLONE_FILES | CLONE_VM | SIGCHLD,
485                     &targ);
486         if (pid < 0)
487                 goto cleanup;
488
489         if (waitpid(pid, &status, 0) < 0)
490                 goto cleanup;
491
492         if (!WIFEXITED(status))
493                 goto cleanup;
494
495         cg_test_b_procs_fd = targ.fd;
496         if (cg_test_b_procs_fd < 0)
497                 goto cleanup;
498
499         if (cg_enter_current(cg_test_a))
500                 goto cleanup;
501
502         if ((status = write(cg_test_b_procs_fd, "0", 1)) >= 0 || errno != ENOENT)
503                 goto cleanup;
504
505         ret = KSFT_PASS;
506
507 cleanup:
508         cg_enter_current(root);
509         if (cg_test_b_procs_fd >= 0)
510                 close(cg_test_b_procs_fd);
511         if (cg_test_b)
512                 cg_destroy(cg_test_b);
513         if (cg_test_a)
514                 cg_destroy(cg_test_a);
515         free(cg_test_b_procs);
516         free(cg_test_a_procs);
517         free(cg_test_b);
518         free(cg_test_a);
519         return ret;
520 }
521
522 #define T(x) { x, #x }
523 struct corecg_test {
524         int (*fn)(const char *root);
525         const char *name;
526 } tests[] = {
527         T(test_cgcore_internal_process_constraint),
528         T(test_cgcore_top_down_constraint_enable),
529         T(test_cgcore_top_down_constraint_disable),
530         T(test_cgcore_no_internal_process_constraint_on_threads),
531         T(test_cgcore_parent_becomes_threaded),
532         T(test_cgcore_invalid_domain),
533         T(test_cgcore_populated),
534         T(test_cgcore_lesser_euid_open),
535         T(test_cgcore_lesser_ns_open),
536 };
537 #undef T
538
539 int main(int argc, char *argv[])
540 {
541         char root[PATH_MAX];
542         int i, ret = EXIT_SUCCESS;
543
544         if (cg_find_unified_root(root, sizeof(root)))
545                 ksft_exit_skip("cgroup v2 isn't mounted\n");
546
547         if (cg_read_strstr(root, "cgroup.subtree_control", "memory"))
548                 if (cg_write(root, "cgroup.subtree_control", "+memory"))
549                         ksft_exit_skip("Failed to set memory controller\n");
550
551         for (i = 0; i < ARRAY_SIZE(tests); i++) {
552                 switch (tests[i].fn(root)) {
553                 case KSFT_PASS:
554                         ksft_test_result_pass("%s\n", tests[i].name);
555                         break;
556                 case KSFT_SKIP:
557                         ksft_test_result_skip("%s\n", tests[i].name);
558                         break;
559                 default:
560                         ret = EXIT_FAILURE;
561                         ksft_test_result_fail("%s\n", tests[i].name);
562                         break;
563                 }
564         }
565
566         return ret;
567 }