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 }