github.com/anonymouse64/snapd@v0.0.0-20210824153203-04c4c42d842d/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 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 }