1 | /* SPDX-License-Identifier: GPL-2.0 */ |
2 | #include <stdbool.h> |
3 | #include <linux/limits.h> |
4 | #include <sys/ptrace.h> |
5 | #include <sys/types.h> |
6 | #include <sys/mman.h> |
7 | #include <unistd.h> |
8 | #include <stdio.h> |
9 | #include <errno.h> |
10 | #include <stdlib.h> |
11 | #include <string.h> |
12 | #include <sys/wait.h> |
13 | |
14 | #include "../kselftest.h" |
15 | #include "cgroup_util.h" |
16 | |
17 | #define DEBUG |
18 | #ifdef DEBUG |
19 | #define debug(args...) fprintf(stderr, args) |
20 | #else |
21 | #define debug(args...) |
22 | #endif |
23 | |
24 | /* |
25 | * Check if the cgroup is frozen by looking at the cgroup.events::frozen value. |
26 | */ |
27 | static int cg_check_frozen(const char *cgroup, bool frozen) |
28 | { |
29 | if (frozen) { |
30 | if (cg_read_strstr(cgroup, control: "cgroup.events" , needle: "frozen 1" ) != 0) { |
31 | debug("Cgroup %s isn't frozen\n" , cgroup); |
32 | return -1; |
33 | } |
34 | } else { |
35 | /* |
36 | * Check the cgroup.events::frozen value. |
37 | */ |
38 | if (cg_read_strstr(cgroup, control: "cgroup.events" , needle: "frozen 0" ) != 0) { |
39 | debug("Cgroup %s is frozen\n" , cgroup); |
40 | return -1; |
41 | } |
42 | } |
43 | |
44 | return 0; |
45 | } |
46 | |
47 | /* |
48 | * Freeze the given cgroup. |
49 | */ |
50 | static int cg_freeze_nowait(const char *cgroup, bool freeze) |
51 | { |
52 | return cg_write(cgroup, control: "cgroup.freeze" , buf: freeze ? "1" : "0" ); |
53 | } |
54 | |
55 | /* |
56 | * Attach a task to the given cgroup and wait for a cgroup frozen event. |
57 | * All transient events (e.g. populated) are ignored. |
58 | */ |
59 | static int cg_enter_and_wait_for_frozen(const char *cgroup, int pid, |
60 | bool frozen) |
61 | { |
62 | int fd, ret = -1; |
63 | int attempts; |
64 | |
65 | fd = cg_prepare_for_wait(cgroup); |
66 | if (fd < 0) |
67 | return fd; |
68 | |
69 | ret = cg_enter(cgroup, pid); |
70 | if (ret) |
71 | goto out; |
72 | |
73 | for (attempts = 0; attempts < 10; attempts++) { |
74 | ret = cg_wait_for(fd); |
75 | if (ret) |
76 | break; |
77 | |
78 | ret = cg_check_frozen(cgroup, frozen); |
79 | if (ret) |
80 | continue; |
81 | } |
82 | |
83 | out: |
84 | close(fd); |
85 | return ret; |
86 | } |
87 | |
88 | /* |
89 | * Freeze the given cgroup and wait for the inotify signal. |
90 | * If there are no events in 10 seconds, treat this as an error. |
91 | * Then check that the cgroup is in the desired state. |
92 | */ |
93 | static int cg_freeze_wait(const char *cgroup, bool freeze) |
94 | { |
95 | int fd, ret = -1; |
96 | |
97 | fd = cg_prepare_for_wait(cgroup); |
98 | if (fd < 0) |
99 | return fd; |
100 | |
101 | ret = cg_freeze_nowait(cgroup, freeze); |
102 | if (ret) { |
103 | debug("Error: cg_freeze_nowait() failed\n" ); |
104 | goto out; |
105 | } |
106 | |
107 | ret = cg_wait_for(fd); |
108 | if (ret) |
109 | goto out; |
110 | |
111 | ret = cg_check_frozen(cgroup, frozen: freeze); |
112 | out: |
113 | close(fd); |
114 | return ret; |
115 | } |
116 | |
117 | /* |
118 | * A simple process running in a sleep loop until being |
119 | * re-parented. |
120 | */ |
121 | static int child_fn(const char *cgroup, void *arg) |
122 | { |
123 | int ppid = getppid(); |
124 | |
125 | while (getppid() == ppid) |
126 | usleep(1000); |
127 | |
128 | return getppid() == ppid; |
129 | } |
130 | |
131 | /* |
132 | * A simple test for the cgroup freezer: populated the cgroup with 100 |
133 | * running processes and freeze it. Then unfreeze it. Then it kills all |
134 | * processes and destroys the cgroup. |
135 | */ |
136 | static int test_cgfreezer_simple(const char *root) |
137 | { |
138 | int ret = KSFT_FAIL; |
139 | char *cgroup = NULL; |
140 | int i; |
141 | |
142 | cgroup = cg_name(root, name: "cg_test_simple" ); |
143 | if (!cgroup) |
144 | goto cleanup; |
145 | |
146 | if (cg_create(cgroup)) |
147 | goto cleanup; |
148 | |
149 | for (i = 0; i < 100; i++) |
150 | cg_run_nowait(cgroup, fn: child_fn, NULL); |
151 | |
152 | if (cg_wait_for_proc_count(cgroup, count: 100)) |
153 | goto cleanup; |
154 | |
155 | if (cg_check_frozen(cgroup, frozen: false)) |
156 | goto cleanup; |
157 | |
158 | if (cg_freeze_wait(cgroup, freeze: true)) |
159 | goto cleanup; |
160 | |
161 | if (cg_freeze_wait(cgroup, freeze: false)) |
162 | goto cleanup; |
163 | |
164 | ret = KSFT_PASS; |
165 | |
166 | cleanup: |
167 | if (cgroup) |
168 | cg_destroy(cgroup); |
169 | free(cgroup); |
170 | return ret; |
171 | } |
172 | |
173 | /* |
174 | * The test creates the following hierarchy: |
175 | * A |
176 | * / / \ \ |
177 | * B E I K |
178 | * /\ | |
179 | * C D F |
180 | * | |
181 | * G |
182 | * | |
183 | * H |
184 | * |
185 | * with a process in C, H and 3 processes in K. |
186 | * Then it tries to freeze and unfreeze the whole tree. |
187 | */ |
188 | static int test_cgfreezer_tree(const char *root) |
189 | { |
190 | char *cgroup[10] = {0}; |
191 | int ret = KSFT_FAIL; |
192 | int i; |
193 | |
194 | cgroup[0] = cg_name(root, name: "cg_test_tree_A" ); |
195 | if (!cgroup[0]) |
196 | goto cleanup; |
197 | |
198 | cgroup[1] = cg_name(root: cgroup[0], name: "B" ); |
199 | if (!cgroup[1]) |
200 | goto cleanup; |
201 | |
202 | cgroup[2] = cg_name(root: cgroup[1], name: "C" ); |
203 | if (!cgroup[2]) |
204 | goto cleanup; |
205 | |
206 | cgroup[3] = cg_name(root: cgroup[1], name: "D" ); |
207 | if (!cgroup[3]) |
208 | goto cleanup; |
209 | |
210 | cgroup[4] = cg_name(root: cgroup[0], name: "E" ); |
211 | if (!cgroup[4]) |
212 | goto cleanup; |
213 | |
214 | cgroup[5] = cg_name(root: cgroup[4], name: "F" ); |
215 | if (!cgroup[5]) |
216 | goto cleanup; |
217 | |
218 | cgroup[6] = cg_name(root: cgroup[5], name: "G" ); |
219 | if (!cgroup[6]) |
220 | goto cleanup; |
221 | |
222 | cgroup[7] = cg_name(root: cgroup[6], name: "H" ); |
223 | if (!cgroup[7]) |
224 | goto cleanup; |
225 | |
226 | cgroup[8] = cg_name(root: cgroup[0], name: "I" ); |
227 | if (!cgroup[8]) |
228 | goto cleanup; |
229 | |
230 | cgroup[9] = cg_name(root: cgroup[0], name: "K" ); |
231 | if (!cgroup[9]) |
232 | goto cleanup; |
233 | |
234 | for (i = 0; i < 10; i++) |
235 | if (cg_create(cgroup: cgroup[i])) |
236 | goto cleanup; |
237 | |
238 | cg_run_nowait(cgroup: cgroup[2], fn: child_fn, NULL); |
239 | cg_run_nowait(cgroup: cgroup[7], fn: child_fn, NULL); |
240 | cg_run_nowait(cgroup: cgroup[9], fn: child_fn, NULL); |
241 | cg_run_nowait(cgroup: cgroup[9], fn: child_fn, NULL); |
242 | cg_run_nowait(cgroup: cgroup[9], fn: child_fn, NULL); |
243 | |
244 | /* |
245 | * Wait until all child processes will enter |
246 | * corresponding cgroups. |
247 | */ |
248 | |
249 | if (cg_wait_for_proc_count(cgroup: cgroup[2], count: 1) || |
250 | cg_wait_for_proc_count(cgroup: cgroup[7], count: 1) || |
251 | cg_wait_for_proc_count(cgroup: cgroup[9], count: 3)) |
252 | goto cleanup; |
253 | |
254 | /* |
255 | * Freeze B. |
256 | */ |
257 | if (cg_freeze_wait(cgroup: cgroup[1], freeze: true)) |
258 | goto cleanup; |
259 | |
260 | /* |
261 | * Freeze F. |
262 | */ |
263 | if (cg_freeze_wait(cgroup: cgroup[5], freeze: true)) |
264 | goto cleanup; |
265 | |
266 | /* |
267 | * Freeze G. |
268 | */ |
269 | if (cg_freeze_wait(cgroup: cgroup[6], freeze: true)) |
270 | goto cleanup; |
271 | |
272 | /* |
273 | * Check that A and E are not frozen. |
274 | */ |
275 | if (cg_check_frozen(cgroup: cgroup[0], frozen: false)) |
276 | goto cleanup; |
277 | |
278 | if (cg_check_frozen(cgroup: cgroup[4], frozen: false)) |
279 | goto cleanup; |
280 | |
281 | /* |
282 | * Freeze A. Check that A, B and E are frozen. |
283 | */ |
284 | if (cg_freeze_wait(cgroup: cgroup[0], freeze: true)) |
285 | goto cleanup; |
286 | |
287 | if (cg_check_frozen(cgroup: cgroup[1], frozen: true)) |
288 | goto cleanup; |
289 | |
290 | if (cg_check_frozen(cgroup: cgroup[4], frozen: true)) |
291 | goto cleanup; |
292 | |
293 | /* |
294 | * Unfreeze B, F and G |
295 | */ |
296 | if (cg_freeze_nowait(cgroup: cgroup[1], freeze: false)) |
297 | goto cleanup; |
298 | |
299 | if (cg_freeze_nowait(cgroup: cgroup[5], freeze: false)) |
300 | goto cleanup; |
301 | |
302 | if (cg_freeze_nowait(cgroup: cgroup[6], freeze: false)) |
303 | goto cleanup; |
304 | |
305 | /* |
306 | * Check that C and H are still frozen. |
307 | */ |
308 | if (cg_check_frozen(cgroup: cgroup[2], frozen: true)) |
309 | goto cleanup; |
310 | |
311 | if (cg_check_frozen(cgroup: cgroup[7], frozen: true)) |
312 | goto cleanup; |
313 | |
314 | /* |
315 | * Unfreeze A. Check that A, C and K are not frozen. |
316 | */ |
317 | if (cg_freeze_wait(cgroup: cgroup[0], freeze: false)) |
318 | goto cleanup; |
319 | |
320 | if (cg_check_frozen(cgroup: cgroup[2], frozen: false)) |
321 | goto cleanup; |
322 | |
323 | if (cg_check_frozen(cgroup: cgroup[9], frozen: false)) |
324 | goto cleanup; |
325 | |
326 | ret = KSFT_PASS; |
327 | |
328 | cleanup: |
329 | for (i = 9; i >= 0 && cgroup[i]; i--) { |
330 | cg_destroy(cgroup: cgroup[i]); |
331 | free(cgroup[i]); |
332 | } |
333 | |
334 | return ret; |
335 | } |
336 | |
337 | /* |
338 | * A fork bomb emulator. |
339 | */ |
340 | static int forkbomb_fn(const char *cgroup, void *arg) |
341 | { |
342 | int ppid; |
343 | |
344 | fork(); |
345 | fork(); |
346 | |
347 | ppid = getppid(); |
348 | |
349 | while (getppid() == ppid) |
350 | usleep(1000); |
351 | |
352 | return getppid() == ppid; |
353 | } |
354 | |
355 | /* |
356 | * The test runs a fork bomb in a cgroup and tries to freeze it. |
357 | * Then it kills all processes and checks that cgroup isn't populated |
358 | * anymore. |
359 | */ |
360 | static int test_cgfreezer_forkbomb(const char *root) |
361 | { |
362 | int ret = KSFT_FAIL; |
363 | char *cgroup = NULL; |
364 | |
365 | cgroup = cg_name(root, name: "cg_forkbomb_test" ); |
366 | if (!cgroup) |
367 | goto cleanup; |
368 | |
369 | if (cg_create(cgroup)) |
370 | goto cleanup; |
371 | |
372 | cg_run_nowait(cgroup, fn: forkbomb_fn, NULL); |
373 | |
374 | usleep(100000); |
375 | |
376 | if (cg_freeze_wait(cgroup, freeze: true)) |
377 | goto cleanup; |
378 | |
379 | if (cg_killall(cgroup)) |
380 | goto cleanup; |
381 | |
382 | if (cg_wait_for_proc_count(cgroup, count: 0)) |
383 | goto cleanup; |
384 | |
385 | ret = KSFT_PASS; |
386 | |
387 | cleanup: |
388 | if (cgroup) |
389 | cg_destroy(cgroup); |
390 | free(cgroup); |
391 | return ret; |
392 | } |
393 | |
394 | /* |
395 | * The test creates a cgroups and freezes it. Then it creates a child cgroup |
396 | * and populates it with a task. After that it checks that the child cgroup |
397 | * is frozen and the parent cgroup remains frozen too. |
398 | */ |
399 | static int test_cgfreezer_mkdir(const char *root) |
400 | { |
401 | int ret = KSFT_FAIL; |
402 | char *parent, *child = NULL; |
403 | int pid; |
404 | |
405 | parent = cg_name(root, name: "cg_test_mkdir_A" ); |
406 | if (!parent) |
407 | goto cleanup; |
408 | |
409 | child = cg_name(root: parent, name: "cg_test_mkdir_B" ); |
410 | if (!child) |
411 | goto cleanup; |
412 | |
413 | if (cg_create(cgroup: parent)) |
414 | goto cleanup; |
415 | |
416 | if (cg_freeze_wait(cgroup: parent, freeze: true)) |
417 | goto cleanup; |
418 | |
419 | if (cg_create(cgroup: child)) |
420 | goto cleanup; |
421 | |
422 | pid = cg_run_nowait(cgroup: child, fn: child_fn, NULL); |
423 | if (pid < 0) |
424 | goto cleanup; |
425 | |
426 | if (cg_wait_for_proc_count(cgroup: child, count: 1)) |
427 | goto cleanup; |
428 | |
429 | if (cg_check_frozen(cgroup: child, frozen: true)) |
430 | goto cleanup; |
431 | |
432 | if (cg_check_frozen(cgroup: parent, frozen: true)) |
433 | goto cleanup; |
434 | |
435 | ret = KSFT_PASS; |
436 | |
437 | cleanup: |
438 | if (child) |
439 | cg_destroy(cgroup: child); |
440 | free(child); |
441 | if (parent) |
442 | cg_destroy(cgroup: parent); |
443 | free(parent); |
444 | return ret; |
445 | } |
446 | |
447 | /* |
448 | * The test creates two nested cgroups, freezes the parent |
449 | * and removes the child. Then it checks that the parent cgroup |
450 | * remains frozen and it's possible to create a new child |
451 | * without unfreezing. The new child is frozen too. |
452 | */ |
453 | static int test_cgfreezer_rmdir(const char *root) |
454 | { |
455 | int ret = KSFT_FAIL; |
456 | char *parent, *child = NULL; |
457 | |
458 | parent = cg_name(root, name: "cg_test_rmdir_A" ); |
459 | if (!parent) |
460 | goto cleanup; |
461 | |
462 | child = cg_name(root: parent, name: "cg_test_rmdir_B" ); |
463 | if (!child) |
464 | goto cleanup; |
465 | |
466 | if (cg_create(cgroup: parent)) |
467 | goto cleanup; |
468 | |
469 | if (cg_create(cgroup: child)) |
470 | goto cleanup; |
471 | |
472 | if (cg_freeze_wait(cgroup: parent, freeze: true)) |
473 | goto cleanup; |
474 | |
475 | if (cg_destroy(cgroup: child)) |
476 | goto cleanup; |
477 | |
478 | if (cg_check_frozen(cgroup: parent, frozen: true)) |
479 | goto cleanup; |
480 | |
481 | if (cg_create(cgroup: child)) |
482 | goto cleanup; |
483 | |
484 | if (cg_check_frozen(cgroup: child, frozen: true)) |
485 | goto cleanup; |
486 | |
487 | ret = KSFT_PASS; |
488 | |
489 | cleanup: |
490 | if (child) |
491 | cg_destroy(cgroup: child); |
492 | free(child); |
493 | if (parent) |
494 | cg_destroy(cgroup: parent); |
495 | free(parent); |
496 | return ret; |
497 | } |
498 | |
499 | /* |
500 | * The test creates two cgroups: A and B, runs a process in A |
501 | * and performs several migrations: |
502 | * 1) A (running) -> B (frozen) |
503 | * 2) B (frozen) -> A (running) |
504 | * 3) A (frozen) -> B (frozen) |
505 | * |
506 | * On each step it checks the actual state of both cgroups. |
507 | */ |
508 | static int test_cgfreezer_migrate(const char *root) |
509 | { |
510 | int ret = KSFT_FAIL; |
511 | char *cgroup[2] = {0}; |
512 | int pid; |
513 | |
514 | cgroup[0] = cg_name(root, name: "cg_test_migrate_A" ); |
515 | if (!cgroup[0]) |
516 | goto cleanup; |
517 | |
518 | cgroup[1] = cg_name(root, name: "cg_test_migrate_B" ); |
519 | if (!cgroup[1]) |
520 | goto cleanup; |
521 | |
522 | if (cg_create(cgroup: cgroup[0])) |
523 | goto cleanup; |
524 | |
525 | if (cg_create(cgroup: cgroup[1])) |
526 | goto cleanup; |
527 | |
528 | pid = cg_run_nowait(cgroup: cgroup[0], fn: child_fn, NULL); |
529 | if (pid < 0) |
530 | goto cleanup; |
531 | |
532 | if (cg_wait_for_proc_count(cgroup: cgroup[0], count: 1)) |
533 | goto cleanup; |
534 | |
535 | /* |
536 | * Migrate from A (running) to B (frozen) |
537 | */ |
538 | if (cg_freeze_wait(cgroup: cgroup[1], freeze: true)) |
539 | goto cleanup; |
540 | |
541 | if (cg_enter_and_wait_for_frozen(cgroup: cgroup[1], pid, frozen: true)) |
542 | goto cleanup; |
543 | |
544 | if (cg_check_frozen(cgroup: cgroup[0], frozen: false)) |
545 | goto cleanup; |
546 | |
547 | /* |
548 | * Migrate from B (frozen) to A (running) |
549 | */ |
550 | if (cg_enter_and_wait_for_frozen(cgroup: cgroup[0], pid, frozen: false)) |
551 | goto cleanup; |
552 | |
553 | if (cg_check_frozen(cgroup: cgroup[1], frozen: true)) |
554 | goto cleanup; |
555 | |
556 | /* |
557 | * Migrate from A (frozen) to B (frozen) |
558 | */ |
559 | if (cg_freeze_wait(cgroup: cgroup[0], freeze: true)) |
560 | goto cleanup; |
561 | |
562 | if (cg_enter_and_wait_for_frozen(cgroup: cgroup[1], pid, frozen: true)) |
563 | goto cleanup; |
564 | |
565 | if (cg_check_frozen(cgroup: cgroup[0], frozen: true)) |
566 | goto cleanup; |
567 | |
568 | ret = KSFT_PASS; |
569 | |
570 | cleanup: |
571 | if (cgroup[0]) |
572 | cg_destroy(cgroup: cgroup[0]); |
573 | free(cgroup[0]); |
574 | if (cgroup[1]) |
575 | cg_destroy(cgroup: cgroup[1]); |
576 | free(cgroup[1]); |
577 | return ret; |
578 | } |
579 | |
580 | /* |
581 | * The test checks that ptrace works with a tracing process in a frozen cgroup. |
582 | */ |
583 | static int test_cgfreezer_ptrace(const char *root) |
584 | { |
585 | int ret = KSFT_FAIL; |
586 | char *cgroup = NULL; |
587 | siginfo_t siginfo; |
588 | int pid; |
589 | |
590 | cgroup = cg_name(root, name: "cg_test_ptrace" ); |
591 | if (!cgroup) |
592 | goto cleanup; |
593 | |
594 | if (cg_create(cgroup)) |
595 | goto cleanup; |
596 | |
597 | pid = cg_run_nowait(cgroup, fn: child_fn, NULL); |
598 | if (pid < 0) |
599 | goto cleanup; |
600 | |
601 | if (cg_wait_for_proc_count(cgroup, count: 1)) |
602 | goto cleanup; |
603 | |
604 | if (cg_freeze_wait(cgroup, freeze: true)) |
605 | goto cleanup; |
606 | |
607 | if (ptrace(PTRACE_SEIZE, pid, NULL, NULL)) |
608 | goto cleanup; |
609 | |
610 | if (ptrace(PTRACE_INTERRUPT, pid, NULL, NULL)) |
611 | goto cleanup; |
612 | |
613 | waitpid(pid, NULL, 0); |
614 | |
615 | /* |
616 | * Cgroup has to remain frozen, however the test task |
617 | * is in traced state. |
618 | */ |
619 | if (cg_check_frozen(cgroup, frozen: true)) |
620 | goto cleanup; |
621 | |
622 | if (ptrace(PTRACE_GETSIGINFO, pid, NULL, &siginfo)) |
623 | goto cleanup; |
624 | |
625 | if (ptrace(PTRACE_DETACH, pid, NULL, NULL)) |
626 | goto cleanup; |
627 | |
628 | if (cg_check_frozen(cgroup, frozen: true)) |
629 | goto cleanup; |
630 | |
631 | ret = KSFT_PASS; |
632 | |
633 | cleanup: |
634 | if (cgroup) |
635 | cg_destroy(cgroup); |
636 | free(cgroup); |
637 | return ret; |
638 | } |
639 | |
640 | /* |
641 | * Check if the process is stopped. |
642 | */ |
643 | static int proc_check_stopped(int pid) |
644 | { |
645 | char buf[PAGE_SIZE]; |
646 | int len; |
647 | |
648 | len = proc_read_text(pid, thread: 0, item: "stat" , buf, size: sizeof(buf)); |
649 | if (len == -1) { |
650 | debug("Can't get %d stat\n" , pid); |
651 | return -1; |
652 | } |
653 | |
654 | if (strstr(buf, "(test_freezer) T " ) == NULL) { |
655 | debug("Process %d in the unexpected state: %s\n" , pid, buf); |
656 | return -1; |
657 | } |
658 | |
659 | return 0; |
660 | } |
661 | |
662 | /* |
663 | * Test that it's possible to freeze a cgroup with a stopped process. |
664 | */ |
665 | static int test_cgfreezer_stopped(const char *root) |
666 | { |
667 | int pid, ret = KSFT_FAIL; |
668 | char *cgroup = NULL; |
669 | |
670 | cgroup = cg_name(root, name: "cg_test_stopped" ); |
671 | if (!cgroup) |
672 | goto cleanup; |
673 | |
674 | if (cg_create(cgroup)) |
675 | goto cleanup; |
676 | |
677 | pid = cg_run_nowait(cgroup, fn: child_fn, NULL); |
678 | |
679 | if (cg_wait_for_proc_count(cgroup, count: 1)) |
680 | goto cleanup; |
681 | |
682 | if (kill(pid, SIGSTOP)) |
683 | goto cleanup; |
684 | |
685 | if (cg_check_frozen(cgroup, frozen: false)) |
686 | goto cleanup; |
687 | |
688 | if (cg_freeze_wait(cgroup, freeze: true)) |
689 | goto cleanup; |
690 | |
691 | if (cg_freeze_wait(cgroup, freeze: false)) |
692 | goto cleanup; |
693 | |
694 | if (proc_check_stopped(pid)) |
695 | goto cleanup; |
696 | |
697 | ret = KSFT_PASS; |
698 | |
699 | cleanup: |
700 | if (cgroup) |
701 | cg_destroy(cgroup); |
702 | free(cgroup); |
703 | return ret; |
704 | } |
705 | |
706 | /* |
707 | * Test that it's possible to freeze a cgroup with a ptraced process. |
708 | */ |
709 | static int test_cgfreezer_ptraced(const char *root) |
710 | { |
711 | int pid, ret = KSFT_FAIL; |
712 | char *cgroup = NULL; |
713 | siginfo_t siginfo; |
714 | |
715 | cgroup = cg_name(root, name: "cg_test_ptraced" ); |
716 | if (!cgroup) |
717 | goto cleanup; |
718 | |
719 | if (cg_create(cgroup)) |
720 | goto cleanup; |
721 | |
722 | pid = cg_run_nowait(cgroup, fn: child_fn, NULL); |
723 | |
724 | if (cg_wait_for_proc_count(cgroup, count: 1)) |
725 | goto cleanup; |
726 | |
727 | if (ptrace(PTRACE_SEIZE, pid, NULL, NULL)) |
728 | goto cleanup; |
729 | |
730 | if (ptrace(PTRACE_INTERRUPT, pid, NULL, NULL)) |
731 | goto cleanup; |
732 | |
733 | waitpid(pid, NULL, 0); |
734 | |
735 | if (cg_check_frozen(cgroup, frozen: false)) |
736 | goto cleanup; |
737 | |
738 | if (cg_freeze_wait(cgroup, freeze: true)) |
739 | goto cleanup; |
740 | |
741 | /* |
742 | * cg_check_frozen(cgroup, true) will fail here, |
743 | * because the task is in the TRACEd state. |
744 | */ |
745 | if (cg_freeze_wait(cgroup, freeze: false)) |
746 | goto cleanup; |
747 | |
748 | if (ptrace(PTRACE_GETSIGINFO, pid, NULL, &siginfo)) |
749 | goto cleanup; |
750 | |
751 | if (ptrace(PTRACE_DETACH, pid, NULL, NULL)) |
752 | goto cleanup; |
753 | |
754 | ret = KSFT_PASS; |
755 | |
756 | cleanup: |
757 | if (cgroup) |
758 | cg_destroy(cgroup); |
759 | free(cgroup); |
760 | return ret; |
761 | } |
762 | |
763 | static int vfork_fn(const char *cgroup, void *arg) |
764 | { |
765 | int pid = vfork(); |
766 | |
767 | if (pid == 0) |
768 | while (true) |
769 | sleep(1); |
770 | |
771 | return pid; |
772 | } |
773 | |
774 | /* |
775 | * Test that it's possible to freeze a cgroup with a process, |
776 | * which called vfork() and is waiting for a child. |
777 | */ |
778 | static int test_cgfreezer_vfork(const char *root) |
779 | { |
780 | int ret = KSFT_FAIL; |
781 | char *cgroup = NULL; |
782 | |
783 | cgroup = cg_name(root, name: "cg_test_vfork" ); |
784 | if (!cgroup) |
785 | goto cleanup; |
786 | |
787 | if (cg_create(cgroup)) |
788 | goto cleanup; |
789 | |
790 | cg_run_nowait(cgroup, fn: vfork_fn, NULL); |
791 | |
792 | if (cg_wait_for_proc_count(cgroup, count: 2)) |
793 | goto cleanup; |
794 | |
795 | if (cg_freeze_wait(cgroup, freeze: true)) |
796 | goto cleanup; |
797 | |
798 | ret = KSFT_PASS; |
799 | |
800 | cleanup: |
801 | if (cgroup) |
802 | cg_destroy(cgroup); |
803 | free(cgroup); |
804 | return ret; |
805 | } |
806 | |
807 | #define T(x) { x, #x } |
808 | struct cgfreezer_test { |
809 | int (*fn)(const char *root); |
810 | const char *name; |
811 | } tests[] = { |
812 | T(test_cgfreezer_simple), |
813 | T(test_cgfreezer_tree), |
814 | T(test_cgfreezer_forkbomb), |
815 | T(test_cgfreezer_mkdir), |
816 | T(test_cgfreezer_rmdir), |
817 | T(test_cgfreezer_migrate), |
818 | T(test_cgfreezer_ptrace), |
819 | T(test_cgfreezer_stopped), |
820 | T(test_cgfreezer_ptraced), |
821 | T(test_cgfreezer_vfork), |
822 | }; |
823 | #undef T |
824 | |
825 | int main(int argc, char *argv[]) |
826 | { |
827 | char root[PATH_MAX]; |
828 | int i, ret = EXIT_SUCCESS; |
829 | |
830 | if (cg_find_unified_root(root, len: sizeof(root))) |
831 | ksft_exit_skip(msg: "cgroup v2 isn't mounted\n" ); |
832 | for (i = 0; i < ARRAY_SIZE(tests); i++) { |
833 | switch (tests[i].fn(root)) { |
834 | case KSFT_PASS: |
835 | ksft_test_result_pass(msg: "%s\n" , tests[i].name); |
836 | break; |
837 | case KSFT_SKIP: |
838 | ksft_test_result_skip(msg: "%s\n" , tests[i].name); |
839 | break; |
840 | default: |
841 | ret = EXIT_FAILURE; |
842 | ksft_test_result_fail(msg: "%s\n" , tests[i].name); |
843 | break; |
844 | } |
845 | } |
846 | |
847 | return ret; |
848 | } |
849 | |