diff --git a/src/nspawn/nspawn-gperf.gperf b/src/nspawn/nspawn-gperf.gperf index 315bf33155..67a3682689 100644 --- a/src/nspawn/nspawn-gperf.gperf +++ b/src/nspawn/nspawn-gperf.gperf @@ -67,7 +67,8 @@ Files.TemporaryFileSystem, config_parse_tmpfs, 0, 0 Files.Inaccessible, config_parse_inaccessible, 0, 0 Files.Overlay, config_parse_overlay, 0, 0 Files.OverlayReadOnly, config_parse_overlay, 1, 0 -Files.PrivateUsersChown, config_parse_tristate, 0, offsetof(Settings, userns_chown) +Files.PrivateUsersChown, config_parse_userns_chown, 0, offsetof(Settings, userns_ownership) +Files.PrivateUsersOwnership, config_parse_userns_ownership, 0, offsetof(Settings, userns_ownership) Network.Private, config_parse_tristate, 0, offsetof(Settings, private_network) Network.Interface, config_parse_strv, 0, offsetof(Settings, network_interfaces) Network.MACVLAN, config_parse_strv, 0, offsetof(Settings, network_macvlan) diff --git a/src/nspawn/nspawn-settings.c b/src/nspawn/nspawn-settings.c index d4619bead5..ebd83ec2ec 100644 --- a/src/nspawn/nspawn-settings.c +++ b/src/nspawn/nspawn-settings.c @@ -33,7 +33,7 @@ Settings *settings_new(void) { .timezone = _TIMEZONE_MODE_INVALID, .userns_mode = _USER_NAMESPACE_MODE_INVALID, - .userns_chown = -1, + .userns_ownership = _USER_NAMESPACE_OWNERSHIP_INVALID, .uid_shift = UID_INVALID, .uid_range = UID_INVALID, @@ -84,12 +84,9 @@ int settings_load(FILE *f, const char *path, Settings **ret) { /* Make sure that if userns_mode is set, userns_chown is set to something appropriate, and vice versa. Either * both fields shall be initialized or neither. */ - if (s->userns_mode == USER_NAMESPACE_PICK) - s->userns_chown = true; - else if (s->userns_mode != _USER_NAMESPACE_MODE_INVALID && s->userns_chown < 0) - s->userns_chown = false; - - if (s->userns_chown >= 0 && s->userns_mode == _USER_NAMESPACE_MODE_INVALID) + if (s->userns_mode >= 0 && s->userns_ownership < 0) + s->userns_ownership = s->userns_mode == USER_NAMESPACE_PICK ? USER_NAMESPACE_OWNERSHIP_CHOWN : USER_NAMESPACE_OWNERSHIP_OFF; + if (s->userns_ownership >= 0 && s->userns_mode < 0) s->userns_mode = USER_NAMESPACE_NO; *ret = TAKE_PTR(s); @@ -863,3 +860,42 @@ static const char *const timezone_mode_table[_TIMEZONE_MODE_MAX] = { }; DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(timezone_mode, TimezoneMode, TIMEZONE_AUTO); + +DEFINE_CONFIG_PARSE_ENUM(config_parse_userns_ownership, user_namespace_ownership, UserNamespaceOwnership, "Failed to parse user namespace ownership mode"); + +static const char *const user_namespace_ownership_table[_USER_NAMESPACE_OWNERSHIP_MAX] = { + [USER_NAMESPACE_OWNERSHIP_OFF] = "off", + [USER_NAMESPACE_OWNERSHIP_CHOWN] = "chown", +}; + +DEFINE_STRING_TABLE_LOOKUP(user_namespace_ownership, UserNamespaceOwnership); + +int config_parse_userns_chown( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + UserNamespaceOwnership *ownership = data; + int r; + + assert(rvalue); + assert(ownership); + + /* Compatibility support for UserNamespaceChown=, whose job has been taken over by UserNamespaceOwnership= */ + + r = parse_boolean(rvalue); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse user namespace ownership mode, ignoring: %s", rvalue); + return 0; + } + + *ownership = r ? USER_NAMESPACE_OWNERSHIP_CHOWN : USER_NAMESPACE_OWNERSHIP_OFF; + return 0; +} diff --git a/src/nspawn/nspawn-settings.h b/src/nspawn/nspawn-settings.h index a5e6dbead5..2b453f1ee0 100644 --- a/src/nspawn/nspawn-settings.h +++ b/src/nspawn/nspawn-settings.h @@ -36,6 +36,13 @@ typedef enum UserNamespaceMode { _USER_NAMESPACE_MODE_INVALID = -EINVAL, } UserNamespaceMode; +typedef enum UserNamespaceOwnership { + USER_NAMESPACE_OWNERSHIP_OFF, + USER_NAMESPACE_OWNERSHIP_CHOWN, + _USER_NAMESPACE_OWNERSHIP_MAX, + _USER_NAMESPACE_OWNERSHIP_INVALID = -1, +} UserNamespaceOwnership; + typedef enum ResolvConfMode { RESOLV_CONF_OFF, RESOLV_CONF_COPY_HOST, /* /etc/resolv.conf */ @@ -185,7 +192,7 @@ typedef struct Settings { VolatileMode volatile_mode; CustomMount *custom_mounts; size_t n_custom_mounts; - int userns_chown; + UserNamespaceOwnership userns_ownership; /* [Network] */ int private_network; @@ -255,6 +262,8 @@ CONFIG_PARSER_PROTOTYPE(config_parse_cpu_affinity); CONFIG_PARSER_PROTOTYPE(config_parse_resolv_conf); CONFIG_PARSER_PROTOTYPE(config_parse_link_journal); CONFIG_PARSER_PROTOTYPE(config_parse_timezone); +CONFIG_PARSER_PROTOTYPE(config_parse_userns_chown); +CONFIG_PARSER_PROTOTYPE(config_parse_userns_ownership); const char *resolv_conf_mode_to_string(ResolvConfMode a) _const_; ResolvConfMode resolv_conf_mode_from_string(const char *s) _pure_; @@ -262,6 +271,9 @@ ResolvConfMode resolv_conf_mode_from_string(const char *s) _pure_; const char *timezone_mode_to_string(TimezoneMode a) _const_; TimezoneMode timezone_mode_from_string(const char *s) _pure_; +const char *user_namespace_ownership_to_string(UserNamespaceOwnership a) _const_; +UserNamespaceOwnership user_namespace_ownership_from_string(const char *s) _pure_; + int parse_link_journal(const char *s, LinkJournal *ret_mode, bool *ret_try); void device_node_array_free(DeviceNode *node, size_t n); diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index c124607431..ee84495ca2 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -194,7 +194,7 @@ static char **arg_property = NULL; static sd_bus_message *arg_property_message = NULL; static UserNamespaceMode arg_userns_mode = USER_NAMESPACE_NO; static uid_t arg_uid_shift = UID_INVALID, arg_uid_range = 0x10000U; -static bool arg_userns_chown = false; +static UserNamespaceOwnership arg_userns_ownership = _USER_NAMESPACE_OWNERSHIP_INVALID; static int arg_kill_signal = 0; static CGroupUnified arg_unified_cgroup_hierarchy = CGROUP_UNIFIED_UNKNOWN; static SettingsMask arg_settings_mask = 0; @@ -352,7 +352,9 @@ static int help(void) { " -U --private-users=pick Run within user namespace, autoselect UID/GID range\n" " --private-users[=UIDBASE[:NUIDS]]\n" " Similar, but with user configured UID/GID range\n" - " --private-users-chown Adjust OS tree ownership to private UID/GID range\n\n" + " --private-users-ownership=MODE\n" + " Adjust ('chown') or map ('map') OS tree ownership\n" + " to private UID/GID range\n\n" "%3$sNetworking:%4$s\n" " --private-network Disable network in container\n" " --network-interface=INTERFACE\n" @@ -449,10 +451,10 @@ static int custom_mount_check_all(void) { CustomMount *m = &arg_custom_mounts[i]; if (path_equal(m->destination, "/") && arg_userns_mode != USER_NAMESPACE_NO) { - if (arg_userns_chown) + if (arg_userns_ownership != USER_NAMESPACE_OWNERSHIP_OFF) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "--private-users-chown may not be combined with custom root mounts."); - else if (arg_uid_shift == UID_INVALID) + "--private-users-ownership=own not be combined with custom root mounts."); + if (arg_uid_shift == UID_INVALID) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--private-users with automatic UID shift may not be combined with custom root mounts."); } @@ -685,6 +687,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_CHDIR, ARG_PIVOT_ROOT, ARG_PRIVATE_USERS_CHOWN, + ARG_PRIVATE_USERS_OWNERSHIP, ARG_NOTIFY_READY, ARG_ROOT_HASH, ARG_ROOT_HASH_SIG, @@ -752,7 +755,8 @@ static int parse_argv(int argc, char *argv[]) { { "port", required_argument, NULL, 'p' }, { "property", required_argument, NULL, ARG_PROPERTY }, { "private-users", optional_argument, NULL, ARG_PRIVATE_USERS }, - { "private-users-chown", optional_argument, NULL, ARG_PRIVATE_USERS_CHOWN }, + { "private-users-chown", optional_argument, NULL, ARG_PRIVATE_USERS_CHOWN }, /* obsolete */ + { "private-users-ownership",required_argument, NULL, ARG_PRIVATE_USERS_OWNERSHIP}, { "kill-signal", required_argument, NULL, ARG_KILL_SIGNAL }, { "settings", required_argument, NULL, ARG_SETTINGS }, { "chdir", required_argument, NULL, ARG_CHDIR }, @@ -1217,8 +1221,8 @@ static int parse_argv(int argc, char *argv[]) { arg_uid_range = UINT32_C(0x10000); } else if (streq(optarg, "pick")) { /* pick: User namespacing on, UID range is picked randomly */ - arg_userns_mode = USER_NAMESPACE_PICK; /* Note that arg_userns_chown = true, - * is implied by USER_NAMESPACE_PICK, + arg_userns_mode = USER_NAMESPACE_PICK; /* Note that arg_userns_ownership is + * implied by USER_NAMESPACE_PICK * further down. */ arg_uid_shift = UID_INVALID; arg_uid_range = UINT32_C(0x10000); @@ -1267,8 +1271,8 @@ static int parse_argv(int argc, char *argv[]) { case 'U': if (userns_supported()) { - arg_userns_mode = USER_NAMESPACE_PICK; /* Note that arg_userns_chown = true, - * is implied by USER_NAMESPACE_PICK, + arg_userns_mode = USER_NAMESPACE_PICK; /* Note that arg_userns_ownership is + * implied by USER_NAMESPACE_PICK * further down. */ arg_uid_shift = UID_INVALID; arg_uid_range = UINT32_C(0x10000); @@ -1279,7 +1283,20 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_PRIVATE_USERS_CHOWN: - arg_userns_chown = true; + arg_userns_ownership = USER_NAMESPACE_OWNERSHIP_CHOWN; + + arg_settings_mask |= SETTING_USERNS; + break; + + case ARG_PRIVATE_USERS_OWNERSHIP: + if (streq(optarg, "help")) { + DUMP_STRING_TABLE(user_namespace_ownership, UserNamespaceOwnership, _USER_NAMESPACE_OWNERSHIP_MAX); + return 0; + } + + arg_userns_ownership = user_namespace_ownership_from_string(optarg); + if (arg_userns_ownership < 0) + return log_error_errno(arg_userns_ownership, "Cannot parse --user-namespace-ownership= value: %s", optarg); arg_settings_mask |= SETTING_USERNS; break; @@ -1715,8 +1732,10 @@ static int verify_arguments(void) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--boot cannot be used without namespacing."); } - if (arg_userns_mode == USER_NAMESPACE_PICK) - arg_userns_chown = true; + if (arg_userns_ownership < 0) + arg_userns_ownership = + arg_userns_mode == USER_NAMESPACE_PICK ? USER_NAMESPACE_OWNERSHIP_CHOWN : + USER_NAMESPACE_OWNERSHIP_OFF; if (arg_start_mode == START_BOOT && arg_kill_signal <= 0) arg_kill_signal = SIGRTMIN+3; @@ -1750,15 +1769,15 @@ static int verify_arguments(void) { if (arg_userns_mode != USER_NAMESPACE_NO && !userns_supported()) return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "--private-users= is not supported, kernel compiled without user namespace support."); - if (arg_userns_chown && arg_read_only) + if (arg_userns_ownership == USER_NAMESPACE_OWNERSHIP_CHOWN && arg_read_only) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "--read-only and --private-users-chown may not be combined."); + "--read-only and --private-users-ownership=chown may not be combined."); - /* We don't support --private-users-chown together with any of the volatile modes since we couldn't - * change the read-only part of the tree (i.e. /usr) anyway, or because it would trigger a massive - * copy-up (in case of overlay) making the entire exercise pointless. */ - if (arg_userns_chown && arg_volatile_mode != VOLATILE_NO) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--volatile= and --private-users-chown may not be combined."); + /* We don't support --private-users-ownership=chown together with any of the volatile modes since we + * couldn't change the read-only part of the tree (i.e. /usr) anyway, or because it would trigger a + * massive copy-up (in case of overlay) making the entire exercise pointless. */ + if (arg_userns_ownership == USER_NAMESPACE_OWNERSHIP_CHOWN && arg_volatile_mode != VOLATILE_NO) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--volatile= and --private-users-ownership=chown may not be combined."); /* If --network-namespace-path is given with any other network-related option (except --private-network), * we need to error out, to avoid conflicts between different network options. */ @@ -2795,7 +2814,7 @@ static int recursive_chown(const char *directory, uid_t shift, uid_t range) { assert(directory); - if (arg_userns_mode == USER_NAMESPACE_NO || !arg_userns_chown) + if (arg_userns_mode == USER_NAMESPACE_NO || arg_userns_ownership != USER_NAMESPACE_OWNERSHIP_CHOWN) return 0; r = path_patch_uid(directory, arg_uid_shift, arg_uid_range); @@ -4240,7 +4259,7 @@ static int merge_settings(Settings *settings, const char *path) { arg_userns_mode = settings->userns_mode; arg_uid_shift = settings->uid_shift; arg_uid_range = settings->uid_range; - arg_userns_chown = settings->userns_chown; + arg_userns_ownership = settings->userns_ownership; } }