github.com/freetocompute/snapd@v0.0.0-20210618182524-2fb355d72fd9/cmd/libsnap-confine-private/utils.c (about)

     1  /*
     2   * Copyright (C) 2015 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 <errno.h>
    18  #include <fcntl.h>
    19  #include <stdarg.h>
    20  #include <stdio.h>
    21  #include <stdlib.h>
    22  #include <string.h>
    23  #include <sys/stat.h>
    24  #include <unistd.h>
    25  
    26  #include "cleanup-funcs.h"
    27  #include "panic.h"
    28  #include "utils.h"
    29  
    30  void die(const char *msg, ...)
    31  {
    32  	va_list ap;
    33  	va_start(ap, msg);
    34  	sc_panicv(msg, ap);
    35  	va_end(ap);
    36  }
    37  
    38  struct sc_bool_name {
    39  	const char *text;
    40  	bool value;
    41  };
    42  
    43  static const struct sc_bool_name sc_bool_names[] = {
    44  	{"yes", true},
    45  	{"no", false},
    46  	{"1", true},
    47  	{"0", false},
    48  	{"", false},
    49  };
    50  
    51  /**
    52   * Convert string to a boolean value, with a default.
    53   *
    54   * The return value is 0 in case of success or -1 when the string cannot be
    55   * converted correctly. In such case errno is set to indicate the problem and
    56   * the value is not written back to the caller-supplied pointer.
    57   *
    58   * If the text cannot be recognized, the default value is used.
    59   **/
    60  static int parse_bool(const char *text, bool *value, bool default_value)
    61  {
    62  	if (value == NULL) {
    63  		errno = EFAULT;
    64  		return -1;
    65  	}
    66  	if (text == NULL) {
    67  		*value = default_value;
    68  		return 0;
    69  	}
    70  	for (size_t i = 0; i < sizeof sc_bool_names / sizeof *sc_bool_names;
    71  	     ++i) {
    72  		if (strcmp(text, sc_bool_names[i].text) == 0) {
    73  			*value = sc_bool_names[i].value;
    74  			return 0;
    75  		}
    76  	}
    77  	errno = EINVAL;
    78  	return -1;
    79  }
    80  
    81  /**
    82   * Get an environment variable and convert it to a boolean.
    83   *
    84   * Supported values are those of parse_bool(), namely "yes", "no" as well as "1"
    85   * and "0". All other values are treated as false and a diagnostic message is
    86   * printed to stderr. If the environment variable is unset, set value to the
    87   * default_value as if the environment variable was set to default_value.
    88   **/
    89  static bool getenv_bool(const char *name, bool default_value)
    90  {
    91  	const char *str_value = getenv(name);
    92  	bool value = default_value;
    93  	if (parse_bool(str_value, &value, default_value) < 0) {
    94  		if (errno == EINVAL) {
    95  			fprintf(stderr,
    96  				"WARNING: unrecognized value of environment variable %s (expected yes/no or 1/0)\n",
    97  				name);
    98  			return false;
    99  		} else {
   100  			die("cannot convert value of environment variable %s to a boolean", name);
   101  		}
   102  	}
   103  	return value;
   104  }
   105  
   106  bool sc_is_debug_enabled(void)
   107  {
   108  	return getenv_bool("SNAP_CONFINE_DEBUG", false)
   109  	    || getenv_bool("SNAPD_DEBUG", false);
   110  }
   111  
   112  bool sc_is_reexec_enabled(void)
   113  {
   114  	return getenv_bool("SNAP_REEXEC", true);
   115  }
   116  
   117  void debug(const char *msg, ...)
   118  {
   119  	if (sc_is_debug_enabled()) {
   120  		va_list va;
   121  		va_start(va, msg);
   122  		fprintf(stderr, "DEBUG: ");
   123  		vfprintf(stderr, msg, va);
   124  		fprintf(stderr, "\n");
   125  		va_end(va);
   126  	}
   127  }
   128  
   129  void write_string_to_file(const char *filepath, const char *buf)
   130  {
   131  	debug("write_string_to_file %s %s", filepath, buf);
   132  	FILE *f = fopen(filepath, "w");
   133  	if (f == NULL)
   134  		die("fopen %s failed", filepath);
   135  	if (fwrite(buf, strlen(buf), 1, f) != 1)
   136  		die("fwrite failed");
   137  	if (fflush(f) != 0)
   138  		die("fflush failed");
   139  	if (fclose(f) != 0)
   140  		die("fclose failed");
   141  }
   142  
   143  sc_identity sc_set_effective_identity(sc_identity identity)
   144  {
   145  	debug("set_effective_identity uid:%d (change: %s), gid:%d (change: %s)",
   146  	      identity.uid, identity.change_uid ? "yes" : "no",
   147  	      identity.gid, identity.change_gid ? "yes" : "no");
   148  	/* We are being careful not to return a value instructing us to change GID
   149  	 * or UID by accident. */
   150  	sc_identity old = {
   151  		.change_gid = 0,
   152  		.change_uid = 0,
   153  	};
   154  
   155  	if (identity.change_gid) {
   156  		old.gid = getegid();
   157  		old.change_gid = 1;
   158  		if (setegid(identity.gid) < 0) {
   159  			die("cannot set effective group to %d", identity.gid);
   160  		}
   161  		if (getegid() != identity.gid) {
   162  			die("effective group change from %d to %d has failed",
   163  			    old.gid, identity.gid);
   164  		}
   165  	}
   166  	if (identity.change_uid) {
   167  		old.uid = geteuid();
   168  		old.change_uid = 1;
   169  		if (seteuid(identity.uid) < 0) {
   170  			die("cannot set effective user to %d", identity.uid);
   171  		}
   172  		if (geteuid() != identity.uid) {
   173  			die("effective user change from %d to %d has failed",
   174  			    old.uid, identity.uid);
   175  		}
   176  	}
   177  	return old;
   178  }
   179  
   180  int sc_nonfatal_mkpath(const char *const path, mode_t mode)
   181  {
   182  	// If asked to create an empty path, return immediately.
   183  	if (strlen(path) == 0) {
   184  		return 0;
   185  	}
   186  	// We're going to use strtok_r, which needs to modify the path, so we'll
   187  	// make a copy of it.
   188  	char *path_copy SC_CLEANUP(sc_cleanup_string) = NULL;
   189  	path_copy = strdup(path);
   190  	if (path_copy == NULL) {
   191  		return -1;
   192  	}
   193  	// Open flags to use while we walk the user data path:
   194  	// - Don't follow symlinks
   195  	// - Don't allow child access to file descriptor
   196  	// - Only open a directory (fail otherwise)
   197  	const int open_flags = O_NOFOLLOW | O_CLOEXEC | O_DIRECTORY;
   198  
   199  	// We're going to create each path segment via openat/mkdirat calls instead
   200  	// of mkdir calls, to avoid following symlinks and placing the user data
   201  	// directory somewhere we never intended for it to go. The first step is to
   202  	// get an initial file descriptor.
   203  	int fd SC_CLEANUP(sc_cleanup_close) = AT_FDCWD;
   204  	if (path_copy[0] == '/') {
   205  		fd = open("/", open_flags);
   206  		if (fd < 0) {
   207  			return -1;
   208  		}
   209  	}
   210  	// strtok_r needs a pointer to keep track of where it is in the string.
   211  	char *path_walker = NULL;
   212  
   213  	// Initialize tokenizer and obtain first path segment.
   214  	char *path_segment = strtok_r(path_copy, "/", &path_walker);
   215  	while (path_segment) {
   216  		// Try to create the directory.  It's okay if it already existed, but
   217  		// return with error on any other error. Reset errno before attempting
   218  		// this as it may stay stale (errno is not reset if mkdirat(2) returns
   219  		// successfully).
   220  		errno = 0;
   221  		if (mkdirat(fd, path_segment, mode) < 0 && errno != EEXIST) {
   222  			return -1;
   223  		}
   224  		// Open the parent directory we just made (and close the previous one
   225  		// (but not the special value AT_FDCWD) so we can continue down the
   226  		// path.
   227  		int previous_fd = fd;
   228  		fd = openat(fd, path_segment, open_flags);
   229  		if (previous_fd != AT_FDCWD && close(previous_fd) != 0) {
   230  			return -1;
   231  		}
   232  		if (fd < 0) {
   233  			return -1;
   234  		}
   235  		// Obtain the next path segment.
   236  		path_segment = strtok_r(NULL, "/", &path_walker);
   237  	}
   238  	return 0;
   239  }