github.com/anonymouse64/snapd@v0.0.0-20210824153203-04c4c42d842d/cmd/snap-update-ns/bootstrap.c (about)

     1  /*
     2   * Copyright (C) 2017 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  // IMPORTANT: all the code in this file may be run with elevated privileges
    19  // when invoking snap-update-ns from the setuid snap-confine.
    20  //
    21  // This file is a preprocessor for snap-update-ns' main() function. It will
    22  // perform input validation and clear the environment so that snap-update-ns'
    23  // go code runs with safe inputs when called by the setuid() snap-confine.
    24  
    25  #include "bootstrap.h"
    26  
    27  #include <ctype.h>
    28  #include <errno.h>
    29  #include <fcntl.h>
    30  #include <grp.h>
    31  #include <limits.h>
    32  #include <sched.h>
    33  #include <stdbool.h>
    34  #include <stdio.h>
    35  #include <stdlib.h>
    36  #include <string.h>
    37  #include <sys/capability.h>
    38  #include <sys/prctl.h>
    39  #include <sys/stat.h>
    40  #include <sys/types.h>
    41  #include <unistd.h>
    42  
    43  // bootstrap_errno contains a copy of errno if a system call fails.
    44  int bootstrap_errno = 0;
    45  // bootstrap_msg contains a static string if something fails.
    46  const char *bootstrap_msg = NULL;
    47  
    48  // setns_into_snap switches mount namespace into that of a given snap.
    49  static int setns_into_snap(const char *snap_name)
    50  {
    51  	// Construct the name of the .mnt file to open.
    52  	char buf[PATH_MAX] = {
    53  		0,
    54  	};
    55  	int n = snprintf(buf, sizeof buf, "/run/snapd/ns/%s.mnt", snap_name);
    56  	if (n >= sizeof buf || n < 0) {
    57  		bootstrap_errno = 0;
    58  		bootstrap_msg = "cannot format mount namespace file name";
    59  		return -1;
    60  	}
    61  	// Open the mount namespace file.
    62  	int fd = open(buf, O_RDONLY | O_CLOEXEC | O_NOFOLLOW);
    63  	if (fd < 0) {
    64  		bootstrap_errno = errno;
    65  		bootstrap_msg = "cannot open mount namespace file";
    66  		return -1;
    67  	}
    68  	// Switch to the mount namespace of the given snap.
    69  	int err = setns(fd, CLONE_NEWNS);
    70  	if (err < 0) {
    71  		bootstrap_errno = errno;
    72  		bootstrap_msg = "cannot switch mount namespace";
    73  	};
    74  
    75  	close(fd);
    76  	return err;
    77  }
    78  
    79  // switch_to_privileged_user drops to the real user ID while retaining
    80  // CAP_SYS_ADMIN, for operations such as mount().
    81  static int switch_to_privileged_user()
    82  {
    83  	uid_t real_uid;
    84  	gid_t real_gid;
    85  
    86  	real_uid = getuid();
    87  	if (real_uid == 0) {
    88  		// We're running as root: no need to switch IDs
    89  		return 0;
    90  	}
    91  	real_gid = getgid();
    92  
    93  	// _LINUX_CAPABILITY_VERSION_3 valid for kernel >= 2.6.26. See
    94  	// https://github.com/torvalds/linux/blob/master/kernel/capability.c
    95  	struct __user_cap_header_struct hdr =
    96  	    { _LINUX_CAPABILITY_VERSION_3, 0 };
    97  	struct __user_cap_data_struct data[2] = { {0} };
    98  
    99  	data[0].effective = (CAP_TO_MASK(CAP_SYS_ADMIN) |
   100  			     CAP_TO_MASK(CAP_SETUID) | CAP_TO_MASK(CAP_SETGID));
   101  	data[0].permitted = data[0].effective;
   102  	data[0].inheritable = 0;
   103  	data[1].effective = 0;
   104  	data[1].permitted = 0;
   105  	data[1].inheritable = 0;
   106  
   107  	if (capset(&hdr, data) != 0) {
   108  		bootstrap_errno = errno;
   109  		bootstrap_msg = "cannot set permitted capabilities mask";
   110  		return -1;
   111  	}
   112  
   113  	if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) != 0) {
   114  		bootstrap_errno = errno;
   115  		bootstrap_msg =
   116  		    "cannot tell kernel to keep capabilities over setuid";
   117  		return -1;
   118  	}
   119  
   120  	if (setgroups(1, &real_gid) != 0) {
   121  		bootstrap_errno = errno;
   122  		bootstrap_msg = "cannot drop supplementary groups";
   123  		return -1;
   124  	}
   125  
   126  	if (setgid(real_gid) != 0) {
   127  		bootstrap_errno = errno;
   128  		bootstrap_msg = "cannot switch to real group ID";
   129  		return -1;
   130  	}
   131  
   132  	if (setuid(real_uid) != 0) {
   133  		bootstrap_errno = errno;
   134  		bootstrap_msg = "cannot switch to real user ID";
   135  		return -1;
   136  	}
   137  	// After changing uid, our effective capabilities were dropped.
   138  	// Reacquire CAP_SYS_ADMIN, and discard CAP_SETUID/CAP_SETGID.
   139  	data[0].effective = CAP_TO_MASK(CAP_SYS_ADMIN);
   140  	data[0].permitted = data[0].effective;
   141  	if (capset(&hdr, data) != 0) {
   142  		bootstrap_errno = errno;
   143  		bootstrap_msg =
   144  		    "cannot enable capabilities after switching to real user";
   145  		return -1;
   146  	}
   147  
   148  	return 0;
   149  }
   150  
   151  // TODO: reuse the code from snap-confine, if possible.
   152  static int skip_lowercase_letters(const char **p)
   153  {
   154  	int skipped = 0;
   155  	const char *c;
   156  	for (c = *p; *c >= 'a' && *c <= 'z'; ++c) {
   157  		skipped += 1;
   158  	}
   159  	*p = (*p) + skipped;
   160  	return skipped;
   161  }
   162  
   163  // TODO: reuse the code from snap-confine, if possible.
   164  static int skip_digits(const char **p)
   165  {
   166  	int skipped = 0;
   167  	const char *c;
   168  	for (c = *p; *c >= '0' && *c <= '9'; ++c) {
   169  		skipped += 1;
   170  	}
   171  	*p = (*p) + skipped;
   172  	return skipped;
   173  }
   174  
   175  // TODO: reuse the code from snap-confine, if possible.
   176  static int skip_one_char(const char **p, char c)
   177  {
   178  	if (**p == c) {
   179  		*p += 1;
   180  		return 1;
   181  	}
   182  	return 0;
   183  }
   184  
   185  // validate_snap_name performs full validation of the given name.
   186  int validate_snap_name(const char *snap_name)
   187  {
   188  	// NOTE: This function should be synchronized with the two other
   189  	// implementations: sc_snap_name_validate and snap.ValidateName.
   190  
   191  	// Ensure that name is not NULL
   192  	if (snap_name == NULL) {
   193  		bootstrap_msg = "snap name cannot be NULL";
   194  		return -1;
   195  	}
   196  	// This is a regexp-free routine hand-codes the following pattern:
   197  	//
   198  	// "^([a-z0-9]+-?)*[a-z](-?[a-z0-9])*$"
   199  	//
   200  	// The only motivation for not using regular expressions is so that we
   201  	// don't run untrusted input against a potentially complex regular
   202  	// expression engine.
   203  	const char *p = snap_name;
   204  	if (skip_one_char(&p, '-')) {
   205  		bootstrap_msg = "snap name cannot start with a dash";
   206  		return -1;
   207  	}
   208  	bool got_letter = false;
   209  	int n = 0, m;
   210  	for (; *p != '\0';) {
   211  		if ((m = skip_lowercase_letters(&p)) > 0) {
   212  			n += m;
   213  			got_letter = true;
   214  			continue;
   215  		}
   216  		if ((m = skip_digits(&p)) > 0) {
   217  			n += m;
   218  			continue;
   219  		}
   220  		if (skip_one_char(&p, '-') > 0) {
   221  			n++;
   222  			if (*p == '\0') {
   223  				bootstrap_msg =
   224  				    "snap name cannot end with a dash";
   225  				return -1;
   226  			}
   227  			if (skip_one_char(&p, '-') > 0) {
   228  				bootstrap_msg =
   229  				    "snap name cannot contain two consecutive dashes";
   230  				return -1;
   231  			}
   232  			continue;
   233  		}
   234  		bootstrap_msg =
   235  		    "snap name must use lower case letters, digits or dashes";
   236  		return -1;
   237  	}
   238  	if (!got_letter) {
   239  		bootstrap_msg = "snap name must contain at least one letter";
   240  		return -1;
   241  	}
   242  	if (n < 2) {
   243  		bootstrap_msg = "snap name must be longer than 1 character";
   244  		return -1;
   245  	}
   246  	if (n > 40) {
   247  		bootstrap_msg = "snap name must be shorter than 40 characters";
   248  		return -1;
   249  	}
   250  
   251  	bootstrap_msg = NULL;
   252  	return 0;
   253  }
   254  
   255  static int instance_key_validate(const char *instance_key)
   256  {
   257  	// NOTE: see snap.ValidateInstanceName for reference of a valid instance key
   258  	// format
   259  
   260  	// Ensure that name is not NULL
   261  	if (instance_key == NULL) {
   262  		bootstrap_msg = "instance key cannot be NULL";
   263  		return -1;
   264  	}
   265  	// This is a regexp-free routine hand-coding the following pattern:
   266  	//
   267  	// "^[a-z]{1,10}$"
   268  	//
   269  	// The only motivation for not using regular expressions is so that we don't
   270  	// run untrusted input against a potentially complex regular expression
   271  	// engine.
   272  	int i = 0;
   273  	for (i = 0; instance_key[i] != '\0'; i++) {
   274  		char c = instance_key[i];
   275  		/* NOTE: We are reimplementing islower() and isdigit()
   276  		 * here. For context see
   277  		 * https://github.com/golang/go/issues/29689 */
   278  		if ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9')) {
   279  			continue;
   280  		}
   281  		bootstrap_msg =
   282  		    "instance key must use lower case letters or digits";
   283  		return -1;
   284  	}
   285  
   286  	if (i == 0) {
   287  		bootstrap_msg =
   288  		    "instance key must contain at least one letter or digit";
   289  		return -1;
   290  	} else if (i > 10) {
   291  		bootstrap_msg =
   292  		    "instance key must be shorter than 10 characters";
   293  		return -1;
   294  	}
   295  	return 0;
   296  }
   297  
   298  // validate_instance_name performs full validation of the given snap instance name.
   299  int validate_instance_name(const char *instance_name)
   300  {
   301  	// NOTE: This function should be synchronized with the two other
   302  	// implementations: sc_instance_name_validate and snap.ValidateInstanceName.
   303  
   304  	if (instance_name == NULL) {
   305  		bootstrap_msg = "snap instance name cannot be NULL";
   306  		return -1;
   307  	}
   308  	// 40 char snap_name + '_' + 10 char instance_key + 1 extra overflow + 1
   309  	// NULL
   310  	char s[53] = { 0 };
   311  	strncpy(s, instance_name, sizeof(s) - 1);
   312  
   313  	char *t = s;
   314  	const char *snap_name = strsep(&t, "_");
   315  	const char *instance_key = strsep(&t, "_");
   316  	const char *third_separator = strsep(&t, "_");
   317  	if (third_separator != NULL) {
   318  		bootstrap_msg =
   319  		    "snap instance name can contain only one underscore";
   320  		return -1;
   321  	}
   322  
   323  	if (validate_snap_name(snap_name) < 0) {
   324  		return -1;
   325  	}
   326  	// When the instance_name is a normal snap name, instance_key will be
   327  	// NULL, so only validate instance_key when we found one.
   328  	if (instance_key != NULL && instance_key_validate(instance_key) < 0) {
   329  		return -1;
   330  	}
   331  
   332  	return 0;
   333  }
   334  
   335  // parse the -u argument, returns -1 on failure or 0 on success.
   336  static int parse_arg_u(int argc, char *const *argv, int *optind,
   337  		       unsigned long *uid_out)
   338  {
   339  	if (*optind + 1 == argc || argv[*optind + 1] == NULL) {
   340  		bootstrap_msg = "-u requires an argument";
   341  		bootstrap_errno = 0;
   342  		return -1;
   343  	}
   344  	const char *uid_text = argv[*optind + 1];
   345  	errno = 0;
   346  	char *uid_text_end = NULL;
   347  	unsigned long parsed_uid = strtoul(uid_text, &uid_text_end, 10);
   348  	int saved_errno = errno;
   349  	char c = *uid_text;
   350  	if (
   351  		   /* Reject overflow in parsed representation */
   352  		   (parsed_uid == ULONG_MAX && errno != 0)
   353  		   /* Reject leading whitespace allowed by strtoul. */
   354  		   /* NOTE: We are reimplementing isspace() here.
   355  		    * For context see
   356  		    * https://github.com/golang/go/issues/29689 */
   357  		   || c == ' ' || c == '\t' || c == '\v' || c == '\r'
   358  		   || c == '\n'
   359  		   /* Reject empty string. */
   360  		   || (*uid_text == '\0')
   361  		   /* Reject partially parsed strings. */
   362  		   || (*uid_text != '\0' && uid_text_end != NULL
   363  		       && *uid_text_end != '\0')) {
   364  		bootstrap_msg = "cannot parse user id";
   365  		bootstrap_errno = saved_errno;
   366  		return -1;
   367  	}
   368  	if ((long)parsed_uid < 0) {
   369  		bootstrap_msg = "user id cannot be negative";
   370  		bootstrap_errno = 0;
   371  		return -1;
   372  	}
   373  	if (uid_out != NULL) {
   374  		*uid_out = parsed_uid;
   375  	}
   376  	*optind += 1;		// Account for the argument to -u.
   377  	return 0;
   378  }
   379  
   380  // process_arguments parses given a command line
   381  // argc and argv are defined as for the main() function
   382  void process_arguments(int argc, char *const *argv, const char **snap_name_out,
   383  		       bool *should_setns_out, bool *process_user_fstab,
   384  		       unsigned long *uid_out)
   385  {
   386  	// Find the name of the called program. If it is ending with ".test" then do nothing.
   387  	// NOTE: This lets us use cgo/go to write tests without running the bulk
   388  	// of the code automatically.
   389  	//
   390  	if (argv == NULL || argc < 1) {
   391  		bootstrap_errno = 0;
   392  		bootstrap_msg = "argv0 is corrupted";
   393  		return;
   394  	}
   395  	const char *argv0 = argv[0];
   396  	const char *argv0_suffix_maybe = strstr(argv0, ".test");
   397  	if (argv0_suffix_maybe != NULL
   398  	    && argv0_suffix_maybe[strlen(".test")] == '\0') {
   399  		bootstrap_errno = 0;
   400  		bootstrap_msg = "bootstrap is not enabled while testing";
   401  		return;
   402  	}
   403  
   404  	bool should_setns = true;
   405  	bool user_fstab = false;
   406  	const char *snap_name = NULL;
   407  
   408  	// Sanity check the command line arguments.  The go parts will
   409  	// scan this too.
   410  	int i;
   411  	for (i = 1; i < argc; i++) {
   412  		const char *arg = argv[i];
   413  		if (arg[0] == '-') {
   414  			/* We have an option */
   415  			if (!strcmp(arg, "--from-snap-confine")) {
   416  				// When we are running under "--from-snap-confine"
   417  				// option skip the setns call as snap-confine has
   418  				// already placed us in the right namespace.
   419  				should_setns = false;
   420  			} else if (!strcmp(arg, "--user-mounts")) {
   421  				user_fstab = true;
   422  				// Processing the user-fstab file implies we're being
   423  				// called from snap-confine.
   424  				should_setns = false;
   425  			} else if (!strcmp(arg, "-u")) {
   426  				if (parse_arg_u(argc, argv, &i, uid_out) < 0) {
   427  					return;
   428  				}
   429  				// Providing an user identifier implies we are performing an
   430  				// update of a specific user mount namespace and that we are
   431  				// invoked from snapd and we should setns ourselves. When
   432  				// invoked from snap-confine we are only called with
   433  				// --from-snap-confine and with --user-mounts.
   434  				should_setns = true;
   435  				user_fstab = true;
   436  			} else {
   437  				bootstrap_errno = 0;
   438  				bootstrap_msg = "unsupported option";
   439  				return;
   440  			}
   441  		} else {
   442  			// We expect a single positional argument: the snap name
   443  			if (snap_name != NULL) {
   444  				bootstrap_errno = 0;
   445  				bootstrap_msg = "too many positional arguments";
   446  				return;
   447  			}
   448  			snap_name = arg;
   449  		}
   450  	}
   451  
   452  	// If there's no snap name given, just bail out.
   453  	if (snap_name == NULL) {
   454  		bootstrap_errno = 0;
   455  		bootstrap_msg = "snap name not provided";
   456  		return;
   457  	}
   458  	// Ensure that the snap instance name is valid so that we don't blindly setns into
   459  	// something that is controlled by a potential attacker.
   460  	if (validate_instance_name(snap_name) < 0) {
   461  		bootstrap_errno = 0;
   462  		// bootstap_msg is set by validate_instance_name;
   463  		return;
   464  	}
   465  	// We have a valid snap name now so let's store it.
   466  	if (snap_name_out != NULL) {
   467  		*snap_name_out = snap_name;
   468  	}
   469  	if (should_setns_out != NULL) {
   470  		*should_setns_out = should_setns;
   471  	}
   472  	if (process_user_fstab != NULL) {
   473  		*process_user_fstab = user_fstab;
   474  	}
   475  	bootstrap_errno = 0;
   476  	bootstrap_msg = NULL;
   477  }
   478  
   479  // bootstrap prepares snap-update-ns to work in the namespace of the snap given
   480  // on command line.
   481  void bootstrap(int argc, char **argv, char **envp)
   482  {
   483  	// We may have been started via a setuid-root snap-confine. In order to
   484  	// prevent environment-based attacks we start by erasing all environment
   485  	// variables.
   486  	char *snapd_debug = getenv("SNAPD_DEBUG");
   487  	if (clearenv() != 0) {
   488  		bootstrap_errno = 0;
   489  		bootstrap_msg = "bootstrap could not clear the environment";
   490  		return;
   491  	}
   492  	if (snapd_debug != NULL) {
   493  		setenv("SNAPD_DEBUG", snapd_debug, 0);
   494  	}
   495  	// Analyze the read process cmdline to find the snap name and decide if we
   496  	// should use setns to jump into the mount namespace of a particular snap.
   497  	// This is spread out for easier testability.
   498  	const char *snap_name = NULL;
   499  	bool should_setns = false;
   500  	bool process_user_fstab = false;
   501  	unsigned long uid = 0;
   502  	process_arguments(argc, argv, &snap_name, &should_setns,
   503  			  &process_user_fstab, &uid);
   504  	if (process_user_fstab) {
   505  		switch_to_privileged_user();
   506  		// switch_to_privileged_user sets bootstrap_{errno,msg}
   507  	} else if (snap_name != NULL && should_setns) {
   508  		setns_into_snap(snap_name);
   509  		// setns_into_snap sets bootstrap_{errno,msg}
   510  	}
   511  }