github.com/anonymouse64/snapd@v0.0.0-20210824153203-04c4c42d842d/cmd/libsnap-confine-private/cgroup-support-test.c (about)

     1  /*
     2   * Copyright (C) 2021 Canonical Ltd
     3   *
     4   * This program is free software: you can redistribute it and/or modify
     5   * it under the terms of the GNU General Public License version 3 as
     6   * published by the Free Software Foundation.
     7   *
     8   * This program is distributed in the hope that it will be useful,
     9   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    10   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    11   * GNU General Public License for more details.
    12   *
    13   * You should have received a copy of the GNU General Public License
    14   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    15   *
    16   */
    17  
    18  #include "cgroup-support.c"
    19  
    20  #include <fcntl.h>
    21  #include <glib.h>
    22  #include <glib/gstdio.h>
    23  #include <sys/stat.h>
    24  #include <sys/types.h>
    25  #include <unistd.h>
    26  
    27  #include "../libsnap-confine-private/cleanup-funcs.h"
    28  #include "../libsnap-confine-private/test-utils.h"
    29  #include "cgroup-support.h"
    30  
    31  static void sc_set_self_cgroup_path(const char *mock);
    32  
    33  static void sc_set_cgroup_root(const char *mock) { cgroup_dir = mock; }
    34  
    35  typedef struct _cgroupv2_is_tracking_fixture {
    36      char *self_cgroup;
    37      char *root;
    38  } cgroupv2_is_tracking_fixture;
    39  
    40  static void cgroupv2_is_tracking_set_up(cgroupv2_is_tracking_fixture *fixture, gconstpointer user_data) {
    41      GError *err = NULL;
    42      int fd = g_file_open_tmp("s-c-unit-is-tracking-self-group.XXXXXX", &fixture->self_cgroup, &err);
    43      g_assert_no_error(err);
    44      g_assert_cmpint(fd, >=, 0);
    45      g_close(fd, &err);
    46      g_assert_no_error(err);
    47      sc_set_self_cgroup_path(fixture->self_cgroup);
    48  
    49      fixture->root = g_dir_make_tmp("s-c-unit-test-root.XXXXXX", &err);
    50      sc_set_cgroup_root(fixture->root);
    51  }
    52  
    53  static void cgroupv2_is_tracking_tear_down(cgroupv2_is_tracking_fixture *fixture, gconstpointer user_data) {
    54      GError *err = NULL;
    55  
    56      sc_set_self_cgroup_path("/proc/self/cgroup");
    57      /* mocked file may have been removed by the test */
    58      (void)g_remove(fixture->self_cgroup);
    59      g_free(fixture->self_cgroup);
    60  
    61      sc_set_cgroup_root("/sys/fs/cgroup");
    62      char *cmd = g_strdup_printf("rm -rf %s", fixture->root);
    63      g_debug("cleanup command: %s", cmd);
    64      g_spawn_command_line_sync(cmd, NULL, NULL, NULL, &err);
    65      g_free(cmd);
    66      g_assert_no_error(err);
    67      g_free(fixture->root);
    68  }
    69  
    70  static void _test_sc_cgroupv2_is_tracking_happy(cgroupv2_is_tracking_fixture *fixture) {
    71      /* there exist 3 groups with processes from a given snap */
    72      const char *dirs[] = {
    73          "/foo/bar/baz/snap.foo.app.1234-1234.scope",
    74          "/foo/bar/snap.foo.app.1111-1111.scope",
    75          "/foo/bar/bad",
    76          "/system.slice/snap.foo.bar.service",
    77          "/user/slice/other/app",
    78      };
    79  
    80      for (size_t i = 0; i < sizeof dirs / sizeof dirs[0]; i++) {
    81          char *np = g_build_filename(fixture->root, dirs[i], NULL);
    82          int ret = g_mkdir_with_parents(np, 0755);
    83          g_assert_cmpint(ret, ==, 0);
    84          g_free(np);
    85      }
    86  
    87      bool is_tracking = sc_cgroup_v2_is_tracking_snap("foo");
    88      g_assert_true(is_tracking);
    89  }
    90  
    91  static void test_sc_cgroupv2_is_tracking_happy_scope(cgroupv2_is_tracking_fixture *fixture, gconstpointer user_data) {
    92      g_assert_true(g_file_set_contents(fixture->self_cgroup, "0::/foo/bar/baz/snap.foo.app.1234-1234.scope", -1, NULL));
    93  
    94      _test_sc_cgroupv2_is_tracking_happy(fixture);
    95  }
    96  
    97  static void test_sc_cgroupv2_is_tracking_happy_service(cgroupv2_is_tracking_fixture *fixture, gconstpointer user_data) {
    98      g_assert_true(g_file_set_contents(fixture->self_cgroup, "0::/system.slice/snap.foo.svc.service", -1, NULL));
    99  
   100      _test_sc_cgroupv2_is_tracking_happy(fixture);
   101  }
   102  
   103  static void test_sc_cgroupv2_is_tracking_just_own_group(cgroupv2_is_tracking_fixture *fixture,
   104                                                          gconstpointer user_data) {
   105      g_assert_true(g_file_set_contents(fixture->self_cgroup, "0::/foo/bar/baz/snap.foo.app.1234-1234.scope", -1, NULL));
   106  
   107      /* our group is the only one for this snap */
   108      const char *dirs[] = {
   109          "/foo/bar/baz/snap.foo.app.1234-1234.scope",
   110          "/foo/bar/bad",
   111          "/system.slice/some/app/other",
   112          "/user/slice/other/app",
   113      };
   114  
   115      for (size_t i = 0; i < sizeof dirs / sizeof dirs[0]; i++) {
   116          char *np = g_build_filename(fixture->root, dirs[i], NULL);
   117          int ret = g_mkdir_with_parents(np, 0755);
   118          g_assert_cmpint(ret, ==, 0);
   119          g_free(np);
   120      }
   121  
   122      bool is_tracking = sc_cgroup_v2_is_tracking_snap("foo");
   123      /* our own group is skipped */
   124      g_assert_false(is_tracking);
   125  }
   126  
   127  static void test_sc_cgroupv2_is_tracking_other_snaps(cgroupv2_is_tracking_fixture *fixture, gconstpointer user_data) {
   128      g_assert_true(g_file_set_contents(fixture->self_cgroup, "0::/foo/bar/baz/snap.foo.app.1234-1234.scope", -1, NULL));
   129  
   130      /* our group is the only one for this snap */
   131      const char *dirs[] = {
   132          "/foo/bar/baz/snap.other.app.1234-1234.scope",
   133          "/foo/bar/bad",
   134          "/system.slice/some/app/snap.one-more.app.service",
   135          "/user/slice/other/app",
   136      };
   137  
   138      for (size_t i = 0; i < sizeof dirs / sizeof dirs[0]; i++) {
   139          char *np = g_build_filename(fixture->root, dirs[i], NULL);
   140          int ret = g_mkdir_with_parents(np, 0755);
   141          g_assert_cmpint(ret, ==, 0);
   142          g_free(np);
   143      }
   144  
   145      bool is_tracking = sc_cgroup_v2_is_tracking_snap("foo");
   146      /* our own group is skipped */
   147      g_assert_false(is_tracking);
   148  }
   149  
   150  static void test_sc_cgroupv2_is_tracking_no_dirs(cgroupv2_is_tracking_fixture *fixture, gconstpointer user_data) {
   151      g_assert_true(g_file_set_contents(fixture->self_cgroup, "0::/foo/bar/baz/snap.foo.app.scope", -1, NULL));
   152  
   153      bool is_tracking = sc_cgroup_v2_is_tracking_snap("foo");
   154      g_assert_false(is_tracking);
   155  }
   156  
   157  static void test_sc_cgroupv2_is_tracking_bad_self_group(cgroupv2_is_tracking_fixture *fixture,
   158                                                          gconstpointer user_data) {
   159      /* trigger a failure in own group handling */
   160      g_assert_true(g_file_set_contents(fixture->self_cgroup, "", -1, NULL));
   161  
   162      if (g_test_subprocess()) {
   163          sc_cgroup_v2_is_tracking_snap("foo");
   164      }
   165      g_test_trap_subprocess(NULL, 0, 0);
   166      g_test_trap_assert_failed();
   167      g_test_trap_assert_stderr("cannot obtain own cgroup v2 group path\n");
   168  }
   169  
   170  static void test_sc_cgroupv2_is_tracking_bad_nesting(cgroupv2_is_tracking_fixture *fixture, gconstpointer user_data) {
   171      g_assert_true(g_file_set_contents(fixture->self_cgroup, "0::/foo/bar/baz/snap.foo.app.scope", -1, NULL));
   172  
   173      /* create a hierarchy so deep that it triggers the nesting error */
   174      char *prev_path = g_build_filename(fixture->root, NULL);
   175      for (size_t i = 0; i < max_traversal_depth; i++) {
   176          char *np = g_build_filename(prev_path, "nested", NULL);
   177          int ret = g_mkdir_with_parents(np, 0755);
   178          g_assert_cmpint(ret, ==, 0);
   179          g_free(prev_path);
   180          prev_path = np;
   181      }
   182      g_free(prev_path);
   183  
   184      if (g_test_subprocess()) {
   185          sc_cgroup_v2_is_tracking_snap("foo");
   186      }
   187      g_test_trap_subprocess(NULL, 0, 0);
   188      g_test_trap_assert_failed();
   189      g_test_trap_assert_stderr("cannot traverse cgroups hierarchy deeper than 32 levels\n");
   190  }
   191  
   192  static void test_sc_cgroupv2_is_tracking_dir_permissions(cgroupv2_is_tracking_fixture *fixture,
   193                                                           gconstpointer user_data) {
   194      if (geteuid() == 0) {
   195          g_test_skip("the test will not work when running as root");
   196          return;
   197      }
   198      g_assert_true(g_file_set_contents(fixture->self_cgroup, "0::/foo/bar/baz/snap.foo.app.1234-1234.scope", -1, NULL));
   199  
   200      /* there exist 2 groups with processes from a given snap */
   201      const char *dirs[] = {
   202          "/foo/bar/bad",
   203          "/foo/bar/bad/badperm",
   204      };
   205      for (size_t i = 0; i < sizeof dirs / sizeof dirs[0]; i++) {
   206          int mode = 0755;
   207          if (g_str_has_suffix(dirs[i], "/badperm")) {
   208              mode = 0000;
   209          }
   210          char *np = g_build_filename(fixture->root, dirs[i], NULL);
   211          int ret = g_mkdir_with_parents(np, mode);
   212          g_assert_cmpint(ret, ==, 0);
   213          g_free(np);
   214      }
   215  
   216      /* dies when hitting an error traversing the hierarchy */
   217      if (g_test_subprocess()) {
   218          sc_cgroup_v2_is_tracking_snap("foo");
   219      }
   220      g_test_trap_subprocess(NULL, 0, 0);
   221      g_test_trap_assert_failed();
   222      g_test_trap_assert_stderr("cannot open directory entry \"badperm\": Permission denied\n");
   223  }
   224  
   225  static void test_sc_cgroupv2_is_tracking_no_cgroup_root(cgroupv2_is_tracking_fixture *fixture,
   226                                                          gconstpointer user_data) {
   227      g_assert_true(g_file_set_contents(fixture->self_cgroup, "0::/foo/bar/baz/snap.foo.app.1234-1234.scope", -1, NULL));
   228  
   229      sc_set_cgroup_root("/does/not/exist");
   230  
   231      // does not die when cgroup root is not present
   232      bool is_tracking = sc_cgroup_v2_is_tracking_snap("foo");
   233      g_assert_false(is_tracking);
   234  }
   235  
   236  static void sc_set_self_cgroup_path(const char *mock) { self_cgroup = mock; }
   237  
   238  typedef struct _cgroupv2_own_group_fixture {
   239      char *self_cgroup;
   240  } cgroupv2_own_group_fixture;
   241  
   242  static void cgroupv2_own_group_set_up(cgroupv2_own_group_fixture *fixture, gconstpointer user_data) {
   243      GError *err = NULL;
   244      int fd = g_file_open_tmp("s-c-unit-test.XXXXXX", &fixture->self_cgroup, &err);
   245      g_assert_no_error(err);
   246      g_close(fd, &err);
   247      g_assert_no_error(err);
   248      sc_set_self_cgroup_path(fixture->self_cgroup);
   249  }
   250  
   251  static void cgroupv2_own_group_tear_down(cgroupv2_own_group_fixture *fixture, gconstpointer user_data) {
   252      sc_set_self_cgroup_path("/proc/self/cgroup");
   253      g_remove(fixture->self_cgroup);
   254      g_free(fixture->self_cgroup);
   255  }
   256  
   257  static void test_sc_cgroupv2_own_group_path_simple_happy_scope(cgroupv2_own_group_fixture *fixture,
   258                                                                 gconstpointer user_data) {
   259      char *p SC_CLEANUP(sc_cleanup_string) = NULL;
   260      g_assert_true(g_file_set_contents(fixture->self_cgroup, (char *)user_data, -1, NULL));
   261      p = sc_cgroup_v2_own_path_full();
   262      g_assert_cmpstr(p, ==, "/foo/bar/baz.slice/snap.foo.bar.1234-1234.scope");
   263  }
   264  
   265  static void test_sc_cgroupv2_own_group_path_simple_happy_service(cgroupv2_own_group_fixture *fixture,
   266                                                                   gconstpointer user_data) {
   267      char *p SC_CLEANUP(sc_cleanup_string) = NULL;
   268      g_assert_true(g_file_set_contents(fixture->self_cgroup, (char *)user_data, -1, NULL));
   269      p = sc_cgroup_v2_own_path_full();
   270      g_assert_cmpstr(p, ==, "/system.slice/snap.foo.bar.service");
   271  }
   272  
   273  static void test_sc_cgroupv2_own_group_path_empty(cgroupv2_own_group_fixture *fixture, gconstpointer user_data) {
   274      char *p SC_CLEANUP(sc_cleanup_string) = NULL;
   275      g_assert_true(g_file_set_contents(fixture->self_cgroup, (char *)user_data, -1, NULL));
   276      p = sc_cgroup_v2_own_path_full();
   277      g_assert_null(p);
   278  }
   279  
   280  static void _test_sc_cgroupv2_own_group_path_die_with_message(const char *msg) {
   281      if (g_test_subprocess()) {
   282          char *p SC_CLEANUP(sc_cleanup_string) = NULL;
   283          // keep this separate so that p isn't unused
   284          p = sc_cgroup_v2_own_path_full();
   285      }
   286      g_test_trap_subprocess(NULL, 0, 0);
   287      g_test_trap_assert_failed();
   288      g_test_trap_assert_stderr(msg);
   289  }
   290  
   291  static void test_sc_cgroupv2_own_group_path_die(cgroupv2_own_group_fixture *fixture, gconstpointer user_data) {
   292      g_assert_true(g_file_set_contents(fixture->self_cgroup, (char *)user_data, -1, NULL));
   293      _test_sc_cgroupv2_own_group_path_die_with_message("unexpected content of group entry 0::\n");
   294  }
   295  
   296  static void test_sc_cgroupv2_own_group_path_no_file(cgroupv2_own_group_fixture *fixture, gconstpointer user_data) {
   297      /* make sure that the file is removed if it exists */
   298      (void)g_remove(fixture->self_cgroup);
   299      _test_sc_cgroupv2_own_group_path_die_with_message("cannot open *\n");
   300  }
   301  
   302  static void test_sc_cgroupv2_own_group_path_permission(cgroupv2_own_group_fixture *fixture, gconstpointer user_data) {
   303      if (geteuid() == 0) {
   304          g_test_skip("the test will not work when running as root");
   305          return;
   306      }
   307      int ret = g_chmod(fixture->self_cgroup, 0000);
   308      g_assert_cmpint(ret, ==, 0);
   309      _test_sc_cgroupv2_own_group_path_die_with_message("cannot open *: Permission denied\n");
   310  }
   311  
   312  static void __attribute__((constructor)) init(void) {
   313      g_test_add("/cgroup/v2/own_path_full_newline", cgroupv2_own_group_fixture,
   314                 "0::/foo/bar/baz.slice/snap.foo.bar.1234-1234.scope\n", cgroupv2_own_group_set_up,
   315                 test_sc_cgroupv2_own_group_path_simple_happy_scope, cgroupv2_own_group_tear_down);
   316      g_test_add("/cgroup/v2/own_path_full_no_newline", cgroupv2_own_group_fixture,
   317                 "0::/foo/bar/baz.slice/snap.foo.bar.1234-1234.scope", cgroupv2_own_group_set_up,
   318                 test_sc_cgroupv2_own_group_path_simple_happy_scope, cgroupv2_own_group_tear_down);
   319      g_test_add("/cgroup/v2/own_path_full_firstline", cgroupv2_own_group_fixture,
   320                 "0::/foo/bar/baz.slice/snap.foo.bar.1234-1234.scope\n"
   321                 "0::/bad\n",
   322                 cgroupv2_own_group_set_up, test_sc_cgroupv2_own_group_path_simple_happy_scope,
   323                 cgroupv2_own_group_tear_down);
   324      g_test_add("/cgroup/v2/own_path_full_ignore_non_unified", cgroupv2_own_group_fixture,
   325                 "1::/ignored\n"
   326                 "0::/foo/bar/baz.slice/snap.foo.bar.1234-1234.scope\n",
   327                 cgroupv2_own_group_set_up, test_sc_cgroupv2_own_group_path_simple_happy_scope,
   328                 cgroupv2_own_group_tear_down);
   329      g_test_add("/cgroup/v2/own_path_full_service", cgroupv2_own_group_fixture,
   330                 "0::/system.slice/snap.foo.bar.service\n", cgroupv2_own_group_set_up,
   331                 test_sc_cgroupv2_own_group_path_simple_happy_service, cgroupv2_own_group_tear_down);
   332      g_test_add("/cgroup/v2/own_path_full_empty", cgroupv2_own_group_fixture, "", cgroupv2_own_group_set_up,
   333                 test_sc_cgroupv2_own_group_path_empty, cgroupv2_own_group_tear_down);
   334      g_test_add("/cgroup/v2/own_path_full_not_found", cgroupv2_own_group_fixture,
   335                 /* missing 0:: group */
   336                 "1::/ignored\n"
   337                 "2::/foo/bar/baz.slice\n",
   338                 cgroupv2_own_group_set_up, test_sc_cgroupv2_own_group_path_empty, cgroupv2_own_group_tear_down);
   339      g_test_add("/cgroup/v2/own_path_full_die", cgroupv2_own_group_fixture, "0::", cgroupv2_own_group_set_up,
   340                 test_sc_cgroupv2_own_group_path_die, cgroupv2_own_group_tear_down);
   341      g_test_add("/cgroup/v2/own_path_full_no_file", cgroupv2_own_group_fixture, NULL, cgroupv2_own_group_set_up,
   342                 test_sc_cgroupv2_own_group_path_no_file, cgroupv2_own_group_tear_down);
   343      g_test_add("/cgroup/v2/own_path_full_permission", cgroupv2_own_group_fixture, NULL, cgroupv2_own_group_set_up,
   344                 test_sc_cgroupv2_own_group_path_permission, cgroupv2_own_group_tear_down);
   345  
   346      g_test_add("/cgroup/v2/is_tracking_happy_scope", cgroupv2_is_tracking_fixture, NULL, cgroupv2_is_tracking_set_up,
   347                 test_sc_cgroupv2_is_tracking_happy_scope, cgroupv2_is_tracking_tear_down);
   348      g_test_add("/cgroup/v2/is_tracking_happy_service", cgroupv2_is_tracking_fixture, NULL, cgroupv2_is_tracking_set_up,
   349                 test_sc_cgroupv2_is_tracking_happy_service, cgroupv2_is_tracking_tear_down);
   350      g_test_add("/cgroup/v2/is_tracking_just_own", cgroupv2_is_tracking_fixture, NULL, cgroupv2_is_tracking_set_up,
   351                 test_sc_cgroupv2_is_tracking_just_own_group, cgroupv2_is_tracking_tear_down);
   352      g_test_add("/cgroup/v2/is_tracking_only_other_snaps", cgroupv2_is_tracking_fixture, NULL,
   353                 cgroupv2_is_tracking_set_up, test_sc_cgroupv2_is_tracking_other_snaps, cgroupv2_is_tracking_tear_down);
   354      g_test_add("/cgroup/v2/is_tracking_empty_groups", cgroupv2_is_tracking_fixture, NULL, cgroupv2_is_tracking_set_up,
   355                 test_sc_cgroupv2_is_tracking_no_dirs, cgroupv2_is_tracking_tear_down);
   356      g_test_add("/cgroup/v2/is_tracking_bad_self_group", cgroupv2_is_tracking_fixture, NULL, cgroupv2_is_tracking_set_up,
   357                 test_sc_cgroupv2_is_tracking_bad_self_group, cgroupv2_is_tracking_tear_down);
   358      g_test_add("/cgroup/v2/is_tracking_bad_dir_permissions", cgroupv2_is_tracking_fixture, NULL,
   359                 cgroupv2_is_tracking_set_up, test_sc_cgroupv2_is_tracking_dir_permissions,
   360                 cgroupv2_is_tracking_tear_down);
   361      g_test_add("/cgroup/v2/is_tracking_bad_nesting", cgroupv2_is_tracking_fixture, NULL, cgroupv2_is_tracking_set_up,
   362                 test_sc_cgroupv2_is_tracking_bad_nesting, cgroupv2_is_tracking_tear_down);
   363      g_test_add("/cgroup/v2/is_tracking_no_cgroup_root", cgroupv2_is_tracking_fixture, NULL, cgroupv2_is_tracking_set_up,
   364                 test_sc_cgroupv2_is_tracking_no_cgroup_root, cgroupv2_is_tracking_tear_down);
   365  }