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