github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/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 }