849 lines · c
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 DEBUG18#ifdef DEBUG19#define debug(args...) fprintf(stderr, args)20#else21#define debug(args...)22#endif23 24/*25 * Check if the cgroup is frozen by looking at the cgroup.events::frozen value.26 */27static int cg_check_frozen(const char *cgroup, bool frozen)28{29 if (frozen) {30 if (cg_read_strstr(cgroup, "cgroup.events", "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, "cgroup.events", "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 */50static int cg_freeze_nowait(const char *cgroup, bool freeze)51{52 return cg_write(cgroup, "cgroup.freeze", 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 */59static 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 83out: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 */93static 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, freeze);112out:113 close(fd);114 return ret;115}116 117/*118 * A simple process running in a sleep loop until being119 * re-parented.120 */121static 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 100133 * running processes and freeze it. Then unfreeze it. Then it kills all134 * processes and destroys the cgroup.135 */136static 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, "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, child_fn, NULL);151 152 if (cg_wait_for_proc_count(cgroup, 100))153 goto cleanup;154 155 if (cg_check_frozen(cgroup, false))156 goto cleanup;157 158 if (cg_freeze_wait(cgroup, true))159 goto cleanup;160 161 if (cg_freeze_wait(cgroup, false))162 goto cleanup;163 164 ret = KSFT_PASS;165 166cleanup:167 if (cgroup)168 cg_destroy(cgroup);169 free(cgroup);170 return ret;171}172 173/*174 * The test creates the following hierarchy:175 * A176 * / / \ \177 * B E I K178 * /\ |179 * C D F180 * |181 * G182 * |183 * H184 *185 * with a process in C, H and 3 processes in K.186 * Then it tries to freeze and unfreeze the whole tree.187 */188static 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, "cg_test_tree_A");195 if (!cgroup[0])196 goto cleanup;197 198 cgroup[1] = cg_name(cgroup[0], "B");199 if (!cgroup[1])200 goto cleanup;201 202 cgroup[2] = cg_name(cgroup[1], "C");203 if (!cgroup[2])204 goto cleanup;205 206 cgroup[3] = cg_name(cgroup[1], "D");207 if (!cgroup[3])208 goto cleanup;209 210 cgroup[4] = cg_name(cgroup[0], "E");211 if (!cgroup[4])212 goto cleanup;213 214 cgroup[5] = cg_name(cgroup[4], "F");215 if (!cgroup[5])216 goto cleanup;217 218 cgroup[6] = cg_name(cgroup[5], "G");219 if (!cgroup[6])220 goto cleanup;221 222 cgroup[7] = cg_name(cgroup[6], "H");223 if (!cgroup[7])224 goto cleanup;225 226 cgroup[8] = cg_name(cgroup[0], "I");227 if (!cgroup[8])228 goto cleanup;229 230 cgroup[9] = cg_name(cgroup[0], "K");231 if (!cgroup[9])232 goto cleanup;233 234 for (i = 0; i < 10; i++)235 if (cg_create(cgroup[i]))236 goto cleanup;237 238 cg_run_nowait(cgroup[2], child_fn, NULL);239 cg_run_nowait(cgroup[7], child_fn, NULL);240 cg_run_nowait(cgroup[9], child_fn, NULL);241 cg_run_nowait(cgroup[9], child_fn, NULL);242 cg_run_nowait(cgroup[9], child_fn, NULL);243 244 /*245 * Wait until all child processes will enter246 * corresponding cgroups.247 */248 249 if (cg_wait_for_proc_count(cgroup[2], 1) ||250 cg_wait_for_proc_count(cgroup[7], 1) ||251 cg_wait_for_proc_count(cgroup[9], 3))252 goto cleanup;253 254 /*255 * Freeze B.256 */257 if (cg_freeze_wait(cgroup[1], true))258 goto cleanup;259 260 /*261 * Freeze F.262 */263 if (cg_freeze_wait(cgroup[5], true))264 goto cleanup;265 266 /*267 * Freeze G.268 */269 if (cg_freeze_wait(cgroup[6], true))270 goto cleanup;271 272 /*273 * Check that A and E are not frozen.274 */275 if (cg_check_frozen(cgroup[0], false))276 goto cleanup;277 278 if (cg_check_frozen(cgroup[4], false))279 goto cleanup;280 281 /*282 * Freeze A. Check that A, B and E are frozen.283 */284 if (cg_freeze_wait(cgroup[0], true))285 goto cleanup;286 287 if (cg_check_frozen(cgroup[1], true))288 goto cleanup;289 290 if (cg_check_frozen(cgroup[4], true))291 goto cleanup;292 293 /*294 * Unfreeze B, F and G295 */296 if (cg_freeze_nowait(cgroup[1], false))297 goto cleanup;298 299 if (cg_freeze_nowait(cgroup[5], false))300 goto cleanup;301 302 if (cg_freeze_nowait(cgroup[6], false))303 goto cleanup;304 305 /*306 * Check that C and H are still frozen.307 */308 if (cg_check_frozen(cgroup[2], true))309 goto cleanup;310 311 if (cg_check_frozen(cgroup[7], 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[0], false))318 goto cleanup;319 320 if (cg_check_frozen(cgroup[2], false))321 goto cleanup;322 323 if (cg_check_frozen(cgroup[9], false))324 goto cleanup;325 326 ret = KSFT_PASS;327 328cleanup:329 for (i = 9; i >= 0 && cgroup[i]; i--) {330 cg_destroy(cgroup[i]);331 free(cgroup[i]);332 }333 334 return ret;335}336 337/*338 * A fork bomb emulator.339 */340static 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 populated358 * anymore.359 */360static int test_cgfreezer_forkbomb(const char *root)361{362 int ret = KSFT_FAIL;363 char *cgroup = NULL;364 365 cgroup = cg_name(root, "cg_forkbomb_test");366 if (!cgroup)367 goto cleanup;368 369 if (cg_create(cgroup))370 goto cleanup;371 372 cg_run_nowait(cgroup, forkbomb_fn, NULL);373 374 usleep(100000);375 376 if (cg_freeze_wait(cgroup, true))377 goto cleanup;378 379 if (cg_killall(cgroup))380 goto cleanup;381 382 if (cg_wait_for_proc_count(cgroup, 0))383 goto cleanup;384 385 ret = KSFT_PASS;386 387cleanup: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 cgroup396 * and populates it with a task. After that it checks that the child cgroup397 * is frozen and the parent cgroup remains frozen too.398 */399static 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, "cg_test_mkdir_A");406 if (!parent)407 goto cleanup;408 409 child = cg_name(parent, "cg_test_mkdir_B");410 if (!child)411 goto cleanup;412 413 if (cg_create(parent))414 goto cleanup;415 416 if (cg_freeze_wait(parent, true))417 goto cleanup;418 419 if (cg_create(child))420 goto cleanup;421 422 pid = cg_run_nowait(child, child_fn, NULL);423 if (pid < 0)424 goto cleanup;425 426 if (cg_wait_for_proc_count(child, 1))427 goto cleanup;428 429 if (cg_check_frozen(child, true))430 goto cleanup;431 432 if (cg_check_frozen(parent, true))433 goto cleanup;434 435 ret = KSFT_PASS;436 437cleanup:438 if (child)439 cg_destroy(child);440 free(child);441 if (parent)442 cg_destroy(parent);443 free(parent);444 return ret;445}446 447/*448 * The test creates two nested cgroups, freezes the parent449 * and removes the child. Then it checks that the parent cgroup450 * remains frozen and it's possible to create a new child451 * without unfreezing. The new child is frozen too.452 */453static int test_cgfreezer_rmdir(const char *root)454{455 int ret = KSFT_FAIL;456 char *parent, *child = NULL;457 458 parent = cg_name(root, "cg_test_rmdir_A");459 if (!parent)460 goto cleanup;461 462 child = cg_name(parent, "cg_test_rmdir_B");463 if (!child)464 goto cleanup;465 466 if (cg_create(parent))467 goto cleanup;468 469 if (cg_create(child))470 goto cleanup;471 472 if (cg_freeze_wait(parent, true))473 goto cleanup;474 475 if (cg_destroy(child))476 goto cleanup;477 478 if (cg_check_frozen(parent, true))479 goto cleanup;480 481 if (cg_create(child))482 goto cleanup;483 484 if (cg_check_frozen(child, true))485 goto cleanup;486 487 ret = KSFT_PASS;488 489cleanup:490 if (child)491 cg_destroy(child);492 free(child);493 if (parent)494 cg_destroy(parent);495 free(parent);496 return ret;497}498 499/*500 * The test creates two cgroups: A and B, runs a process in A501 * 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 */508static 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, "cg_test_migrate_A");515 if (!cgroup[0])516 goto cleanup;517 518 cgroup[1] = cg_name(root, "cg_test_migrate_B");519 if (!cgroup[1])520 goto cleanup;521 522 if (cg_create(cgroup[0]))523 goto cleanup;524 525 if (cg_create(cgroup[1]))526 goto cleanup;527 528 pid = cg_run_nowait(cgroup[0], child_fn, NULL);529 if (pid < 0)530 goto cleanup;531 532 if (cg_wait_for_proc_count(cgroup[0], 1))533 goto cleanup;534 535 /*536 * Migrate from A (running) to B (frozen)537 */538 if (cg_freeze_wait(cgroup[1], true))539 goto cleanup;540 541 if (cg_enter_and_wait_for_frozen(cgroup[1], pid, true))542 goto cleanup;543 544 if (cg_check_frozen(cgroup[0], false))545 goto cleanup;546 547 /*548 * Migrate from B (frozen) to A (running)549 */550 if (cg_enter_and_wait_for_frozen(cgroup[0], pid, false))551 goto cleanup;552 553 if (cg_check_frozen(cgroup[1], true))554 goto cleanup;555 556 /*557 * Migrate from A (frozen) to B (frozen)558 */559 if (cg_freeze_wait(cgroup[0], true))560 goto cleanup;561 562 if (cg_enter_and_wait_for_frozen(cgroup[1], pid, true))563 goto cleanup;564 565 if (cg_check_frozen(cgroup[0], true))566 goto cleanup;567 568 ret = KSFT_PASS;569 570cleanup:571 if (cgroup[0])572 cg_destroy(cgroup[0]);573 free(cgroup[0]);574 if (cgroup[1])575 cg_destroy(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 */583static 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, "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, child_fn, NULL);598 if (pid < 0)599 goto cleanup;600 601 if (cg_wait_for_proc_count(cgroup, 1))602 goto cleanup;603 604 if (cg_freeze_wait(cgroup, 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 task617 * is in traced state.618 */619 if (cg_check_frozen(cgroup, 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, true))629 goto cleanup;630 631 ret = KSFT_PASS;632 633cleanup:634 if (cgroup)635 cg_destroy(cgroup);636 free(cgroup);637 return ret;638}639 640/*641 * Check if the process is stopped.642 */643static int proc_check_stopped(int pid)644{645 char buf[PAGE_SIZE];646 int len;647 648 len = proc_read_text(pid, 0, "stat", buf, 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 */665static int test_cgfreezer_stopped(const char *root)666{667 int pid, ret = KSFT_FAIL;668 char *cgroup = NULL;669 670 cgroup = cg_name(root, "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, child_fn, NULL);678 679 if (cg_wait_for_proc_count(cgroup, 1))680 goto cleanup;681 682 if (kill(pid, SIGSTOP))683 goto cleanup;684 685 if (cg_check_frozen(cgroup, false))686 goto cleanup;687 688 if (cg_freeze_wait(cgroup, true))689 goto cleanup;690 691 if (cg_freeze_wait(cgroup, false))692 goto cleanup;693 694 if (proc_check_stopped(pid))695 goto cleanup;696 697 ret = KSFT_PASS;698 699cleanup: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 */709static 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, "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, child_fn, NULL);723 724 if (cg_wait_for_proc_count(cgroup, 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, false))736 goto cleanup;737 738 if (cg_freeze_wait(cgroup, 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, 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 756cleanup:757 if (cgroup)758 cg_destroy(cgroup);759 free(cgroup);760 return ret;761}762 763static 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 */778static int test_cgfreezer_vfork(const char *root)779{780 int ret = KSFT_FAIL;781 char *cgroup = NULL;782 783 cgroup = cg_name(root, "cg_test_vfork");784 if (!cgroup)785 goto cleanup;786 787 if (cg_create(cgroup))788 goto cleanup;789 790 cg_run_nowait(cgroup, vfork_fn, NULL);791 792 if (cg_wait_for_proc_count(cgroup, 2))793 goto cleanup;794 795 if (cg_freeze_wait(cgroup, true))796 goto cleanup;797 798 ret = KSFT_PASS;799 800cleanup:801 if (cgroup)802 cg_destroy(cgroup);803 free(cgroup);804 return ret;805}806 807#define T(x) { x, #x }808struct 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 T824 825int 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, sizeof(root), NULL))831 ksft_exit_skip("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("%s\n", tests[i].name);836 break;837 case KSFT_SKIP:838 ksft_test_result_skip("%s\n", tests[i].name);839 break;840 default:841 ret = EXIT_FAILURE;842 ksft_test_result_fail("%s\n", tests[i].name);843 break;844 }845 }846 847 return ret;848}849