Compare commits

...

2 Commits

Author SHA1 Message Date
Andres Beltran 954db013bd
Merge 185b7307d9 into d80a9042ca 2024-09-17 13:48:15 +02:00
Andres Beltran 185b7307d9 Add id-mapped mount support for StateDirectory= 2024-09-10 18:03:59 +00:00
7 changed files with 203 additions and 7 deletions

View File

@ -1476,6 +1476,12 @@ CapabilityBoundingSet=~CAP_B CAP_C</programlisting>
below the locations defined in the following table. Also, the corresponding environment variable will below the locations defined in the following table. Also, the corresponding environment variable will
be defined with the full paths of the directories. If multiple directories are set, then in the be defined with the full paths of the directories. If multiple directories are set, then in the
environment variable the paths are concatenated with colon (<literal>:</literal>).</para> environment variable the paths are concatenated with colon (<literal>:</literal>).</para>
<para>If the kernel version suppors <ulink url="https://lwn.net/Articles/896255/">id-mapped mounts</ulink>,
the specified directories will be owned by "nobody" in the host namespace and will be mapped to (and will be
owned by) the service's UID/GUID in its own namespace. For backward compatibility, existing directories
created without id-mapped mounts will be kept untouched.</para>
<table> <table>
<title>Automatic directory creation and environment variables</title> <title>Automatic directory creation and environment variables</title>
<tgroup cols='4'> <tgroup cols='4'>

View File

@ -2280,6 +2280,42 @@ static int create_many_symlinks(const char *root, const char *source, char **sym
return 0; return 0;
} }
static bool is_idmapping_supported(const char *path) {
_cleanup_close_ int mount_fd = -EBADF, userns_fd = -EBADF, dir_fd = -EBADF;
_cleanup_free_ char *uid_map = NULL, *gid_map = NULL;
int r;
dir_fd = open(path, O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW);
if (dir_fd < 0)
return false;
mount_fd = open_tree(dir_fd, "", AT_EMPTY_PATH | OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC);
if (mount_fd < 0)
return false;
r = strextendf(&uid_map, UID_FMT " " UID_FMT " " UID_FMT "\n", UID_NOBODY, UID_NOBODY, 1u);
if (r < 0)
return false;
r = strextendf(&gid_map, GID_FMT " " GID_FMT " " GID_FMT "\n", GID_NOBODY, GID_NOBODY, 1u);
if (r < 0)
return false;
userns_fd = userns_acquire(uid_map, gid_map);
if (userns_fd < 0)
return false;
r = mount_setattr(mount_fd, "", AT_EMPTY_PATH,
&(struct mount_attr) {
.attr_set = MOUNT_ATTR_IDMAP,
.userns_fd = userns_fd,
}, sizeof(struct mount_attr));
if (r < 0)
return false;
return true;
}
static int setup_exec_directory( static int setup_exec_directory(
const ExecContext *context, const ExecContext *context,
const ExecParameters *params, const ExecParameters *params,
@ -2563,12 +2599,37 @@ static int setup_exec_directory(
if (params->runtime_scope != RUNTIME_SCOPE_SYSTEM) if (params->runtime_scope != RUNTIME_SCOPE_SYSTEM)
continue; continue;
/* Then, change the ownership of the whole tree, if necessary. When dynamic users are used we /* Use 'nobody' uid/gid for exec directories if ID-mapping is supported. For backward compatibility,
* continue doing chmod/chown if the directory was chmod/chowned before (if uid/gid is not 'nobody') */
bool idmapping_supported = is_idmapping_supported(pp ?: p);
log_debug("ID-mapping is%s supported for exec directory %s", idmapping_supported ? "" : "not", pp ?: p);
struct stat st;
r = RET_NERRNO(stat(pp ?: p, &st));
if (r < 0)
goto fail;
/* Change the ownership of the whole tree, if necessary. When dynamic users are used we
* drop the suid/sgid bits, since we really don't want SUID/SGID files for dynamic UID/GID * drop the suid/sgid bits, since we really don't want SUID/SGID files for dynamic UID/GID
* assignments to exist. */ * assignments to exist. */
if (uid == 0 || gid == 0) {
i->idmapped = false;
r = path_chown_recursive(pp ?: p, uid, gid, context->dynamic_user ? 01777 : 07777, AT_SYMLINK_FOLLOW); r = path_chown_recursive(pp ?: p, uid, gid, context->dynamic_user ? 01777 : 07777, AT_SYMLINK_FOLLOW);
if (r < 0) if (r < 0)
goto fail; goto fail;
}else if (idmapping_supported && st.st_uid == UID_NOBODY && st.st_gid == GID_NOBODY) {
i->idmapped = true;
}else if (idmapping_supported && st.st_uid == (uid_t)0 && st.st_gid == (gid_t)0) {
r = path_chown_recursive(pp ?: p, UID_NOBODY, GID_NOBODY, context->dynamic_user ? 01777 : 07777, AT_SYMLINK_FOLLOW);
if (r < 0)
goto fail;
i->idmapped = true;
}else {
i->idmapped = false;
r = path_chown_recursive(pp ?: p, uid, gid, context->dynamic_user ? 01777 : 07777, AT_SYMLINK_FOLLOW);
if (r < 0)
goto fail;
}
} }
/* If we are not going to run in a namespace, set up the symlinks - otherwise /* If we are not going to run in a namespace, set up the symlinks - otherwise
@ -2620,6 +2681,8 @@ static int setup_smack(
static int compile_bind_mounts( static int compile_bind_mounts(
const ExecContext *context, const ExecContext *context,
const ExecParameters *params, const ExecParameters *params,
uid_t uid,
gid_t gid,
BindMount **ret_bind_mounts, BindMount **ret_bind_mounts,
size_t *ret_n_bind_mounts, size_t *ret_n_bind_mounts,
char ***ret_empty_directories) { char ***ret_empty_directories) {
@ -2718,6 +2781,9 @@ static int compile_bind_mounts(
.destination = TAKE_PTR(d), .destination = TAKE_PTR(d),
.nosuid = context->dynamic_user, /* don't allow suid/sgid when DynamicUser= is on */ .nosuid = context->dynamic_user, /* don't allow suid/sgid when DynamicUser= is on */
.recursive = true, .recursive = true,
.idmapped = i->idmapped,
.uid = uid,
.gid = gid,
}; };
} }
} }
@ -3040,7 +3106,9 @@ static int apply_mount_namespace(
ExecRuntime *runtime, ExecRuntime *runtime,
const char *memory_pressure_path, const char *memory_pressure_path,
bool needs_sandboxing, bool needs_sandboxing,
char **error_path) { char **error_path,
uid_t uid,
gid_t gid) {
_cleanup_(verity_settings_done) VeritySettings verity = VERITY_SETTINGS_DEFAULT; _cleanup_(verity_settings_done) VeritySettings verity = VERITY_SETTINGS_DEFAULT;
_cleanup_strv_free_ char **empty_directories = NULL, **symlinks = NULL, _cleanup_strv_free_ char **empty_directories = NULL, **symlinks = NULL,
@ -3076,7 +3144,7 @@ static int apply_mount_namespace(
return r; return r;
} }
r = compile_bind_mounts(context, params, &bind_mounts, &n_bind_mounts, &empty_directories); r = compile_bind_mounts(context, params, uid, gid, &bind_mounts, &n_bind_mounts, &empty_directories);
if (r < 0) if (r < 0)
return r; return r;
@ -4830,7 +4898,9 @@ int exec_invoke(
runtime, runtime,
memory_pressure_path, memory_pressure_path,
needs_sandboxing, needs_sandboxing,
&error_path); &error_path,
uid,
gid);
if (r < 0) { if (r < 0) {
*exit_status = EXIT_NAMESPACE; *exit_status = EXIT_NAMESPACE;
return log_exec_error_errno(context, params, r, "Failed to set up mount namespacing%s%s: %m", return log_exec_error_errno(context, params, r, "Failed to set up mount namespacing%s%s: %m",

View File

@ -2721,6 +2721,7 @@ int exec_directory_add(ExecDirectory *d, const char *path, const char *symlink)
d->items[d->n_items++] = (ExecDirectoryItem) { d->items[d->n_items++] = (ExecDirectoryItem) {
.path = TAKE_PTR(p), .path = TAKE_PTR(p),
.symlinks = TAKE_PTR(s), .symlinks = TAKE_PTR(s),
.idmapped = false,
}; };
return 1; /* new item is added */ return 1; /* new item is added */

View File

@ -156,6 +156,7 @@ typedef struct ExecDirectoryItem {
char *path; char *path;
char **symlinks; char **symlinks;
bool only_create; bool only_create;
bool idmapped;
} ExecDirectoryItem; } ExecDirectoryItem;
typedef struct ExecDirectory { typedef struct ExecDirectory {

View File

@ -113,6 +113,9 @@ typedef struct MountEntry {
LIST_HEAD(MountOptions, image_options_const); LIST_HEAD(MountOptions, image_options_const);
char **overlay_layers; char **overlay_layers;
VeritySettings verity; VeritySettings verity;
bool idmapped;
uid_t uid;
gid_t gid;
} MountEntry; } MountEntry;
typedef struct MountList { typedef struct MountList {
@ -451,6 +454,9 @@ static int append_bind_mounts(MountList *ml, const BindMount *binds, size_t n) {
.flags = b->nodev ? MS_NODEV : 0, .flags = b->nodev ? MS_NODEV : 0,
.source_const = b->source, .source_const = b->source,
.ignore = b->ignore_enoent, .ignore = b->ignore_enoent,
.idmapped = b->idmapped,
.uid = b->uid,
.gid = b->gid,
}; };
} }
@ -1806,6 +1812,48 @@ static int apply_one_mount(
} }
log_debug("Successfully mounted %s to %s", what, mount_entry_path(m)); log_debug("Successfully mounted %s to %s", what, mount_entry_path(m));
/* Take care of id-mapped mounts */
if (m->idmapped && uid_is_valid(m->uid) && gid_is_valid(m->gid)) {
_cleanup_close_ int userns_fd = -EBADF;
_cleanup_free_ char *uid_map = NULL, *gid_map = NULL;
log_debug("Setting an id-mapped mount on %s", mount_entry_path(m));
// Do mapping from nobody (in setup_exec_directory()) -> this uid
r = strextendf(&uid_map, UID_FMT " " UID_FMT " " UID_FMT "\n", UID_NOBODY, m->uid, 1u);
if (r < 0)
return log_oom();
// Consider StateDirectory=xxx aaa xxx:aaa/222
// To allow for later symlink creation (by root) in create_symlinks_from_tuples(), map root as well
if (m->uid != (uid_t)0) {
r = strextendf(&uid_map, UID_FMT " " UID_FMT " " UID_FMT "\n", (uid_t)0, (uid_t)0, 1u);
if (r < 0)
return log_oom();
}
r = strextendf(&gid_map, GID_FMT " " GID_FMT " " GID_FMT "\n", GID_NOBODY, m->gid, 1u);
if (r < 0)
return log_oom();
if (m->gid != (gid_t)0) {
r = strextendf(&gid_map, GID_FMT " " GID_FMT " " GID_FMT "\n", (gid_t)0, (gid_t)0, 1u);
if (r < 0)
return log_oom();
}
userns_fd = userns_acquire(uid_map, gid_map);
if (userns_fd < 0)
return log_error_errno(userns_fd, "Failed to allocate user namespace: %m");
r = remount_idmap_fd(STRV_MAKE(mount_entry_path(m)), userns_fd);
if (r < 0)
return log_error_errno(r, "Failed to create an id-mapped mount: %m");
log_debug("ID-mapped mount created successfully for %s from %u to %u", mount_entry_path(m), UID_NOBODY, m->uid);
}
return 1; return 1;
} }

View File

@ -78,6 +78,9 @@ struct BindMount {
bool noexec; bool noexec;
bool recursive; bool recursive;
bool ignore_enoent; bool ignore_enoent;
bool idmapped;
uid_t uid;
gid_t gid;
}; };
struct TemporaryFileSystem { struct TemporaryFileSystem {

View File

@ -148,12 +148,79 @@ EOF
systemctl start testservice-34-check-writable.service systemctl start testservice-34-check-writable.service
} }
test_check_idmapped_mounts() {
rm -rf /var/lib/testidmapped /var/lib/private/testidmapped
cat >/run/systemd/system/testservice-34-check-idmapped.service <<\EOF
[Unit]
Description=Check id-mapped directories when DynamicUser=yes with StateDirectory
[Service]
# Relevant only for sanitizer runs
EnvironmentFile=-/usr/lib/systemd/systemd-asan-env
Type=oneshot
MountAPIVFS=yes
DynamicUser=yes
PrivateUsers=yes
TemporaryFileSystem=/run /var/opt /var/lib /vol
UMask=0000
StateDirectory=testidmapped:sampleservice
ExecStart=/bin/bash -c ' \
set -eux; \
set -o pipefail; \
touch /var/lib/sampleservice/testfile; \
[[ $(awk "NR==2 {print \$1}" /proc/self/uid_map) == $(stat -c "%%u" /var/lib/private/testidmapped/testfile) ]]; \
'
EOF
systemctl daemon-reload
systemctl start testservice-34-check-idmapped.service
[[ $(stat -c "%u" /var/lib/private/testidmapped/testfile) == 65534 ]]
}
test_check_idmapped_mounts_root() {
rm -rf /var/lib/testidmapped /var/lib/private/testidmapped
cat >/run/systemd/system/testservice-34-check-idmapped.service <<\EOF
[Unit]
Description=Check id-mapped directories when DynamicUser=no with StateDirectory
[Service]
# Relevant only for sanitizer runs
EnvironmentFile=-/usr/lib/systemd/systemd-asan-env
Type=oneshot
MountAPIVFS=yes
User=root
DynamicUser=no
PrivateUsers=no
TemporaryFileSystem=/run /var/opt /var/lib /vol
UMask=0000
StateDirectory=testidmapped:sampleservice
ExecStart=/bin/bash -c ' \
set -eux; \
set -o pipefail; \
touch /var/lib/sampleservice/testfile; \
[[ 0 == $(stat -c "%%u" /var/lib/testidmapped/testfile) ]]; \
'
EOF
systemctl daemon-reload
systemctl start testservice-34-check-idmapped.service
[[ $(stat -c "%u" /var/lib/testidmapped/testfile) == 0 ]]
}
test_directory "StateDirectory" "/var/lib" test_directory "StateDirectory" "/var/lib"
test_directory "RuntimeDirectory" "/run" test_directory "RuntimeDirectory" "/run"
test_directory "CacheDirectory" "/var/cache" test_directory "CacheDirectory" "/var/cache"
test_directory "LogsDirectory" "/var/log" test_directory "LogsDirectory" "/var/log"
test_check_writable test_check_writable
test_check_idmapped_mounts
test_check_idmapped_mounts_root
systemd-analyze log-level info systemd-analyze log-level info