github.com/tompreston/snapd@v0.0.0-20210817193607-954edfcb9611/cmd/snap-device-helper/snap-device-helper-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 "../libsnap-confine-private/test-utils.h" 19 20 #include <fcntl.h> 21 #include <glib.h> 22 #include <glib/gstdio.h> 23 #include <string.h> 24 #include <sys/stat.h> 25 #include <sys/types.h> 26 #include <sys/wait.h> 27 #include <unistd.h> 28 29 #include "snap-device-helper.c" 30 31 #include "../libsnap-confine-private/device-cgroup-support.h" 32 33 typedef struct _sdh_test_fixture { 34 char *sysroot; 35 } sdh_test_fixture; 36 37 static void mkdir_in_sysroot(sdh_test_fixture *fixture, const char *path) { 38 char *p = g_build_filename(fixture->sysroot, path, NULL); 39 g_assert(g_mkdir_with_parents(p, 0755) == 0); 40 g_free(p); 41 } 42 43 static void symlink_in_sysroot(sdh_test_fixture *fixture, const char *from, const char *to) { 44 g_debug("mock symlink from %s to %s", from, to); 45 char *pfrom = g_build_filename(fixture->sysroot, from, NULL); 46 g_assert(g_path_is_absolute(to) == FALSE); 47 g_assert_cmpint(symlink(to, pfrom), ==, 0); 48 g_free(pfrom); 49 } 50 51 static void sdh_test_set_up(sdh_test_fixture *fixture, gconstpointer user_data) { 52 gchar *mock_dir = g_dir_make_tmp(NULL, NULL); 53 g_assert_nonnull(mock_dir); 54 55 fixture->sysroot = mock_dir; 56 sysroot = mock_dir; 57 58 char *sys_devices = g_build_filename(fixture->sysroot, "sys", "devices", NULL); 59 g_assert(g_mkdir_with_parents(sys_devices, 0755) == 0); 60 g_free(sys_devices); 61 char *sys_class_block = g_build_filename(fixture->sysroot, "sys", "class", "block", NULL); 62 g_assert(g_mkdir_with_parents(sys_class_block, 0755) == 0); 63 g_free(sys_class_block); 64 char *sys_class_other = g_build_filename(fixture->sysroot, "sys", "class", "other", NULL); 65 g_assert(g_mkdir_with_parents(sys_class_other, 0755) == 0); 66 g_free(sys_class_other); 67 68 g_debug("mock sysroot dir: %s", mock_dir); 69 } 70 71 static void mocks_reset(void); 72 73 static void sdh_test_tear_down(sdh_test_fixture *fixture, gconstpointer user_data) { 74 sysroot = ""; 75 if (!g_strcmp0(sysroot, "/")) { 76 rm_rf_tmp(fixture->sysroot); 77 } 78 mocks_reset(); 79 g_free(fixture->sysroot); 80 } 81 82 static struct mocks { 83 size_t cgorup_new_calls; 84 void *new_ret; 85 char *new_tag; 86 int new_flags; 87 88 size_t cgroup_allow_calls; 89 size_t cgroup_deny_calls; 90 int device_type; 91 int device_major; 92 int device_minor; 93 int device_ret; 94 95 } mocks; 96 97 static void mocks_reset(void) { 98 if (mocks.new_tag != NULL) { 99 g_free(mocks.new_tag); 100 } 101 memset(&mocks, 0, sizeof(mocks)); 102 } 103 104 /* mocked in test */ 105 sc_device_cgroup *sc_device_cgroup_new(const char *security_tag, int flags) { 106 g_debug("cgroup new called"); 107 mocks.cgorup_new_calls++; 108 mocks.new_tag = g_strdup(security_tag); 109 mocks.new_flags = flags; 110 return (sc_device_cgroup *)mocks.new_ret; 111 } 112 113 int sc_device_cgroup_allow(sc_device_cgroup *self, int kind, int major, int minor) { 114 mocks.cgroup_allow_calls++; 115 mocks.device_type = kind; 116 mocks.device_major = major; 117 mocks.device_minor = minor; 118 return 0; 119 } 120 121 int sc_device_cgroup_deny(sc_device_cgroup *self, int kind, int major, int minor) { 122 mocks.cgroup_deny_calls++; 123 mocks.device_type = kind; 124 mocks.device_major = major; 125 mocks.device_minor = minor; 126 return 0; 127 } 128 129 struct sdh_test_data { 130 char *action; 131 // snap.foo.bar 132 char *app; 133 // snap_foo_bar 134 char *mangled_appname; 135 }; 136 137 static void test_sdh_action(sdh_test_fixture *fixture, gconstpointer test_data) { 138 struct sdh_test_data *td = (struct sdh_test_data *)test_data; 139 140 struct sdh_invocation inv_block = { 141 .action = td->action, 142 .tagname = td->mangled_appname, 143 .devpath = "/devices/foo/block/sda/sda4", 144 .majmin = "8:4", 145 }; 146 147 mkdir_in_sysroot(fixture, "/sys/devices/foo/block/sda/sda4"); 148 symlink_in_sysroot(fixture, "/sys/devices/foo/block/sda/sda4/subsystem", "../../../../../class/block"); 149 150 int bogus = 0; 151 /* make cgroup_device_new return a non-NULL */ 152 mocks.new_ret = &bogus; 153 154 int ret = snap_device_helper_run(&inv_block); 155 g_assert_cmpint(ret, ==, 0); 156 g_assert_cmpint(mocks.cgorup_new_calls, ==, 1); 157 if (g_strcmp0(td->action, "add") == 0 || g_strcmp0(td->action, "change") == 0) { 158 g_assert_cmpint(mocks.cgroup_allow_calls, ==, 1); 159 g_assert_cmpint(mocks.cgroup_deny_calls, ==, 0); 160 } else if (g_strcmp0(td->action, "remove") == 0) { 161 g_assert_cmpint(mocks.cgroup_allow_calls, ==, 0); 162 g_assert_cmpint(mocks.cgroup_deny_calls, ==, 1); 163 } 164 g_assert_cmpint(mocks.device_major, ==, 8); 165 g_assert_cmpint(mocks.device_minor, ==, 4); 166 g_assert_cmpint(mocks.device_type, ==, S_IFBLK); 167 g_assert_nonnull(mocks.new_tag); 168 g_assert_nonnull(td->app); 169 g_assert_cmpstr(mocks.new_tag, ==, td->app); 170 g_assert_cmpint(mocks.new_flags, !=, 0); 171 g_assert_cmpint(mocks.new_flags, ==, SC_DEVICE_CGROUP_FROM_EXISTING); 172 173 g_debug("reset"); 174 mocks_reset(); 175 mocks.new_ret = &bogus; 176 177 struct sdh_invocation inv_serial = { 178 .action = td->action, 179 .tagname = td->mangled_appname, 180 .devpath = "/devices/foo/tty/ttyS0", 181 .majmin = "6:64", 182 }; 183 mkdir_in_sysroot(fixture, "/sys/devices/foo/tty/ttyS0"); 184 symlink_in_sysroot(fixture, "/sys/devices/foo/tty/ttyS0/subsystem", "../../../../class/other"); 185 ret = snap_device_helper_run(&inv_serial); 186 g_assert_cmpint(ret, ==, 0); 187 g_assert_cmpint(mocks.cgorup_new_calls, ==, 1); 188 if (g_strcmp0(td->action, "add") == 0 || g_strcmp0(td->action, "change") == 0) { 189 g_assert_cmpint(mocks.cgroup_allow_calls, ==, 1); 190 g_assert_cmpint(mocks.cgroup_deny_calls, ==, 0); 191 } else if (g_strcmp0(td->action, "remove") == 0) { 192 g_assert_cmpint(mocks.cgroup_allow_calls, ==, 0); 193 g_assert_cmpint(mocks.cgroup_deny_calls, ==, 1); 194 } 195 g_assert_cmpint(mocks.device_major, ==, 6); 196 g_assert_cmpint(mocks.device_minor, ==, 64); 197 g_assert_cmpint(mocks.device_type, ==, S_IFCHR); 198 g_assert_nonnull(mocks.new_tag); 199 g_assert_nonnull(td->app); 200 g_assert_cmpstr(mocks.new_tag, ==, td->app); 201 g_assert_cmpint(mocks.new_flags, !=, 0); 202 g_assert_cmpint(mocks.new_flags, ==, SC_DEVICE_CGROUP_FROM_EXISTING); 203 } 204 205 static void test_sdh_action_nvme(sdh_test_fixture *fixture, gconstpointer test_data) { 206 /* hierarchy from an actual system with a nvme disk */ 207 mkdir_in_sysroot(fixture, "/sys/devices/pci0000:00/0000:00:01.1/0000:01:00.0/nvme/nvme0/nvme0n1"); 208 mkdir_in_sysroot(fixture, "/sys/devices/pci0000:00/0000:00:01.1/0000:01:00.0/nvme/nvme0/nvme0n1p1"); 209 mkdir_in_sysroot(fixture, "/sys/devices/pci0000:00/0000:00:01.1/0000:01:00.0/nvme/nvme0/ng0n1"); 210 mkdir_in_sysroot(fixture, "/sys/devices/pci0000:00/0000:00:01.1/0000:01:00.0/nvme/nvme0/hwmon0"); 211 symlink_in_sysroot(fixture, "/sys//devices/pci0000:00/0000:00:01.1/0000:01:00.0/nvme/nvme0/nvme0n1/subsystem", 212 "../../../../../../../class/block"); 213 symlink_in_sysroot(fixture, "/sys//devices/pci0000:00/0000:00:01.1/0000:01:00.0/nvme/nvme0/nvme0n1p1/subsystem", 214 "../../../../../../../class/block"); 215 symlink_in_sysroot(fixture, "/sys//devices/pci0000:00/0000:00:01.1/0000:01:00.0/nvme/nvme0/subsystem", 216 "../../../../../../class/nvme"); 217 symlink_in_sysroot(fixture, "/sys//devices/pci0000:00/0000:00:01.1/0000:01:00.0/nvme/nvme0/ng0n1/subsystem", 218 "../../../../../../class/nvme-generic"); 219 symlink_in_sysroot(fixture, "/sys//devices/pci0000:00/0000:00:01.1/0000:01:00.0/nvme/nvme0/hwmon0/subsystem", 220 "../../../../../../class/hwmon"); 221 222 struct { 223 const char *dev; 224 const char *majmin; 225 int expected_maj; 226 int expected_min; 227 int expected_type; 228 } tcs[] = { 229 { 230 .dev = "/devices/pci0000:00/0000:00:01.1/0000:01:00.0/nvme/nvme0/nvme0n1", 231 .majmin = "259:0", 232 .expected_maj = 259, 233 .expected_min = 0, 234 .expected_type = S_IFBLK, 235 }, 236 { 237 .dev = "/devices/pci0000:00/0000:00:01.1/0000:01:00.0/nvme/nvme0/nvme0n1p1", 238 .majmin = "259:1", 239 .expected_maj = 259, 240 .expected_min = 1, 241 .expected_type = S_IFBLK, 242 }, 243 { 244 .dev = "/devices/pci0000:00/0000:00:01.1/0000:01:00.0/nvme/nvme0", 245 .majmin = "242:0", 246 .expected_maj = 242, 247 .expected_min = 0, 248 .expected_type = S_IFCHR, 249 }, 250 { 251 .dev = "/devices/pci0000:00/0000:00:01.1/0000:01:00.0/nvme/nvme0/hwmon0", 252 .majmin = "241:0", 253 .expected_maj = 241, 254 .expected_min = 0, 255 .expected_type = S_IFCHR, 256 }, 257 }; 258 259 int bogus = 0; 260 261 for (size_t i = 0; i < sizeof(tcs) / sizeof(tcs[0]); i++) { 262 mocks_reset(); 263 /* make cgroup_device_new return a non-NULL */ 264 mocks.new_ret = &bogus; 265 266 struct sdh_invocation inv_block = { 267 .action = "add", 268 .tagname = "snap_foo_bar", 269 .devpath = tcs[i].dev, 270 .majmin = tcs[i].majmin, 271 }; 272 int ret = snap_device_helper_run(&inv_block); 273 g_assert_cmpint(ret, ==, 0); 274 g_assert_cmpint(mocks.cgorup_new_calls, ==, 1); 275 g_assert_cmpint(mocks.cgroup_allow_calls, ==, 1); 276 g_assert_cmpint(mocks.cgroup_deny_calls, ==, 0); 277 g_assert_cmpint(mocks.device_major, ==, tcs[i].expected_maj); 278 g_assert_cmpint(mocks.device_minor, ==, tcs[i].expected_min); 279 g_assert_cmpint(mocks.device_type, ==, tcs[i].expected_type); 280 g_assert_cmpint(mocks.new_flags, !=, 0); 281 g_assert_cmpint(mocks.new_flags, ==, SC_DEVICE_CGROUP_FROM_EXISTING); 282 } 283 } 284 285 static void run_sdh_die(const char *action, const char *tagname, const char *devpath, const char *majmin, 286 const char *msg) { 287 struct sdh_invocation inv = { 288 .action = action, 289 .tagname = tagname, 290 .devpath = devpath, 291 .majmin = majmin, 292 }; 293 if (g_test_subprocess()) { 294 errno = 0; 295 snap_device_helper_run(&inv); 296 } 297 g_test_trap_subprocess(NULL, 0, 0); 298 g_test_trap_assert_failed(); 299 g_test_trap_assert_stderr(msg); 300 } 301 302 static void test_sdh_err_noappname(sdh_test_fixture *fixture, gconstpointer test_data) { 303 // missing appname 304 run_sdh_die("add", "", "/devices/foo/block/sda/sda4", "8:4", "malformed tag \"\"\n"); 305 } 306 307 static void test_sdh_err_badappname(sdh_test_fixture *fixture, gconstpointer test_data) { 308 // malformed appname 309 run_sdh_die("add", "foo_bar", "/devices/foo/block/sda/sda4", "8:4", "malformed tag \"foo_bar\"\n"); 310 } 311 static void test_sdh_err_nodevpath(sdh_test_fixture *fixture, gconstpointer test_data) { 312 // missing devpath 313 run_sdh_die("add", "snap_foo_bar", "", "8:4", "no or malformed devpath \"\"\n"); 314 } 315 316 static void test_sdh_err_wrongdevmajorminor1(sdh_test_fixture *fixture, gconstpointer test_data) { 317 // missing device major:minor numbers 318 run_sdh_die("add", "snap_foo_bar", "/devices/foo/block/sda/sda4", "", "no or malformed major/minor \"\"\n"); 319 } 320 321 static void test_sdh_err_wrongdevmajorminor2(sdh_test_fixture *fixture, gconstpointer test_data) { 322 // too short major:minor numbers 323 run_sdh_die("add", "snap_foo_bar", "/devices/foo/block/sda/sda4", "8", "no or malformed major/minor \"8\"\n"); 324 } 325 326 static void test_sdh_err_wrongdevmajorminor_late1(sdh_test_fixture *fixture, gconstpointer test_data) { 327 // mock enough to the major:minor extraction in the code 328 mkdir_in_sysroot(fixture, "/sys/devices/foo/block/sda/sda4"); 329 symlink_in_sysroot(fixture, "/sys/devices/foo/block/sda/sda4/subsystem", "../../../../../class/block"); 330 331 // ensure mocked sc_device_cgroup_new() returns non-NULL 332 int bogus = 0; 333 mocks.new_ret = &bogus; 334 335 // missing ":" 336 run_sdh_die("add", "snap_foo_bar", "/devices/foo/block/sda/sda4", "100", "malformed major:minor string: 100\n"); 337 } 338 339 static void test_sdh_err_wrongdevmajorminor_late2(sdh_test_fixture *fixture, gconstpointer test_data) { 340 // mock enough to the major:minor extraction in the code 341 mkdir_in_sysroot(fixture, "/sys/devices/foo/block/sda/sda4"); 342 symlink_in_sysroot(fixture, "/sys/devices/foo/block/sda/sda4/subsystem", "../../../../../class/block"); 343 344 // ensure mocked sc_device_cgroup_new() returns non-NULL 345 int bogus = 0; 346 mocks.new_ret = &bogus; 347 348 // missing part after ":" 349 run_sdh_die("add", "snap_foo_bar", "/devices/foo/block/sda/sda4", "88:", "malformed major:minor string: 88:\n"); 350 } 351 352 static void test_sdh_err_badaction(sdh_test_fixture *fixture, gconstpointer test_data) { 353 // bogus action 354 run_sdh_die("badaction", "snap_foo_bar", "/devices/foo/block/sda/sda4", "8:4", 355 "ERROR: unknown action \"badaction\"\n"); 356 } 357 358 static void test_sdh_err_nosymlink(sdh_test_fixture *fixture, gconstpointer test_data) { 359 // missing symlink 360 run_sdh_die("add", "snap_foo_bar", "/devices/foo/block/sda/sda4", "8:4", 361 "cannot read symlink */sys//devices/foo/block/sda/sda4/subsystem*\n"); 362 } 363 364 static void test_sdh_err_funtag1(sdh_test_fixture *fixture, gconstpointer test_data) { 365 run_sdh_die("add", "snap___bar", "/devices/foo/block/sda/sda4", "8:4", 366 "security tag \"snap._.bar\" for snap \"_\" is not valid\n"); 367 } 368 369 static void test_sdh_err_funtag2(sdh_test_fixture *fixture, gconstpointer test_data) { 370 run_sdh_die("add", "snap_foobar", "/devices/foo/block/sda/sda4", "8:4", 371 "missing app name in tag \"snap_foobar\"\n"); 372 } 373 374 static void test_sdh_err_funtag3(sdh_test_fixture *fixture, gconstpointer test_data) { 375 run_sdh_die("add", "snap_", "/devices/foo/block/sda/sda4", "8:4", "tag \"snap_\" length 5 is incorrect\n"); 376 } 377 378 static void test_sdh_err_funtag4(sdh_test_fixture *fixture, gconstpointer test_data) { 379 run_sdh_die("add", "snap_foo_", "/devices/foo/block/sda/sda4", "8:4", 380 "security tag \"snap.foo.\" for snap \"foo\" is not valid\n"); 381 } 382 383 static void test_sdh_err_funtag5(sdh_test_fixture *fixture, gconstpointer test_data) { 384 run_sdh_die( 385 "add", "snap_thisisverylonginstancenameabovelengthlimit_instancekey_bar", "/devices/foo/block/sda/sda4", "8:4", 386 "snap instance of tag \"snap_thisisverylonginstancenameabovelengthlimit_instancekey_bar\" is too long\n"); 387 } 388 389 static void test_sdh_err_funtag6(sdh_test_fixture *fixture, gconstpointer test_data) { 390 run_sdh_die("add", "snap__barbar", "/devices/foo/block/sda/sda4", "8:4", 391 "missing snap name in tag \"snap__barbar\"\n"); 392 } 393 394 static void test_sdh_err_funtag7(sdh_test_fixture *fixture, gconstpointer test_data) { 395 run_sdh_die("add", "snap_barbarbarbar", "/devices/foo/block/sda/sda4", "8:4", 396 "missing app name in tag \"snap_barbarbarbar\"\n"); 397 } 398 399 static void test_sdh_err_funtag8(sdh_test_fixture *fixture, gconstpointer test_data) { 400 run_sdh_die("add", "snap_#_barbar", "/devices/foo/block/sda/sda4", "8:4", 401 "security tag \"snap.#.barbar\" for snap \"#\" is not valid\n"); 402 } 403 404 static struct sdh_test_data add_data = {"add", "snap.foo.bar", "snap_foo_bar"}; 405 static struct sdh_test_data change_data = {"change", "snap.foo.bar", "snap_foo_bar"}; 406 407 static struct sdh_test_data remove_data = {"remove", "snap.foo.bar", "snap_foo_bar"}; 408 409 static struct sdh_test_data instance_add_data = {"add", "snap.foo_bar.baz", "snap_foo_bar_baz"}; 410 411 static struct sdh_test_data instance_change_data = {"change", "snap.foo_bar.baz", "snap_foo_bar_baz"}; 412 413 static struct sdh_test_data instance_remove_data = {"remove", "snap.foo_bar.baz", "snap_foo_bar_baz"}; 414 415 static struct sdh_test_data add_hook_data = {"add", "snap.foo.hook.configure", "snap_foo_hook_configure"}; 416 417 static struct sdh_test_data instance_add_hook_data = {"add", "snap.foo_bar.hook.configure", 418 "snap_foo_bar_hook_configure"}; 419 420 static struct sdh_test_data instance_add_instance_name_is_hook_data = {"add", "snap.foo_hook.hook.configure", 421 "snap_foo_hook_hook_configure"}; 422 423 static void __attribute__((constructor)) init(void) { 424 #define _test_add(_name, _data, _func) \ 425 g_test_add(_name, sdh_test_fixture, _data, sdh_test_set_up, _func, sdh_test_tear_down) 426 427 _test_add("/snap-device-helper/add", &add_data, test_sdh_action); 428 _test_add("/snap-device-helper/change", &change_data, test_sdh_action); 429 _test_add("/snap-device-helper/remove", &remove_data, test_sdh_action); 430 431 _test_add("/snap-device-helper/err/no-appname", NULL, test_sdh_err_noappname); 432 _test_add("/snap-device-helper/err/bad-appname", NULL, test_sdh_err_badappname); 433 _test_add("/snap-device-helper/err/no-devpath", NULL, test_sdh_err_nodevpath); 434 _test_add("/snap-device-helper/err/wrong-devmajorminor1", NULL, test_sdh_err_wrongdevmajorminor1); 435 _test_add("/snap-device-helper/err/wrong-devmajorminor2", NULL, test_sdh_err_wrongdevmajorminor2); 436 _test_add("/snap-device-helper/err/wrong-devmajorminor_late1", NULL, test_sdh_err_wrongdevmajorminor_late1); 437 _test_add("/snap-device-helper/err/wrong-devmajorminor_late2", NULL, test_sdh_err_wrongdevmajorminor_late2); 438 _test_add("/snap-device-helper/err/bad-action", NULL, test_sdh_err_badaction); 439 _test_add("/snap-device-helper/err/no-symlink", NULL, test_sdh_err_nosymlink); 440 _test_add("/snap-device-helper/err/funtag1", NULL, test_sdh_err_funtag1); 441 _test_add("/snap-device-helper/err/funtag2", NULL, test_sdh_err_funtag2); 442 _test_add("/snap-device-helper/err/funtag3", NULL, test_sdh_err_funtag3); 443 _test_add("/snap-device-helper/err/funtag4", NULL, test_sdh_err_funtag4); 444 _test_add("/snap-device-helper/err/funtag5", NULL, test_sdh_err_funtag5); 445 _test_add("/snap-device-helper/err/funtag6", NULL, test_sdh_err_funtag6); 446 _test_add("/snap-device-helper/err/funtag7", NULL, test_sdh_err_funtag7); 447 _test_add("/snap-device-helper/err/funtag8", NULL, test_sdh_err_funtag8); 448 // parallel instances 449 _test_add("/snap-device-helper/parallel/add", &instance_add_data, test_sdh_action); 450 _test_add("/snap-device-helper/parallel/change", &instance_change_data, test_sdh_action); 451 _test_add("/snap-device-helper/parallel/remove", &instance_remove_data, test_sdh_action); 452 // hooks 453 _test_add("/snap-device-helper/hook/add", &add_hook_data, test_sdh_action); 454 _test_add("/snap-device-helper/hook/parallel/add", &instance_add_hook_data, test_sdh_action); 455 _test_add("/snap-device-helper/hook-name-hook/parallel/add", &instance_add_instance_name_is_hook_data, 456 test_sdh_action); 457 458 _test_add("/snap-device-helper/nvme", NULL, test_sdh_action_nvme); 459 }