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 }