Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/tools/testing/selftests/cgroup/test_cpuset.c
26285 views
1
// SPDX-License-Identifier: GPL-2.0
2
3
#include <linux/limits.h>
4
#include <signal.h>
5
6
#include "../kselftest.h"
7
#include "cgroup_util.h"
8
9
static int idle_process_fn(const char *cgroup, void *arg)
10
{
11
(void)pause();
12
return 0;
13
}
14
15
static int do_migration_fn(const char *cgroup, void *arg)
16
{
17
int object_pid = (int)(size_t)arg;
18
19
if (setuid(TEST_UID))
20
return EXIT_FAILURE;
21
22
// XXX checking /proc/$pid/cgroup would be quicker than wait
23
if (cg_enter(cgroup, object_pid) ||
24
cg_wait_for_proc_count(cgroup, 1))
25
return EXIT_FAILURE;
26
27
return EXIT_SUCCESS;
28
}
29
30
static int do_controller_fn(const char *cgroup, void *arg)
31
{
32
const char *child = cgroup;
33
const char *parent = arg;
34
35
if (setuid(TEST_UID))
36
return EXIT_FAILURE;
37
38
if (!cg_read_strstr(child, "cgroup.controllers", "cpuset"))
39
return EXIT_FAILURE;
40
41
if (cg_write(parent, "cgroup.subtree_control", "+cpuset"))
42
return EXIT_FAILURE;
43
44
if (cg_read_strstr(child, "cgroup.controllers", "cpuset"))
45
return EXIT_FAILURE;
46
47
if (cg_write(parent, "cgroup.subtree_control", "-cpuset"))
48
return EXIT_FAILURE;
49
50
if (!cg_read_strstr(child, "cgroup.controllers", "cpuset"))
51
return EXIT_FAILURE;
52
53
return EXIT_SUCCESS;
54
}
55
56
/*
57
* Migrate a process between two sibling cgroups.
58
* The success should only depend on the parent cgroup permissions and not the
59
* migrated process itself (cpuset controller is in place because it uses
60
* security_task_setscheduler() in cgroup v1).
61
*
62
* Deliberately don't set cpuset.cpus in children to avoid definining migration
63
* permissions between two different cpusets.
64
*/
65
static int test_cpuset_perms_object(const char *root, bool allow)
66
{
67
char *parent = NULL, *child_src = NULL, *child_dst = NULL;
68
char *parent_procs = NULL, *child_src_procs = NULL, *child_dst_procs = NULL;
69
const uid_t test_euid = TEST_UID;
70
int object_pid = 0;
71
int ret = KSFT_FAIL;
72
73
parent = cg_name(root, "cpuset_test_0");
74
if (!parent)
75
goto cleanup;
76
parent_procs = cg_name(parent, "cgroup.procs");
77
if (!parent_procs)
78
goto cleanup;
79
if (cg_create(parent))
80
goto cleanup;
81
82
child_src = cg_name(parent, "cpuset_test_1");
83
if (!child_src)
84
goto cleanup;
85
child_src_procs = cg_name(child_src, "cgroup.procs");
86
if (!child_src_procs)
87
goto cleanup;
88
if (cg_create(child_src))
89
goto cleanup;
90
91
child_dst = cg_name(parent, "cpuset_test_2");
92
if (!child_dst)
93
goto cleanup;
94
child_dst_procs = cg_name(child_dst, "cgroup.procs");
95
if (!child_dst_procs)
96
goto cleanup;
97
if (cg_create(child_dst))
98
goto cleanup;
99
100
if (cg_write(parent, "cgroup.subtree_control", "+cpuset"))
101
goto cleanup;
102
103
if (cg_read_strstr(child_src, "cgroup.controllers", "cpuset") ||
104
cg_read_strstr(child_dst, "cgroup.controllers", "cpuset"))
105
goto cleanup;
106
107
/* Enable permissions along src->dst tree path */
108
if (chown(child_src_procs, test_euid, -1) ||
109
chown(child_dst_procs, test_euid, -1))
110
goto cleanup;
111
112
if (allow && chown(parent_procs, test_euid, -1))
113
goto cleanup;
114
115
/* Fork a privileged child as a test object */
116
object_pid = cg_run_nowait(child_src, idle_process_fn, NULL);
117
if (object_pid < 0)
118
goto cleanup;
119
120
/* Carry out migration in a child process that can drop all privileges
121
* (including capabilities), the main process must remain privileged for
122
* cleanup.
123
* Child process's cgroup is irrelevant but we place it into child_dst
124
* as hacky way to pass information about migration target to the child.
125
*/
126
if (allow ^ (cg_run(child_dst, do_migration_fn, (void *)(size_t)object_pid) == EXIT_SUCCESS))
127
goto cleanup;
128
129
ret = KSFT_PASS;
130
131
cleanup:
132
if (object_pid > 0) {
133
(void)kill(object_pid, SIGTERM);
134
(void)clone_reap(object_pid, WEXITED);
135
}
136
137
cg_destroy(child_dst);
138
free(child_dst_procs);
139
free(child_dst);
140
141
cg_destroy(child_src);
142
free(child_src_procs);
143
free(child_src);
144
145
cg_destroy(parent);
146
free(parent_procs);
147
free(parent);
148
149
return ret;
150
}
151
152
static int test_cpuset_perms_object_allow(const char *root)
153
{
154
return test_cpuset_perms_object(root, true);
155
}
156
157
static int test_cpuset_perms_object_deny(const char *root)
158
{
159
return test_cpuset_perms_object(root, false);
160
}
161
162
/*
163
* Migrate a process between parent and child implicitely
164
* Implicit migration happens when a controller is enabled/disabled.
165
*
166
*/
167
static int test_cpuset_perms_subtree(const char *root)
168
{
169
char *parent = NULL, *child = NULL;
170
char *parent_procs = NULL, *parent_subctl = NULL, *child_procs = NULL;
171
const uid_t test_euid = TEST_UID;
172
int object_pid = 0;
173
int ret = KSFT_FAIL;
174
175
parent = cg_name(root, "cpuset_test_0");
176
if (!parent)
177
goto cleanup;
178
parent_procs = cg_name(parent, "cgroup.procs");
179
if (!parent_procs)
180
goto cleanup;
181
parent_subctl = cg_name(parent, "cgroup.subtree_control");
182
if (!parent_subctl)
183
goto cleanup;
184
if (cg_create(parent))
185
goto cleanup;
186
187
child = cg_name(parent, "cpuset_test_1");
188
if (!child)
189
goto cleanup;
190
child_procs = cg_name(child, "cgroup.procs");
191
if (!child_procs)
192
goto cleanup;
193
if (cg_create(child))
194
goto cleanup;
195
196
/* Enable permissions as in a delegated subtree */
197
if (chown(parent_procs, test_euid, -1) ||
198
chown(parent_subctl, test_euid, -1) ||
199
chown(child_procs, test_euid, -1))
200
goto cleanup;
201
202
/* Put a privileged child in the subtree and modify controller state
203
* from an unprivileged process, the main process remains privileged
204
* for cleanup.
205
* The unprivileged child runs in subtree too to avoid parent and
206
* internal-node constraing violation.
207
*/
208
object_pid = cg_run_nowait(child, idle_process_fn, NULL);
209
if (object_pid < 0)
210
goto cleanup;
211
212
if (cg_run(child, do_controller_fn, parent) != EXIT_SUCCESS)
213
goto cleanup;
214
215
ret = KSFT_PASS;
216
217
cleanup:
218
if (object_pid > 0) {
219
(void)kill(object_pid, SIGTERM);
220
(void)clone_reap(object_pid, WEXITED);
221
}
222
223
cg_destroy(child);
224
free(child_procs);
225
free(child);
226
227
cg_destroy(parent);
228
free(parent_subctl);
229
free(parent_procs);
230
free(parent);
231
232
return ret;
233
}
234
235
236
#define T(x) { x, #x }
237
struct cpuset_test {
238
int (*fn)(const char *root);
239
const char *name;
240
} tests[] = {
241
T(test_cpuset_perms_object_allow),
242
T(test_cpuset_perms_object_deny),
243
T(test_cpuset_perms_subtree),
244
};
245
#undef T
246
247
int main(int argc, char *argv[])
248
{
249
char root[PATH_MAX];
250
int i, ret = EXIT_SUCCESS;
251
252
if (cg_find_unified_root(root, sizeof(root), NULL))
253
ksft_exit_skip("cgroup v2 isn't mounted\n");
254
255
if (cg_read_strstr(root, "cgroup.subtree_control", "cpuset"))
256
if (cg_write(root, "cgroup.subtree_control", "+cpuset"))
257
ksft_exit_skip("Failed to set cpuset controller\n");
258
259
for (i = 0; i < ARRAY_SIZE(tests); i++) {
260
switch (tests[i].fn(root)) {
261
case KSFT_PASS:
262
ksft_test_result_pass("%s\n", tests[i].name);
263
break;
264
case KSFT_SKIP:
265
ksft_test_result_skip("%s\n", tests[i].name);
266
break;
267
default:
268
ret = EXIT_FAILURE;
269
ksft_test_result_fail("%s\n", tests[i].name);
270
break;
271
}
272
}
273
274
return ret;
275
}
276
277