github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/cmd/snap-confine/udev-support.c (about)

     1  /*
     2   * Copyright (C) 2015-2016 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  #include "config.h"
    18  
    19  #include <ctype.h>
    20  #include <errno.h>
    21  #include <limits.h>
    22  #include <sys/sysmacros.h>
    23  #include <sched.h>
    24  #include <string.h>
    25  #include <sys/stat.h>
    26  #include <sys/types.h>
    27  #include <sys/wait.h>
    28  #include <unistd.h>
    29  
    30  #include "../libsnap-confine-private/snap.h"
    31  #include "../libsnap-confine-private/string-utils.h"
    32  #include "../libsnap-confine-private/utils.h"
    33  #include "udev-support.h"
    34  
    35  static void
    36  _run_snappy_app_dev_add_majmin(struct snappy_udev *udev_s,
    37  			       const char *path, unsigned major, unsigned minor)
    38  {
    39  	int status = 0;
    40  	pid_t pid = fork();
    41  	if (pid < 0) {
    42  		die("cannot fork support process for device cgroup assignment");
    43  	}
    44  	if (pid == 0) {
    45  		uid_t real_uid, effective_uid, saved_uid;
    46  		if (getresuid(&real_uid, &effective_uid, &saved_uid) != 0)
    47  			die("cannot get real, effective and saved user IDs");
    48  		// can't update the cgroup unless the real_uid is 0, euid as
    49  		// 0 is not enough
    50  		if (real_uid != 0 && effective_uid == 0)
    51  			if (setuid(0) != 0)
    52  				die("cannot set user ID to zero");
    53  		char buf[64] = { 0 };
    54  		// pass snappy-add-dev an empty environment so the
    55  		// user-controlled environment can't be used to subvert
    56  		// snappy-add-dev
    57  		char *env[] = { NULL };
    58  		if (minor == UINT_MAX) {
    59  			sc_must_snprintf(buf, sizeof(buf), "%u:*", major);
    60  		} else {
    61  			sc_must_snprintf(buf, sizeof(buf), "%u:%u", major,
    62  					 minor);
    63  		}
    64  		debug("running snap-device-helper add %s %s %s",
    65  		      udev_s->tagname, path, buf);
    66  		// This code runs inside the core snap. We have two paths
    67  		// for the udev helper.
    68  		//
    69  		// First try new "snap-device-helper" path first but
    70  		// when running against an older core snap fallback to
    71  		// the old name.
    72  		if (access("/usr/lib/snapd/snap-device-helper", X_OK) == 0)
    73  			execle("/usr/lib/snapd/snap-device-helper",
    74  			       "/usr/lib/snapd/snap-device-helper", "add",
    75  			       udev_s->tagname, path, buf, NULL, env);
    76  		else if (access("/usr/libexec/snapd/snap-device-helper", X_OK) == 0)
    77  			execle("/usr/libexec/snapd/snap-device-helper",
    78  			       "/usr/libexec/snapd/snap-device-helper", "add",
    79  			       udev_s->tagname, path, buf, NULL, env);
    80  		else
    81  			execle("/lib/udev/snappy-app-dev",
    82  			       "/lib/udev/snappy-app-dev", "add",
    83  			       udev_s->tagname, path, buf, NULL, env);
    84  		die("execl failed");
    85  	}
    86  	if (waitpid(pid, &status, 0) < 0)
    87  		die("waitpid failed");
    88  	if (WIFEXITED(status) && WEXITSTATUS(status) != 0)
    89  		die("child exited with status %i", WEXITSTATUS(status));
    90  	else if (WIFSIGNALED(status))
    91  		die("child died with signal %i", WTERMSIG(status));
    92  }
    93  
    94  void run_snappy_app_dev_add(struct snappy_udev *udev_s, const char *path)
    95  {
    96  	if (udev_s == NULL)
    97  		die("snappy_udev is NULL");
    98  	if (udev_s->udev == NULL)
    99  		die("snappy_udev->udev is NULL");
   100  	if (udev_s->tagname_len == 0
   101  	    || udev_s->tagname_len >= MAX_BUF
   102  	    || strnlen(udev_s->tagname, MAX_BUF) != udev_s->tagname_len
   103  	    || udev_s->tagname[udev_s->tagname_len] != '\0')
   104  		die("snappy_udev->tagname has invalid length");
   105  
   106  	debug("%s: %s %s", __func__, path, udev_s->tagname);
   107  
   108  	struct udev_device *d =
   109  	    udev_device_new_from_syspath(udev_s->udev, path);
   110  	// udev may be late with registering the device, e.g. under high load, and
   111  	// udev lookup will fail.
   112  	if (d == NULL) {
   113  		debug("cannot find device from syspath %s", path);
   114  		return;
   115  	}
   116  	dev_t devnum = udev_device_get_devnum(d);
   117  	udev_device_unref(d);
   118  
   119  	unsigned int devmaj = major(devnum);
   120  	unsigned int devmin = minor(devnum);
   121  	// per udev_device_get_devnum man page, on failure a device type with minor
   122  	// and major number set to 0 is returned.
   123  	if (devmaj == 0 && devmin == 0) {
   124  		debug("cannot get major/minor numbers for %s", path);
   125  	} else {
   126  		_run_snappy_app_dev_add_majmin(udev_s, path, devmaj, devmin);
   127  	}
   128  }
   129  
   130  /*
   131   * snappy_udev_init() - setup the snappy_udev structure. Return 0 if devices
   132   * are assigned, else return -1. Callers should use snappy_udev_cleanup() to
   133   * cleanup.
   134   */
   135  int snappy_udev_init(const char *security_tag, struct snappy_udev *udev_s)
   136  {
   137  	debug("%s", __func__);
   138  	int rc = 0;
   139  
   140  	udev_s->tagname[0] = '\0';
   141  	udev_s->tagname_len = 0;
   142  	// TAG+="snap_<security tag>" (udev doesn't like '.' in the tag name)
   143  	udev_s->tagname_len = sc_must_snprintf(udev_s->tagname, MAX_BUF,
   144  					       "%s", security_tag);
   145  	for (size_t i = 0; i < udev_s->tagname_len; i++)
   146  		if (udev_s->tagname[i] == '.')
   147  			udev_s->tagname[i] = '_';
   148  
   149  	udev_s->udev = udev_new();
   150  	if (udev_s->udev == NULL)
   151  		die("udev_new failed");
   152  
   153  	udev_s->devices = udev_enumerate_new(udev_s->udev);
   154  	if (udev_s->devices == NULL)
   155  		die("udev_enumerate_new failed");
   156  
   157  	if (udev_enumerate_add_match_tag(udev_s->devices, udev_s->tagname) < 0)
   158  		die("udev_enumerate_add_match_tag");
   159  
   160  	if (udev_enumerate_scan_devices(udev_s->devices) < 0)
   161  		die("udev_enumerate_scan failed");
   162  
   163  	udev_s->assigned = udev_enumerate_get_list_entry(udev_s->devices);
   164  	if (udev_s->assigned == NULL)
   165  		rc = -1;
   166  
   167  	return rc;
   168  }
   169  
   170  void snappy_udev_cleanup(struct snappy_udev *udev_s)
   171  {
   172  	// udev_s->assigned does not need to be unreferenced since it is a
   173  	// pointer into udev_s->devices
   174  	if (udev_s->devices != NULL)
   175  		udev_enumerate_unref(udev_s->devices);
   176  	if (udev_s->udev != NULL)
   177  		udev_unref(udev_s->udev);
   178  }
   179  
   180  void setup_devices_cgroup(const char *security_tag, struct snappy_udev *udev_s)
   181  {
   182  	debug("%s", __func__);
   183  	// Hardcode major/minor for common sysfs devices to avoid udev lookup
   184  	struct {
   185  		char *name;
   186  		unsigned int maj;
   187  		unsigned int min;
   188  	} static_devices[] = {
   189  		{.name="/sys/class/mem/null", .maj=1, .min=3},
   190  		{.name="/sys/class/mem/full", .maj=1, .min=7},
   191  		{.name="/sys/class/mem/zero", .maj=1, .min=5},
   192  		{.name="/sys/class/mem/random", .maj=1, .min=8},
   193  		{.name="/sys/class/mem/urandom", .maj=1, .min=9},
   194  		{.name="/sys/class/tty/tty", .maj=5, .min=0},
   195  		{.name="/sys/class/tty/console", .maj=5, .min=1},
   196  		{.name="/sys/class/tty/ptmx", .maj=5, .min=2},
   197  		{.name=NULL, .maj=0, .min=0}
   198  	};
   199  
   200  	if (udev_s == NULL)
   201  		die("snappy_udev is NULL");
   202  	if (udev_s->udev == NULL)
   203  		die("snappy_udev->udev is NULL");
   204  	if (udev_s->devices == NULL)
   205  		die("snappy_udev->devices is NULL");
   206  	if (udev_s->assigned == NULL)
   207  		die("snappy_udev->assigned is NULL");
   208  	if (udev_s->tagname_len == 0
   209  	    || udev_s->tagname_len >= MAX_BUF
   210  	    || strnlen(udev_s->tagname, MAX_BUF) != udev_s->tagname_len
   211  	    || udev_s->tagname[udev_s->tagname_len] != '\0')
   212  		die("snappy_udev->tagname has invalid length");
   213  
   214  	// create devices cgroup controller
   215  	char cgroup_dir[PATH_MAX] = { 0 };
   216  
   217  	sc_must_snprintf(cgroup_dir, sizeof(cgroup_dir),
   218  			 "/sys/fs/cgroup/devices/%s/", security_tag);
   219  	sc_identity old = sc_set_effective_identity(sc_root_group_identity());
   220  	if (mkdir(cgroup_dir, 0755) < 0 && errno != EEXIST)
   221  		die("cannot create cgroup hierarchy %s", cgroup_dir);
   222  	(void)sc_set_effective_identity(old);
   223  
   224  	// move ourselves into it
   225  	char cgroup_file[PATH_MAX] = { 0 };
   226  	sc_must_snprintf(cgroup_file, sizeof(cgroup_file), "%s%s", cgroup_dir,
   227  			 "cgroup.procs");
   228  
   229  	char buf[128] = { 0 };
   230  	sc_must_snprintf(buf, sizeof(buf), "%i", getpid());
   231  	write_string_to_file(cgroup_file, buf);
   232  
   233  	// deny by default. Write 'a' to devices.deny to remove all existing
   234  	// devices that were added in previous launcher invocations, then add
   235  	// the static and assigned devices. This ensures that at application
   236  	// launch the cgroup only has what is currently assigned.
   237  	sc_must_snprintf(cgroup_file, sizeof(cgroup_file), "%s%s", cgroup_dir,
   238  			 "devices.deny");
   239  	write_string_to_file(cgroup_file, "a");
   240  
   241  	// add the common devices
   242  	for (int i = 0; static_devices[i].name != NULL; i++)
   243  		_run_snappy_app_dev_add_majmin(udev_s, static_devices[i].name,
   244  				static_devices[i].maj, static_devices[i].min);
   245  
   246  	// add glob for current and future PTY slaves. We unconditionally add
   247  	// them since we use a devpts newinstance. Unix98 PTY slaves major
   248  	// are 136-143.
   249  	// https://github.com/torvalds/linux/blob/master/Documentation/admin-guide/devices.txt
   250  	for (unsigned pty_major = 136; pty_major <= 143; pty_major++) {
   251  		// '/dev/pts/slaves' is only used for debugging and by
   252  		// /usr/lib/snapd/snap-device-helper to determine if it is a block
   253  		// device, so just use something to indicate what the
   254  		// addition is for
   255  		_run_snappy_app_dev_add_majmin(udev_s, "/dev/pts/slaves",
   256  					       pty_major, UINT_MAX);
   257  	}
   258  
   259  	// nvidia modules are proprietary and therefore aren't in sysfs and
   260  	// can't be udev tagged. For now, just add existing nvidia devices to
   261  	// the cgroup unconditionally (AppArmor will still mediate the access).
   262  	// We'll want to rethink this if snapd needs to mediate access to other
   263  	// proprietary devices.
   264  	//
   265  	// Device major and minor numbers are described in (though nvidia-uvm
   266  	// currently isn't listed):
   267  	// https://github.com/torvalds/linux/blob/master/Documentation/admin-guide/devices.txt
   268  	char nv_path[15] = { 0 };	// /dev/nvidiaXXX
   269  	const char *nvctl_path = "/dev/nvidiactl";
   270  	const char *nvuvm_path = "/dev/nvidia-uvm";
   271  	const char *nvidia_modeset_path = "/dev/nvidia-modeset";
   272  
   273  	struct stat sbuf;
   274  
   275  	// /dev/nvidia0 through /dev/nvidia254
   276  	for (unsigned nv_minor = 0; nv_minor < 255; nv_minor++) {
   277  		sc_must_snprintf(nv_path, sizeof(nv_path), "/dev/nvidia%u",
   278  				 nv_minor);
   279  
   280  		// Stop trying to find devices after one is not found. In this
   281  		// manner, we'll add /dev/nvidia0 and /dev/nvidia1 but stop
   282  		// trying to find nvidia3 - nvidia254 if nvidia2 is not found.
   283  		if (stat(nv_path, &sbuf) != 0) {
   284  			break;
   285  		}
   286  		_run_snappy_app_dev_add_majmin(udev_s, nv_path,
   287  					       major(sbuf.st_rdev),
   288  					       minor(sbuf.st_rdev));
   289  	}
   290  
   291  	// /dev/nvidiactl
   292  	if (stat(nvctl_path, &sbuf) == 0) {
   293  		_run_snappy_app_dev_add_majmin(udev_s, nvctl_path,
   294  					       major(sbuf.st_rdev),
   295  					       minor(sbuf.st_rdev));
   296  	}
   297  	// /dev/nvidia-uvm
   298  	if (stat(nvuvm_path, &sbuf) == 0) {
   299  		_run_snappy_app_dev_add_majmin(udev_s, nvuvm_path,
   300  					       major(sbuf.st_rdev),
   301  					       minor(sbuf.st_rdev));
   302  	}
   303  	// /dev/nvidia-modeset
   304  	if (stat(nvidia_modeset_path, &sbuf) == 0) {
   305  		_run_snappy_app_dev_add_majmin(udev_s, nvidia_modeset_path,
   306  					       major(sbuf.st_rdev),
   307  					       minor(sbuf.st_rdev));
   308  	}
   309  	// /dev/uhid isn't represented in sysfs, so add it to the device cgroup
   310  	// if it exists and let AppArmor handle the mediation
   311  	if (stat("/dev/uhid", &sbuf) == 0) {
   312  		_run_snappy_app_dev_add_majmin(udev_s, "/dev/uhid",
   313  					       major(sbuf.st_rdev),
   314  					       minor(sbuf.st_rdev));
   315  	}
   316  	// When CONFIG_TUN=m, /dev/net/tun will exist but using it doesn't
   317  	// autoload the tun module but also /dev/net/tun isn't udev tagged
   318  	// until it is loaded. To work around this, if /dev/net/tun exists, add
   319  	// it unconditionally to the cgroup and rely on AppArmor to mediate the
   320  	// access. LP: #1859084
   321  	if (stat("/dev/net/tun", &sbuf) == 0) {
   322  		_run_snappy_app_dev_add_majmin(udev_s, "/dev/net/tun",
   323  					       major(sbuf.st_rdev),
   324  					       minor(sbuf.st_rdev));
   325  	}
   326  	// add the assigned devices
   327  	while (udev_s->assigned != NULL) {
   328  		const char *path = udev_list_entry_get_name(udev_s->assigned);
   329  		if (path == NULL)
   330  			die("udev_list_entry_get_name failed");
   331  		run_snappy_app_dev_add(udev_s, path);
   332  		udev_s->assigned = udev_list_entry_get_next(udev_s->assigned);
   333  	}
   334  }