github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/cmd/snap-confine/snap-device-helper-test.c (about) 1 /* 2 * Copyright (C) 2018 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 <glib.h> 21 #include <glib/gstdio.h> 22 #include <fcntl.h> 23 #include <sys/types.h> 24 #include <sys/stat.h> 25 #include <sys/wait.h> 26 #include <string.h> 27 28 // TODO: build at runtime 29 static const char *sdh_path_default = "snap-confine/snap-device-helper"; 30 31 // A variant of unsetenv that is compatible with GDestroyNotify 32 static void my_unsetenv(const char *k) 33 { 34 g_unsetenv(k); 35 } 36 37 // A variant of rm_rf_tmp that calls g_free() on its parameter 38 static void rm_rf_tmp_free(gchar * dirpath) 39 { 40 rm_rf_tmp(dirpath); 41 g_free(dirpath); 42 } 43 44 static gchar *find_sdh_path(void) 45 { 46 const char *sdh_from_env = g_getenv("SNAP_DEVICE_HELPER"); 47 if (sdh_from_env != NULL) { 48 return g_strdup(sdh_from_env); 49 } 50 return g_strdup(sdh_path_default); 51 } 52 53 static int run_sdh(gchar * action, 54 gchar * appname, gchar * devpath, gchar * majmin) 55 { 56 gchar *mod_appname = g_strdup(appname); 57 gchar *sdh_path = find_sdh_path(); 58 59 // appnames have the following format: 60 // - snap.<snap>.<app> 61 // - snap.<snap>_<instance>.<app> 62 // snap-device-helper expects: 63 // - snap_<snap>_<app> 64 // - snap_<snap>_<instance>_<app> 65 for (size_t i = 0; i < strlen(mod_appname); i++) { 66 if (mod_appname[i] == '.') { 67 mod_appname[i] = '_'; 68 } 69 } 70 g_debug("appname modified from %s to %s", appname, mod_appname); 71 72 GError *err = NULL; 73 74 GPtrArray *argv = g_ptr_array_new(); 75 g_ptr_array_add(argv, sdh_path); 76 g_ptr_array_add(argv, action); 77 g_ptr_array_add(argv, mod_appname); 78 g_ptr_array_add(argv, devpath); 79 g_ptr_array_add(argv, majmin); 80 g_ptr_array_add(argv, NULL); 81 82 int status = 0; 83 84 gboolean ret = g_spawn_sync(NULL, (gchar **) argv->pdata, NULL, 0, 85 NULL, NULL, NULL, NULL, &status, &err); 86 g_free(mod_appname); 87 g_free(sdh_path); 88 g_ptr_array_unref(argv); 89 90 if (!ret) { 91 g_debug("failed with: %s", err->message); 92 g_error_free(err); 93 return -2; 94 } 95 96 return WEXITSTATUS(status); 97 } 98 99 struct sdh_test_data { 100 char *action; 101 // snap.foo.bar 102 char *app; 103 // snap_foo_bar 104 char *mangled_appname; 105 char *file_with_data; 106 char *file_with_no_data; 107 }; 108 109 static void test_sdh_action(gconstpointer test_data) 110 { 111 struct sdh_test_data *td = (struct sdh_test_data *)test_data; 112 113 gchar *mock_dir = g_dir_make_tmp(NULL, NULL); 114 g_assert_nonnull(mock_dir); 115 g_test_queue_destroy((GDestroyNotify) rm_rf_tmp_free, mock_dir); 116 117 gchar *app_dir = g_build_filename(mock_dir, td->app, NULL); 118 gchar *with_data = g_build_filename(mock_dir, 119 td->app, 120 td->file_with_data, 121 NULL); 122 gchar *without_data = g_build_filename(mock_dir, 123 td->app, 124 td->file_with_no_data, 125 NULL); 126 gchar *data = NULL; 127 128 g_assert(g_mkdir_with_parents(app_dir, 0755) == 0); 129 g_free(app_dir); 130 131 g_debug("mock cgroup dir: %s", mock_dir); 132 133 g_setenv("DEVICES_CGROUP", mock_dir, TRUE); 134 135 g_test_queue_destroy((GDestroyNotify) my_unsetenv, "DEVICES_CGROUP"); 136 137 int ret = 138 run_sdh(td->action, td->app, "/devices/foo/block/sda/sda4", "8:4"); 139 g_assert_cmpint(ret, ==, 0); 140 g_assert_true(g_file_get_contents(with_data, &data, NULL, NULL)); 141 g_assert_cmpstr(data, ==, "b 8:4 rwm\n"); 142 g_clear_pointer(&data, g_free); 143 g_assert(g_remove(with_data) == 0); 144 145 g_assert_false(g_file_get_contents(without_data, &data, NULL, NULL)); 146 147 ret = 148 run_sdh(td->action, td->mangled_appname, "/devices/foo/tty/ttyS0", 149 "4:64"); 150 g_assert_cmpint(ret, ==, 0); 151 g_assert_true(g_file_get_contents(with_data, &data, NULL, NULL)); 152 g_assert_cmpstr(data, ==, "c 4:64 rwm\n"); 153 g_clear_pointer(&data, g_free); 154 g_assert(g_remove(with_data) == 0); 155 156 g_assert_false(g_file_get_contents(without_data, &data, NULL, NULL)); 157 158 g_free(with_data); 159 g_free(without_data); 160 } 161 162 static void test_sdh_err(void) 163 { 164 // missing appname 165 int ret = run_sdh("add", "", "/devices/foo/block/sda/sda4", "8:4"); 166 g_assert_cmpint(ret, ==, 1); 167 // malformed appname 168 ret = run_sdh("add", "foo_bar", "/devices/foo/block/sda/sda4", "8:4"); 169 g_assert_cmpint(ret, ==, 1); 170 // missing devpath 171 ret = run_sdh("add", "snap_foo_bar", "", "8:4"); 172 g_assert_cmpint(ret, ==, 1); 173 // missing device major:minor numbers 174 ret = run_sdh("add", "snap_foo_bar", "/devices/foo/block/sda/sda4", ""); 175 g_assert_cmpint(ret, ==, 0); 176 177 // mock some stuff so that we can reach the 'action' checks 178 gchar *mock_dir = g_dir_make_tmp(NULL, NULL); 179 g_assert_nonnull(mock_dir); 180 g_test_queue_destroy((GDestroyNotify) rm_rf_tmp_free, mock_dir); 181 182 gchar *app_dir = g_build_filename(mock_dir, "snap.foo.bar", NULL); 183 g_assert(g_mkdir_with_parents(app_dir, 0755) == 0); 184 g_free(app_dir); 185 g_setenv("DEVICES_CGROUP", mock_dir, TRUE); 186 187 g_test_queue_destroy((GDestroyNotify) my_unsetenv, "DEVICES_CGROUP"); 188 189 ret = 190 run_sdh("badaction", "snap_foo_bar", "/devices/foo/block/sda/sda4", 191 "8:4"); 192 g_assert_cmpint(ret, ==, 1); 193 } 194 195 static struct sdh_test_data add_data = 196 { "add", "snap.foo.bar", "snap_foo_bar", "devices.allow", "devices.deny" }; 197 static struct sdh_test_data change_data = 198 { "change", "snap.foo.bar", "snap_foo_bar", "devices.allow", 199 "devices.deny" 200 }; 201 202 static struct sdh_test_data remove_data = 203 { "remove", "snap.foo.bar", "snap_foo_bar", "devices.deny", 204 "devices.allow" 205 }; 206 207 static struct sdh_test_data instance_add_data = 208 { "add", "snap.foo_bar.baz", "snap_foo_bar_baz", "devices.allow", 209 "devices.deny" 210 }; 211 212 static struct sdh_test_data instance_change_data = 213 { "change", "snap.foo_bar.baz", "snap_foo_bar_baz", "devices.allow", 214 "devices.deny" 215 }; 216 217 static struct sdh_test_data instance_remove_data = 218 { "remove", "snap.foo_bar.baz", "snap_foo_bar_baz", "devices.deny", 219 "devices.allow" 220 }; 221 222 static struct sdh_test_data add_hook_data = 223 { "add", "snap.foo.hook.configure", "snap_foo_hook_configure", 224 "devices.allow", "devices.deny" 225 }; 226 227 static struct sdh_test_data instance_add_hook_data = 228 { "add", "snap.foo_bar.hook.configure", "snap_foo_bar_hook_configure", 229 "devices.allow", "devices.deny" 230 }; 231 232 static struct sdh_test_data instance_add_instance_name_is_hook_data = 233 { "add", "snap.foo_hook.hook.configure", "snap_foo_hook_hook_configure", 234 "devices.allow", "devices.deny" 235 }; 236 237 static void __attribute__((constructor)) init(void) 238 { 239 240 g_test_add_data_func("/snap-device-helper/add", 241 &add_data, test_sdh_action); 242 g_test_add_data_func("/snap-device-helper/change", &change_data, 243 test_sdh_action); 244 g_test_add_data_func("/snap-device-helper/remove", &remove_data, 245 test_sdh_action); 246 g_test_add_func("/snap-device-helper/err", test_sdh_err); 247 g_test_add_data_func("/snap-device-helper/parallel/add", 248 &instance_add_data, test_sdh_action); 249 g_test_add_data_func("/snap-device-helper/parallel/change", 250 &instance_change_data, test_sdh_action); 251 g_test_add_data_func("/snap-device-helper/parallel/remove", 252 &instance_remove_data, test_sdh_action); 253 // hooks 254 g_test_add_data_func("/snap-device-helper/hook/add", 255 &add_hook_data, test_sdh_action); 256 g_test_add_data_func("/snap-device-helper/hook/parallel/add", 257 &instance_add_hook_data, test_sdh_action); 258 g_test_add_data_func("/snap-device-helper/hook-name-hook/parallel/add", 259 &instance_add_instance_name_is_hook_data, 260 test_sdh_action); 261 }