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  }