hc
2023-12-11 d2ccde1c8e90d38cee87a1b0309ad2827f3fd30d
kernel/tools/testing/selftests/cgroup/test_core.c
....@@ -1,13 +1,131 @@
11 /* SPDX-License-Identifier: GPL-2.0 */
22
3
+#define _GNU_SOURCE
34 #include <linux/limits.h>
5
+#include <linux/sched.h>
46 #include <sys/types.h>
7
+#include <sys/mman.h>
8
+#include <sys/wait.h>
59 #include <unistd.h>
10
+#include <fcntl.h>
11
+#include <sched.h>
612 #include <stdio.h>
713 #include <errno.h>
14
+#include <signal.h>
15
+#include <string.h>
16
+#include <pthread.h>
817
918 #include "../kselftest.h"
1019 #include "cgroup_util.h"
20
+
21
+static int touch_anon(char *buf, size_t size)
22
+{
23
+ int fd;
24
+ char *pos = buf;
25
+
26
+ fd = open("/dev/urandom", O_RDONLY);
27
+ if (fd < 0)
28
+ return -1;
29
+
30
+ while (size > 0) {
31
+ ssize_t ret = read(fd, pos, size);
32
+
33
+ if (ret < 0) {
34
+ if (errno != EINTR) {
35
+ close(fd);
36
+ return -1;
37
+ }
38
+ } else {
39
+ pos += ret;
40
+ size -= ret;
41
+ }
42
+ }
43
+ close(fd);
44
+
45
+ return 0;
46
+}
47
+
48
+static int alloc_and_touch_anon_noexit(const char *cgroup, void *arg)
49
+{
50
+ int ppid = getppid();
51
+ size_t size = (size_t)arg;
52
+ void *buf;
53
+
54
+ buf = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON,
55
+ 0, 0);
56
+ if (buf == MAP_FAILED)
57
+ return -1;
58
+
59
+ if (touch_anon((char *)buf, size)) {
60
+ munmap(buf, size);
61
+ return -1;
62
+ }
63
+
64
+ while (getppid() == ppid)
65
+ sleep(1);
66
+
67
+ munmap(buf, size);
68
+ return 0;
69
+}
70
+
71
+/*
72
+ * Create a child process that allocates and touches 100MB, then waits to be
73
+ * killed. Wait until the child is attached to the cgroup, kill all processes
74
+ * in that cgroup and wait until "cgroup.procs" is empty. At this point try to
75
+ * destroy the empty cgroup. The test helps detect race conditions between
76
+ * dying processes leaving the cgroup and cgroup destruction path.
77
+ */
78
+static int test_cgcore_destroy(const char *root)
79
+{
80
+ int ret = KSFT_FAIL;
81
+ char *cg_test = NULL;
82
+ int child_pid;
83
+ char buf[PAGE_SIZE];
84
+
85
+ cg_test = cg_name(root, "cg_test");
86
+
87
+ if (!cg_test)
88
+ goto cleanup;
89
+
90
+ for (int i = 0; i < 10; i++) {
91
+ if (cg_create(cg_test))
92
+ goto cleanup;
93
+
94
+ child_pid = cg_run_nowait(cg_test, alloc_and_touch_anon_noexit,
95
+ (void *) MB(100));
96
+
97
+ if (child_pid < 0)
98
+ goto cleanup;
99
+
100
+ /* wait for the child to enter cgroup */
101
+ if (cg_wait_for_proc_count(cg_test, 1))
102
+ goto cleanup;
103
+
104
+ if (cg_killall(cg_test))
105
+ goto cleanup;
106
+
107
+ /* wait for cgroup to be empty */
108
+ while (1) {
109
+ if (cg_read(cg_test, "cgroup.procs", buf, sizeof(buf)))
110
+ goto cleanup;
111
+ if (buf[0] == '\0')
112
+ break;
113
+ usleep(1000);
114
+ }
115
+
116
+ if (rmdir(cg_test))
117
+ goto cleanup;
118
+
119
+ if (waitpid(child_pid, NULL, 0) < 0)
120
+ goto cleanup;
121
+ }
122
+ ret = KSFT_PASS;
123
+cleanup:
124
+ if (cg_test)
125
+ cg_destroy(cg_test);
126
+ free(cg_test);
127
+ return ret;
128
+}
11129
12130 /*
13131 * A(0) - B(0) - C(1)
....@@ -22,8 +140,11 @@
22140 static int test_cgcore_populated(const char *root)
23141 {
24142 int ret = KSFT_FAIL;
143
+ int err;
25144 char *cg_test_a = NULL, *cg_test_b = NULL;
26145 char *cg_test_c = NULL, *cg_test_d = NULL;
146
+ int cgroup_fd = -EBADF;
147
+ pid_t pid;
27148
28149 cg_test_a = cg_name(root, "cg_test_a");
29150 cg_test_b = cg_name(root, "cg_test_a/cg_test_b");
....@@ -75,6 +196,52 @@
75196 if (cg_read_strcmp(cg_test_d, "cgroup.events", "populated 0\n"))
76197 goto cleanup;
77198
199
+ /* Test that we can directly clone into a new cgroup. */
200
+ cgroup_fd = dirfd_open_opath(cg_test_d);
201
+ if (cgroup_fd < 0)
202
+ goto cleanup;
203
+
204
+ pid = clone_into_cgroup(cgroup_fd);
205
+ if (pid < 0) {
206
+ if (errno == ENOSYS)
207
+ goto cleanup_pass;
208
+ goto cleanup;
209
+ }
210
+
211
+ if (pid == 0) {
212
+ if (raise(SIGSTOP))
213
+ exit(EXIT_FAILURE);
214
+ exit(EXIT_SUCCESS);
215
+ }
216
+
217
+ err = cg_read_strcmp(cg_test_d, "cgroup.events", "populated 1\n");
218
+
219
+ (void)clone_reap(pid, WSTOPPED);
220
+ (void)kill(pid, SIGCONT);
221
+ (void)clone_reap(pid, WEXITED);
222
+
223
+ if (err)
224
+ goto cleanup;
225
+
226
+ if (cg_read_strcmp(cg_test_d, "cgroup.events", "populated 0\n"))
227
+ goto cleanup;
228
+
229
+ /* Remove cgroup. */
230
+ if (cg_test_d) {
231
+ cg_destroy(cg_test_d);
232
+ free(cg_test_d);
233
+ cg_test_d = NULL;
234
+ }
235
+
236
+ pid = clone_into_cgroup(cgroup_fd);
237
+ if (pid < 0)
238
+ goto cleanup_pass;
239
+ if (pid == 0)
240
+ exit(EXIT_SUCCESS);
241
+ (void)clone_reap(pid, WEXITED);
242
+ goto cleanup;
243
+
244
+cleanup_pass:
78245 ret = KSFT_PASS;
79246
80247 cleanup:
....@@ -90,6 +257,8 @@
90257 free(cg_test_c);
91258 free(cg_test_b);
92259 free(cg_test_a);
260
+ if (cgroup_fd >= 0)
261
+ close(cgroup_fd);
93262 return ret;
94263 }
95264
....@@ -133,6 +302,16 @@
133302 if (errno != EOPNOTSUPP)
134303 goto cleanup;
135304
305
+ if (!clone_into_cgroup_run_wait(child))
306
+ goto cleanup;
307
+
308
+ if (errno == ENOSYS)
309
+ goto cleanup_pass;
310
+
311
+ if (errno != EOPNOTSUPP)
312
+ goto cleanup;
313
+
314
+cleanup_pass:
136315 ret = KSFT_PASS;
137316
138317 cleanup:
....@@ -342,6 +521,9 @@
342521 if (!cg_enter_current(parent))
343522 goto cleanup;
344523
524
+ if (!clone_into_cgroup_run_wait(parent))
525
+ goto cleanup;
526
+
345527 ret = KSFT_PASS;
346528
347529 cleanup:
....@@ -351,6 +533,307 @@
351533 cg_destroy(parent);
352534 free(child);
353535 free(parent);
536
+ return ret;
537
+}
538
+
539
+static void *dummy_thread_fn(void *arg)
540
+{
541
+ return (void *)(size_t)pause();
542
+}
543
+
544
+/*
545
+ * Test threadgroup migration.
546
+ * All threads of a process are migrated together.
547
+ */
548
+static int test_cgcore_proc_migration(const char *root)
549
+{
550
+ int ret = KSFT_FAIL;
551
+ int t, c_threads = 0, n_threads = 13;
552
+ char *src = NULL, *dst = NULL;
553
+ pthread_t threads[n_threads];
554
+
555
+ src = cg_name(root, "cg_src");
556
+ dst = cg_name(root, "cg_dst");
557
+ if (!src || !dst)
558
+ goto cleanup;
559
+
560
+ if (cg_create(src))
561
+ goto cleanup;
562
+ if (cg_create(dst))
563
+ goto cleanup;
564
+
565
+ if (cg_enter_current(src))
566
+ goto cleanup;
567
+
568
+ for (c_threads = 0; c_threads < n_threads; ++c_threads) {
569
+ if (pthread_create(&threads[c_threads], NULL, dummy_thread_fn, NULL))
570
+ goto cleanup;
571
+ }
572
+
573
+ cg_enter_current(dst);
574
+ if (cg_read_lc(dst, "cgroup.threads") != n_threads + 1)
575
+ goto cleanup;
576
+
577
+ ret = KSFT_PASS;
578
+
579
+cleanup:
580
+ for (t = 0; t < c_threads; ++t) {
581
+ pthread_cancel(threads[t]);
582
+ }
583
+
584
+ for (t = 0; t < c_threads; ++t) {
585
+ pthread_join(threads[t], NULL);
586
+ }
587
+
588
+ cg_enter_current(root);
589
+
590
+ if (dst)
591
+ cg_destroy(dst);
592
+ if (src)
593
+ cg_destroy(src);
594
+ free(dst);
595
+ free(src);
596
+ return ret;
597
+}
598
+
599
+static void *migrating_thread_fn(void *arg)
600
+{
601
+ int g, i, n_iterations = 1000;
602
+ char **grps = arg;
603
+ char lines[3][PATH_MAX];
604
+
605
+ for (g = 1; g < 3; ++g)
606
+ snprintf(lines[g], sizeof(lines[g]), "0::%s", grps[g] + strlen(grps[0]));
607
+
608
+ for (i = 0; i < n_iterations; ++i) {
609
+ cg_enter_current_thread(grps[(i % 2) + 1]);
610
+
611
+ if (proc_read_strstr(0, 1, "cgroup", lines[(i % 2) + 1]))
612
+ return (void *)-1;
613
+ }
614
+ return NULL;
615
+}
616
+
617
+/*
618
+ * Test single thread migration.
619
+ * Threaded cgroups allow successful migration of a thread.
620
+ */
621
+static int test_cgcore_thread_migration(const char *root)
622
+{
623
+ int ret = KSFT_FAIL;
624
+ char *dom = NULL;
625
+ char line[PATH_MAX];
626
+ char *grps[3] = { (char *)root, NULL, NULL };
627
+ pthread_t thr;
628
+ void *retval;
629
+
630
+ dom = cg_name(root, "cg_dom");
631
+ grps[1] = cg_name(root, "cg_dom/cg_src");
632
+ grps[2] = cg_name(root, "cg_dom/cg_dst");
633
+ if (!grps[1] || !grps[2] || !dom)
634
+ goto cleanup;
635
+
636
+ if (cg_create(dom))
637
+ goto cleanup;
638
+ if (cg_create(grps[1]))
639
+ goto cleanup;
640
+ if (cg_create(grps[2]))
641
+ goto cleanup;
642
+
643
+ if (cg_write(grps[1], "cgroup.type", "threaded"))
644
+ goto cleanup;
645
+ if (cg_write(grps[2], "cgroup.type", "threaded"))
646
+ goto cleanup;
647
+
648
+ if (cg_enter_current(grps[1]))
649
+ goto cleanup;
650
+
651
+ if (pthread_create(&thr, NULL, migrating_thread_fn, grps))
652
+ goto cleanup;
653
+
654
+ if (pthread_join(thr, &retval))
655
+ goto cleanup;
656
+
657
+ if (retval)
658
+ goto cleanup;
659
+
660
+ snprintf(line, sizeof(line), "0::%s", grps[1] + strlen(grps[0]));
661
+ if (proc_read_strstr(0, 1, "cgroup", line))
662
+ goto cleanup;
663
+
664
+ ret = KSFT_PASS;
665
+
666
+cleanup:
667
+ cg_enter_current(root);
668
+ if (grps[2])
669
+ cg_destroy(grps[2]);
670
+ if (grps[1])
671
+ cg_destroy(grps[1]);
672
+ if (dom)
673
+ cg_destroy(dom);
674
+ free(grps[2]);
675
+ free(grps[1]);
676
+ free(dom);
677
+ return ret;
678
+}
679
+
680
+/*
681
+ * cgroup migration permission check should be performed based on the
682
+ * credentials at the time of open instead of write.
683
+ */
684
+static int test_cgcore_lesser_euid_open(const char *root)
685
+{
686
+ const uid_t test_euid = 65534; /* usually nobody, any !root is fine */
687
+ int ret = KSFT_FAIL;
688
+ char *cg_test_a = NULL, *cg_test_b = NULL;
689
+ char *cg_test_a_procs = NULL, *cg_test_b_procs = NULL;
690
+ int cg_test_b_procs_fd = -1;
691
+ uid_t saved_uid;
692
+
693
+ cg_test_a = cg_name(root, "cg_test_a");
694
+ cg_test_b = cg_name(root, "cg_test_b");
695
+
696
+ if (!cg_test_a || !cg_test_b)
697
+ goto cleanup;
698
+
699
+ cg_test_a_procs = cg_name(cg_test_a, "cgroup.procs");
700
+ cg_test_b_procs = cg_name(cg_test_b, "cgroup.procs");
701
+
702
+ if (!cg_test_a_procs || !cg_test_b_procs)
703
+ goto cleanup;
704
+
705
+ if (cg_create(cg_test_a) || cg_create(cg_test_b))
706
+ goto cleanup;
707
+
708
+ if (cg_enter_current(cg_test_a))
709
+ goto cleanup;
710
+
711
+ if (chown(cg_test_a_procs, test_euid, -1) ||
712
+ chown(cg_test_b_procs, test_euid, -1))
713
+ goto cleanup;
714
+
715
+ saved_uid = geteuid();
716
+ if (seteuid(test_euid))
717
+ goto cleanup;
718
+
719
+ cg_test_b_procs_fd = open(cg_test_b_procs, O_RDWR);
720
+
721
+ if (seteuid(saved_uid))
722
+ goto cleanup;
723
+
724
+ if (cg_test_b_procs_fd < 0)
725
+ goto cleanup;
726
+
727
+ if (write(cg_test_b_procs_fd, "0", 1) >= 0 || errno != EACCES)
728
+ goto cleanup;
729
+
730
+ ret = KSFT_PASS;
731
+
732
+cleanup:
733
+ cg_enter_current(root);
734
+ if (cg_test_b_procs_fd >= 0)
735
+ close(cg_test_b_procs_fd);
736
+ if (cg_test_b)
737
+ cg_destroy(cg_test_b);
738
+ if (cg_test_a)
739
+ cg_destroy(cg_test_a);
740
+ free(cg_test_b_procs);
741
+ free(cg_test_a_procs);
742
+ free(cg_test_b);
743
+ free(cg_test_a);
744
+ return ret;
745
+}
746
+
747
+struct lesser_ns_open_thread_arg {
748
+ const char *path;
749
+ int fd;
750
+ int err;
751
+};
752
+
753
+static int lesser_ns_open_thread_fn(void *arg)
754
+{
755
+ struct lesser_ns_open_thread_arg *targ = arg;
756
+
757
+ targ->fd = open(targ->path, O_RDWR);
758
+ targ->err = errno;
759
+ return 0;
760
+}
761
+
762
+/*
763
+ * cgroup migration permission check should be performed based on the cgroup
764
+ * namespace at the time of open instead of write.
765
+ */
766
+static int test_cgcore_lesser_ns_open(const char *root)
767
+{
768
+ static char stack[65536];
769
+ const uid_t test_euid = 65534; /* usually nobody, any !root is fine */
770
+ int ret = KSFT_FAIL;
771
+ char *cg_test_a = NULL, *cg_test_b = NULL;
772
+ char *cg_test_a_procs = NULL, *cg_test_b_procs = NULL;
773
+ int cg_test_b_procs_fd = -1;
774
+ struct lesser_ns_open_thread_arg targ = { .fd = -1 };
775
+ pid_t pid;
776
+ int status;
777
+
778
+ cg_test_a = cg_name(root, "cg_test_a");
779
+ cg_test_b = cg_name(root, "cg_test_b");
780
+
781
+ if (!cg_test_a || !cg_test_b)
782
+ goto cleanup;
783
+
784
+ cg_test_a_procs = cg_name(cg_test_a, "cgroup.procs");
785
+ cg_test_b_procs = cg_name(cg_test_b, "cgroup.procs");
786
+
787
+ if (!cg_test_a_procs || !cg_test_b_procs)
788
+ goto cleanup;
789
+
790
+ if (cg_create(cg_test_a) || cg_create(cg_test_b))
791
+ goto cleanup;
792
+
793
+ if (cg_enter_current(cg_test_b))
794
+ goto cleanup;
795
+
796
+ if (chown(cg_test_a_procs, test_euid, -1) ||
797
+ chown(cg_test_b_procs, test_euid, -1))
798
+ goto cleanup;
799
+
800
+ targ.path = cg_test_b_procs;
801
+ pid = clone(lesser_ns_open_thread_fn, stack + sizeof(stack),
802
+ CLONE_NEWCGROUP | CLONE_FILES | CLONE_VM | SIGCHLD,
803
+ &targ);
804
+ if (pid < 0)
805
+ goto cleanup;
806
+
807
+ if (waitpid(pid, &status, 0) < 0)
808
+ goto cleanup;
809
+
810
+ if (!WIFEXITED(status))
811
+ goto cleanup;
812
+
813
+ cg_test_b_procs_fd = targ.fd;
814
+ if (cg_test_b_procs_fd < 0)
815
+ goto cleanup;
816
+
817
+ if (cg_enter_current(cg_test_a))
818
+ goto cleanup;
819
+
820
+ if ((status = write(cg_test_b_procs_fd, "0", 1)) >= 0 || errno != ENOENT)
821
+ goto cleanup;
822
+
823
+ ret = KSFT_PASS;
824
+
825
+cleanup:
826
+ cg_enter_current(root);
827
+ if (cg_test_b_procs_fd >= 0)
828
+ close(cg_test_b_procs_fd);
829
+ if (cg_test_b)
830
+ cg_destroy(cg_test_b);
831
+ if (cg_test_a)
832
+ cg_destroy(cg_test_a);
833
+ free(cg_test_b_procs);
834
+ free(cg_test_a_procs);
835
+ free(cg_test_b);
836
+ free(cg_test_a);
354837 return ret;
355838 }
356839
....@@ -366,6 +849,11 @@
366849 T(test_cgcore_parent_becomes_threaded),
367850 T(test_cgcore_invalid_domain),
368851 T(test_cgcore_populated),
852
+ T(test_cgcore_proc_migration),
853
+ T(test_cgcore_thread_migration),
854
+ T(test_cgcore_destroy),
855
+ T(test_cgcore_lesser_euid_open),
856
+ T(test_cgcore_lesser_ns_open),
369857 };
370858 #undef T
371859