github.com/rigado/snapd@v2.42.5-go-mod+incompatible/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  	if (d == NULL)
   111  		die("cannot find device from syspath %s", path);
   112  	dev_t devnum = udev_device_get_devnum(d);
   113  	udev_device_unref(d);
   114  
   115  	_run_snappy_app_dev_add_majmin(udev_s, path, major(devnum),
   116  				       minor(devnum));
   117  }
   118  
   119  /*
   120   * snappy_udev_init() - setup the snappy_udev structure. Return 0 if devices
   121   * are assigned, else return -1. Callers should use snappy_udev_cleanup() to
   122   * cleanup.
   123   */
   124  int snappy_udev_init(const char *security_tag, struct snappy_udev *udev_s)
   125  {
   126  	debug("%s", __func__);
   127  	int rc = 0;
   128  
   129  	udev_s->tagname[0] = '\0';
   130  	udev_s->tagname_len = 0;
   131  	// TAG+="snap_<security tag>" (udev doesn't like '.' in the tag name)
   132  	udev_s->tagname_len = sc_must_snprintf(udev_s->tagname, MAX_BUF,
   133  					       "%s", security_tag);
   134  	for (size_t i = 0; i < udev_s->tagname_len; i++)
   135  		if (udev_s->tagname[i] == '.')
   136  			udev_s->tagname[i] = '_';
   137  
   138  	udev_s->udev = udev_new();
   139  	if (udev_s->udev == NULL)
   140  		die("udev_new failed");
   141  
   142  	udev_s->devices = udev_enumerate_new(udev_s->udev);
   143  	if (udev_s->devices == NULL)
   144  		die("udev_enumerate_new failed");
   145  
   146  	if (udev_enumerate_add_match_tag(udev_s->devices, udev_s->tagname) < 0)
   147  		die("udev_enumerate_add_match_tag");
   148  
   149  	if (udev_enumerate_scan_devices(udev_s->devices) < 0)
   150  		die("udev_enumerate_scan failed");
   151  
   152  	udev_s->assigned = udev_enumerate_get_list_entry(udev_s->devices);
   153  	if (udev_s->assigned == NULL)
   154  		rc = -1;
   155  
   156  	return rc;
   157  }
   158  
   159  void snappy_udev_cleanup(struct snappy_udev *udev_s)
   160  {
   161  	// udev_s->assigned does not need to be unreferenced since it is a
   162  	// pointer into udev_s->devices
   163  	if (udev_s->devices != NULL)
   164  		udev_enumerate_unref(udev_s->devices);
   165  	if (udev_s->udev != NULL)
   166  		udev_unref(udev_s->udev);
   167  }
   168  
   169  void setup_devices_cgroup(const char *security_tag, struct snappy_udev *udev_s)
   170  {
   171  	debug("%s", __func__);
   172  	// Devices that must always be present
   173  	const char *static_devices[] = {
   174  		"/sys/class/mem/null",
   175  		"/sys/class/mem/full",
   176  		"/sys/class/mem/zero",
   177  		"/sys/class/mem/random",
   178  		"/sys/class/mem/urandom",
   179  		"/sys/class/tty/tty",
   180  		"/sys/class/tty/console",
   181  		"/sys/class/tty/ptmx",
   182  		NULL,
   183  	};
   184  
   185  	if (udev_s == NULL)
   186  		die("snappy_udev is NULL");
   187  	if (udev_s->udev == NULL)
   188  		die("snappy_udev->udev is NULL");
   189  	if (udev_s->devices == NULL)
   190  		die("snappy_udev->devices is NULL");
   191  	if (udev_s->assigned == NULL)
   192  		die("snappy_udev->assigned is NULL");
   193  	if (udev_s->tagname_len == 0
   194  	    || udev_s->tagname_len >= MAX_BUF
   195  	    || strnlen(udev_s->tagname, MAX_BUF) != udev_s->tagname_len
   196  	    || udev_s->tagname[udev_s->tagname_len] != '\0')
   197  		die("snappy_udev->tagname has invalid length");
   198  
   199  	// create devices cgroup controller
   200  	char cgroup_dir[PATH_MAX] = { 0 };
   201  
   202  	sc_must_snprintf(cgroup_dir, sizeof(cgroup_dir),
   203  			 "/sys/fs/cgroup/devices/%s/", security_tag);
   204  
   205  	if (mkdir(cgroup_dir, 0755) < 0 && errno != EEXIST)
   206  		die("cannot create cgroup hierarchy %s", cgroup_dir);
   207  
   208  	// move ourselves into it
   209  	char cgroup_file[PATH_MAX] = { 0 };
   210  	sc_must_snprintf(cgroup_file, sizeof(cgroup_file), "%s%s", cgroup_dir,
   211  			 "tasks");
   212  
   213  	char buf[128] = { 0 };
   214  	sc_must_snprintf(buf, sizeof(buf), "%i", getpid());
   215  	write_string_to_file(cgroup_file, buf);
   216  
   217  	// deny by default. Write 'a' to devices.deny to remove all existing
   218  	// devices that were added in previous launcher invocations, then add
   219  	// the static and assigned devices. This ensures that at application
   220  	// launch the cgroup only has what is currently assigned.
   221  	sc_must_snprintf(cgroup_file, sizeof(cgroup_file), "%s%s", cgroup_dir,
   222  			 "devices.deny");
   223  	write_string_to_file(cgroup_file, "a");
   224  
   225  	// add the common devices
   226  	for (int i = 0; static_devices[i] != NULL; i++)
   227  		run_snappy_app_dev_add(udev_s, static_devices[i]);
   228  
   229  	// add glob for current and future PTY slaves. We unconditionally add
   230  	// them since we use a devpts newinstance. Unix98 PTY slaves major
   231  	// are 136-143.
   232  	// https://github.com/torvalds/linux/blob/master/Documentation/admin-guide/devices.txt
   233  	for (unsigned pty_major = 136; pty_major <= 143; pty_major++) {
   234  		// '/dev/pts/slaves' is only used for debugging and by
   235  		// /usr/lib/snapd/snap-device-helper to determine if it is a block
   236  		// device, so just use something to indicate what the
   237  		// addition is for
   238  		_run_snappy_app_dev_add_majmin(udev_s, "/dev/pts/slaves",
   239  					       pty_major, UINT_MAX);
   240  	}
   241  
   242  	// nvidia modules are proprietary and therefore aren't in sysfs and
   243  	// can't be udev tagged. For now, just add existing nvidia devices to
   244  	// the cgroup unconditionally (AppArmor will still mediate the access).
   245  	// We'll want to rethink this if snapd needs to mediate access to other
   246  	// proprietary devices.
   247  	//
   248  	// Device major and minor numbers are described in (though nvidia-uvm
   249  	// currently isn't listed):
   250  	// https://github.com/torvalds/linux/blob/master/Documentation/admin-guide/devices.txt
   251  	char nv_path[15] = { 0 };	// /dev/nvidiaXXX
   252  	const char *nvctl_path = "/dev/nvidiactl";
   253  	const char *nvuvm_path = "/dev/nvidia-uvm";
   254  	const char *nvidia_modeset_path = "/dev/nvidia-modeset";
   255  
   256  	struct stat sbuf;
   257  
   258  	// /dev/nvidia0 through /dev/nvidia254
   259  	for (unsigned nv_minor = 0; nv_minor < 255; nv_minor++) {
   260  		sc_must_snprintf(nv_path, sizeof(nv_path), "/dev/nvidia%u",
   261  				 nv_minor);
   262  
   263  		// Stop trying to find devices after one is not found. In this
   264  		// manner, we'll add /dev/nvidia0 and /dev/nvidia1 but stop
   265  		// trying to find nvidia3 - nvidia254 if nvidia2 is not found.
   266  		if (stat(nv_path, &sbuf) != 0) {
   267  			break;
   268  		}
   269  		_run_snappy_app_dev_add_majmin(udev_s, nv_path,
   270  					       major(sbuf.st_rdev),
   271  					       minor(sbuf.st_rdev));
   272  	}
   273  
   274  	// /dev/nvidiactl
   275  	if (stat(nvctl_path, &sbuf) == 0) {
   276  		_run_snappy_app_dev_add_majmin(udev_s, nvctl_path,
   277  					       major(sbuf.st_rdev),
   278  					       minor(sbuf.st_rdev));
   279  	}
   280  	// /dev/nvidia-uvm
   281  	if (stat(nvuvm_path, &sbuf) == 0) {
   282  		_run_snappy_app_dev_add_majmin(udev_s, nvuvm_path,
   283  					       major(sbuf.st_rdev),
   284  					       minor(sbuf.st_rdev));
   285  	}
   286  	// /dev/nvidia-modeset
   287  	if (stat(nvidia_modeset_path, &sbuf) == 0) {
   288  		_run_snappy_app_dev_add_majmin(udev_s, nvidia_modeset_path,
   289  					       major(sbuf.st_rdev),
   290  					       minor(sbuf.st_rdev));
   291  	}
   292  	// /dev/uhid isn't represented in sysfs, so add it to the device cgroup
   293  	// if it exists and let AppArmor handle the mediation
   294  	if (stat("/dev/uhid", &sbuf) == 0) {
   295  		_run_snappy_app_dev_add_majmin(udev_s, "/dev/uhid",
   296  					       major(sbuf.st_rdev),
   297  					       minor(sbuf.st_rdev));
   298  	}
   299  	// add the assigned devices
   300  	while (udev_s->assigned != NULL) {
   301  		const char *path = udev_list_entry_get_name(udev_s->assigned);
   302  		if (path == NULL)
   303  			die("udev_list_entry_get_name failed");
   304  		run_snappy_app_dev_add(udev_s, path);
   305  		udev_s->assigned = udev_list_entry_get_next(udev_s->assigned);
   306  	}
   307  }