Compare commits
2 Commits
ee7b61685b
...
03e55e7570
Author | SHA1 | Date |
---|---|---|
Andres Beltran | 03e55e7570 | |
Andres Beltran | 185b7307d9 |
|
@ -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'>
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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 */
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue