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