portable: Enable unpriv operation
This does not yet support directory images properly as systemd itself does not support unpriv directory images properly yet. The user profiles are a copy of the system profiles but without DynamicUser=yes (can't be used by user managers) and without ProtectHome=yes (this masks /home which breaks StateDirectory= which is lcoated inside /home)
This commit is contained in:
@@ -17,7 +17,8 @@ two specific features of container management:
|
||||
2. Stricter default security policies, i.e. sand-boxing of applications.
|
||||
|
||||
The primary tool for interacting with Portable Services is `portablectl`,
|
||||
and they are managed by the `systemd-portabled` service.
|
||||
and they are managed by the `systemd-portabled` service. `systemd-portabled` can
|
||||
run as a system or a user service.
|
||||
|
||||
Portable services don't bring anything inherently new to the table.
|
||||
All they do is put together known concepts to cover a specific set of use-cases in a
|
||||
@@ -250,6 +251,14 @@ validated against the (authenticated) image contents.
|
||||
If the field is not specified the image will work fine, but is not necessarily recognizable as
|
||||
portable service image, and any set of units included in the image may be attached, there are no restrictions enforced.
|
||||
|
||||
The [os-release(5)](https://www.freedesktop.org/software/systemd/man/os-release.html) may
|
||||
optionally be extended with a `PORTABLE_SCOPE=` field listing the scope in which the portable
|
||||
service may be used. This field may be set to either `system`, in which case the portable service
|
||||
can only be attached to the system instance of `systemd-portabled`, `user` in which case the portable
|
||||
can only be attached to a user instance of `systemd-portabled`, or `any` in which case it can be
|
||||
attached to either the system instance or user instances of `systemd-portabled`. If not specified, the
|
||||
`system` scope is implied.
|
||||
|
||||
## Extension Images
|
||||
|
||||
Portable services can be delivered as one or multiple images that extend the base
|
||||
|
||||
@@ -636,6 +636,19 @@
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v250"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>PORTABLE_SCOPE=</varname></term>
|
||||
<listitem><para>Specifies the scope of the portable service. Takes one of <literal>system</literal>,
|
||||
<literal>user</literal>, or <literal>any</literal>. When set to <literal>system</literal>, the
|
||||
portable service can only be attached to the system instance of <command>systemd-portabled</command>.
|
||||
When set to <literal>user</literal>, the portable service can only be attached to the user instance
|
||||
of <command>systemd-portabled</command>. When set to <literal>any</literal>, the portable service
|
||||
can be attached to both the system and user instances of <command>systemd-portabled</command>.
|
||||
If not set, <literal>PORTABLE_SCOPE=system</literal> is implied.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v259"/></listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</refsect2>
|
||||
|
||||
|
||||
+15
-14
@@ -144,41 +144,42 @@ modprobedir = prefixdir / 'lib/modprobe.d'
|
||||
pkgdatadir = datadir / 'systemd'
|
||||
environmentdir = prefixdir / 'lib/environment.d'
|
||||
pkgsysconfdir = sysconfdir / 'systemd'
|
||||
userunitdir = prefixdir / 'lib/systemd/user'
|
||||
userpresetdir = prefixdir / 'lib/systemd/user-preset'
|
||||
userunitdir = libexecdir / 'user'
|
||||
userpresetdir = libexecdir / 'user-preset'
|
||||
tmpfilesdir = prefixdir / 'lib/tmpfiles.d'
|
||||
usertmpfilesdir = prefixdir / 'share/user-tmpfiles.d'
|
||||
sysusersdir = prefixdir / 'lib/sysusers.d'
|
||||
sysctldir = prefixdir / 'lib/sysctl.d'
|
||||
binfmtdir = prefixdir / 'lib/binfmt.d'
|
||||
modulesloaddir = prefixdir / 'lib/modules-load.d'
|
||||
networkdir = prefixdir / 'lib/systemd/network'
|
||||
networkdir = libexecdir / 'network'
|
||||
systemgeneratordir = libexecdir / 'system-generators'
|
||||
usergeneratordir = prefixdir / 'lib/systemd/user-generators'
|
||||
systemenvgeneratordir = prefixdir / 'lib/systemd/system-environment-generators'
|
||||
userenvgeneratordir = prefixdir / 'lib/systemd/user-environment-generators'
|
||||
usergeneratordir = libexecdir / 'user-generators'
|
||||
systemenvgeneratordir = libexecdir / 'system-environment-generators'
|
||||
userenvgeneratordir = libexecdir / 'user-environment-generators'
|
||||
systemshutdowndir = libexecdir / 'system-shutdown'
|
||||
systemsleepdir = libexecdir / 'system-sleep'
|
||||
systemunitdir = prefixdir / 'lib/systemd/system'
|
||||
systempresetdir = prefixdir / 'lib/systemd/system-preset'
|
||||
initrdpresetdir = prefixdir / 'lib/systemd/initrd-preset'
|
||||
systemunitdir = libexecdir / 'system'
|
||||
systempresetdir = libexecdir / 'system-preset'
|
||||
initrdpresetdir = libexecdir / 'initrd-preset'
|
||||
udevlibexecdir = prefixdir / 'lib/udev'
|
||||
udevrulesdir = udevlibexecdir / 'rules.d'
|
||||
udevhwdbdir = udevlibexecdir / 'hwdb.d'
|
||||
catalogdir = prefixdir / 'lib/systemd/catalog'
|
||||
catalogdir = libexecdir / 'catalog'
|
||||
kerneldir = prefixdir / 'lib/kernel'
|
||||
kernelinstalldir = kerneldir / 'install.d'
|
||||
factorydir = datadir / 'factory'
|
||||
bootlibdir = prefixdir / 'lib/systemd/boot/efi'
|
||||
testsdir = prefixdir / 'lib/systemd/tests'
|
||||
bootlibdir = libexecdir / 'boot/efi'
|
||||
testsdir = libexecdir / 'tests'
|
||||
unittestsdir = testsdir / 'unit-tests'
|
||||
testdata_dir = testsdir / 'testdata'
|
||||
systemdstatedir = localstatedir / 'lib/systemd'
|
||||
catalogstatedir = systemdstatedir / 'catalog'
|
||||
randomseeddir = localstatedir / 'lib/systemd'
|
||||
profiledir = libexecdir / 'portable' / 'profile'
|
||||
systemprofiledir = libexecdir / 'portable' / 'profile'
|
||||
userprofiledir = libexecdir / 'user' / 'portable' / 'profile'
|
||||
repartdefinitionsdir = libexecdir / 'repart/definitions'
|
||||
ntpservicelistdir = prefixdir / 'lib/systemd/ntp-units.d'
|
||||
ntpservicelistdir = libexecdir / 'ntp-units.d'
|
||||
credstoredir = prefixdir / 'lib/credstore'
|
||||
pcrlockdir = prefixdir / 'lib/pcrlock.d'
|
||||
mimepackagesdir = prefixdir / 'share/mime/packages'
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
[Service]
|
||||
ExecStartPre=cat /usr/lib/os-release
|
||||
ExecStart=sleep 120
|
||||
@@ -7,8 +7,10 @@ mkdir -p "$BUILDROOT/var/lib/app1"
|
||||
cat >>"$BUILDROOT/usr/lib/os-release" <<EOF
|
||||
MARKER=1
|
||||
PORTABLE_PREFIXES=app0 minimal minimal-app0
|
||||
PORTABLE_SCOPE=any
|
||||
EOF
|
||||
if [ ! -L "$BUILDROOT/etc/os-release" ]; then
|
||||
cp "$BUILDROOT/usr/lib/os-release" "$BUILDROOT/etc/os-release"
|
||||
fi
|
||||
cp "$BUILDROOT/usr/lib/systemd/system/minimal-app0.service" "$BUILDROOT/usr/lib/systemd/system/minimal-app0-foo.service"
|
||||
cp "$BUILDROOT/usr/lib/systemd/user/minimal-app0.service" "$BUILDROOT/usr/lib/systemd/user/minimal-app0-foo.service"
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
[Service]
|
||||
ExecStartPre=cat /usr/lib/os-release
|
||||
ExecStart=sleep 120
|
||||
@@ -7,8 +7,10 @@ mkdir -p "$BUILDROOT/var/lib/app1"
|
||||
cat >>"$BUILDROOT/usr/lib/os-release" <<EOF
|
||||
MARKER=2
|
||||
PORTABLE_PREFIXES=app0 minimal minimal-app0
|
||||
PORTABLE_SCOPE=any
|
||||
EOF
|
||||
if [ ! -L "$BUILDROOT/etc/os-release" ]; then
|
||||
cp "$BUILDROOT/usr/lib/os-release" "$BUILDROOT/etc/os-release"
|
||||
fi
|
||||
cp "$BUILDROOT/usr/lib/systemd/system/minimal-app0.service" "$BUILDROOT/usr/lib/systemd/system/minimal-app0-bar.service"
|
||||
cp "$BUILDROOT/usr/lib/systemd/user/minimal-app0.service" "$BUILDROOT/usr/lib/systemd/user/minimal-app0-bar.service"
|
||||
|
||||
@@ -2752,7 +2752,7 @@ static int offline_security_checks(
|
||||
(void) mkdir_parents(dropin, 0755);
|
||||
|
||||
if (!is_path(profile)) {
|
||||
r = find_portable_profile(profile, unit_name, &profile_path);
|
||||
r = find_portable_profile(scope, profile, unit_name, &profile_path);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to find portable profile %s: %m", profile);
|
||||
profile = profile_path;
|
||||
|
||||
@@ -231,7 +231,7 @@ static int acquire_lookup_dirs(
|
||||
},
|
||||
[LOOKUP_DIR_ATTACHED] = {
|
||||
[RUNTIME_SCOPE_SYSTEM] = { "/etc/systemd/system.attached", "/run/systemd/system.attached" },
|
||||
/* Portable services are not available to regular users for now. */
|
||||
[RUNTIME_SCOPE_USER] = { "systemd/user.attached", "systemd/user.attached" },
|
||||
},
|
||||
};
|
||||
|
||||
@@ -349,7 +349,9 @@ static int get_paths_from_environ(const char *var, char ***ret) {
|
||||
|
||||
static char** user_unit_search_dirs(
|
||||
const char *persistent_config,
|
||||
const char *persistent_attached,
|
||||
const char *runtime_config,
|
||||
const char *runtime_attached,
|
||||
const char *global_persistent_config,
|
||||
const char *global_runtime_config,
|
||||
const char *generator,
|
||||
@@ -364,6 +366,7 @@ static char** user_unit_search_dirs(
|
||||
/* The returned strv might contain duplicates, and we expect caller to filter them. */
|
||||
|
||||
assert(persistent_config);
|
||||
assert(persistent_attached);
|
||||
assert(global_persistent_config);
|
||||
assert(global_runtime_config);
|
||||
assert(persistent_control);
|
||||
@@ -375,7 +378,8 @@ static char** user_unit_search_dirs(
|
||||
STRV_IFNOTNULL(runtime_control),
|
||||
STRV_IFNOTNULL(transient),
|
||||
STRV_IFNOTNULL(generator_early),
|
||||
persistent_config);
|
||||
persistent_config,
|
||||
persistent_attached);
|
||||
if (!paths)
|
||||
return NULL;
|
||||
|
||||
@@ -392,6 +396,7 @@ static char** user_unit_search_dirs(
|
||||
/* strv_extend_many() can deal with NULL-s in arguments */
|
||||
if (strv_extend_many(&paths,
|
||||
runtime_config,
|
||||
runtime_attached,
|
||||
global_runtime_config,
|
||||
generator) < 0)
|
||||
return NULL;
|
||||
@@ -551,7 +556,8 @@ int lookup_paths_init(
|
||||
break;
|
||||
|
||||
case RUNTIME_SCOPE_USER:
|
||||
add = user_unit_search_dirs(persistent_config, runtime_config,
|
||||
add = user_unit_search_dirs(persistent_config, persistent_attached,
|
||||
runtime_config, runtime_attached,
|
||||
global_persistent_config, global_runtime_config,
|
||||
generator, generator_early, generator_late,
|
||||
transient,
|
||||
|
||||
@@ -48,10 +48,18 @@ install_data('org.freedesktop.portable1.conf',
|
||||
install_dir : dbuspolicydir)
|
||||
install_data('org.freedesktop.portable1.service',
|
||||
install_dir : dbussystemservicedir)
|
||||
install_data('org.freedesktop.portable1.service-for-session',
|
||||
install_dir : dbussessionservicedir,
|
||||
rename : 'org.freedesktop.portable1.service')
|
||||
install_data('org.freedesktop.portable1.policy',
|
||||
install_dir : polkitpolicydir)
|
||||
|
||||
install_data('profile/default/service.conf', install_dir : profiledir / 'default')
|
||||
install_data('profile/nonetwork/service.conf', install_dir : profiledir / 'nonetwork')
|
||||
install_data('profile/strict/service.conf', install_dir : profiledir / 'strict')
|
||||
install_data('profile/trusted/service.conf', install_dir : profiledir / 'trusted')
|
||||
install_data('profile/system/default/service.conf', install_dir : systemprofiledir / 'default')
|
||||
install_data('profile/system/nonetwork/service.conf', install_dir : systemprofiledir / 'nonetwork')
|
||||
install_data('profile/system/strict/service.conf', install_dir : systemprofiledir / 'strict')
|
||||
install_data('profile/system/trusted/service.conf', install_dir : systemprofiledir / 'trusted')
|
||||
|
||||
install_data('profile/user/default/service.conf', install_dir : userprofiledir / 'default')
|
||||
install_data('profile/user/nonetwork/service.conf', install_dir : userprofiledir / 'nonetwork')
|
||||
install_data('profile/user/strict/service.conf', install_dir : userprofiledir / 'strict')
|
||||
install_data('profile/user/trusted/service.conf', install_dir : userprofiledir / 'trusted')
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
#
|
||||
# This file is part of systemd.
|
||||
#
|
||||
# systemd is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation; either version 2.1 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
[D-BUS Service]
|
||||
Name=org.freedesktop.portable1
|
||||
Exec=/bin/false
|
||||
SystemdService=dbus-org.freedesktop.portable1.service
|
||||
+296
-107
@@ -1,6 +1,7 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <linux/loop.h>
|
||||
#include <sched.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "sd-bus.h"
|
||||
@@ -35,6 +36,8 @@
|
||||
#include "log.h"
|
||||
#include "loop-util.h"
|
||||
#include "mkdir.h"
|
||||
#include "namespace-util.h"
|
||||
#include "nsresource.h"
|
||||
#include "os-util.h"
|
||||
#include "path-lookup.h"
|
||||
#include "pidref.h"
|
||||
@@ -49,6 +52,7 @@
|
||||
#include "string-table.h"
|
||||
#include "strv.h"
|
||||
#include "tmpfile-util.h"
|
||||
#include "uid-classification.h"
|
||||
#include "unit-name.h"
|
||||
#include "vpick.h"
|
||||
|
||||
@@ -334,10 +338,14 @@ static int extract_now(
|
||||
}
|
||||
}
|
||||
|
||||
/* Then, send unit file data to the parent (or/and add it to the hashmap). For that we use our usual unit
|
||||
* discovery logic. Note that we force looking inside of /lib/systemd/system/ for units too, as the
|
||||
* image might have a legacy split-usr layout. */
|
||||
r = lookup_paths_init(&paths, scope, LOOKUP_PATHS_SPLIT_USR, /* root_dir= */ NULL);
|
||||
/* Then, send unit file data to the parent (or/and add it to the hashmap). For that we use our usual
|
||||
* unit discovery logic. If we're running in a user session, we look for units in
|
||||
* /usr/lib/systemd/user/ and corresponding directories. */
|
||||
r = lookup_paths_init(
|
||||
&paths,
|
||||
scope == RUNTIME_SCOPE_USER ? RUNTIME_SCOPE_GLOBAL : RUNTIME_SCOPE_SYSTEM,
|
||||
LOOKUP_PATHS_SPLIT_USR,
|
||||
/* root_dir= */ NULL);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to acquire lookup paths: %m");
|
||||
|
||||
@@ -456,52 +464,106 @@ static int portable_extract_by_path(
|
||||
_cleanup_hashmap_free_ Hashmap *unit_files = NULL;
|
||||
_cleanup_(portable_metadata_unrefp) PortableMetadata* os_release = NULL;
|
||||
_cleanup_(image_policy_freep) ImagePolicy *pinned_image_policy = NULL;
|
||||
_cleanup_(loop_device_unrefp) LoopDevice *d = NULL;
|
||||
int r;
|
||||
|
||||
assert(path);
|
||||
|
||||
r = loop_device_make_by_path(
|
||||
path,
|
||||
O_RDONLY,
|
||||
/* sector_size= */ UINT32_MAX,
|
||||
LO_FLAGS_PARTSCAN,
|
||||
LOCK_SH,
|
||||
&d);
|
||||
if (r == -EISDIR) {
|
||||
/* We can't turn this into a loop-back block device, and this returns EISDIR? Then this is a directory
|
||||
* tree and not a raw device. It's easy then. */
|
||||
_cleanup_close_ int rfd = open(path, O_PATH|O_CLOEXEC);
|
||||
if (rfd < 0)
|
||||
return log_error_errno(errno, "Failed to open '%s': %m", path);
|
||||
|
||||
_cleanup_free_ char *image_name = NULL;
|
||||
struct stat st;
|
||||
if (fstat(rfd, &st) < 0)
|
||||
return log_debug_errno(errno, "Failed to stat '%s': %m", path);
|
||||
|
||||
if (S_ISDIR(st.st_mode)) {
|
||||
_cleanup_free_ char *image_name = NULL;
|
||||
r = path_extract_filename(path, &image_name);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to extract image name from path '%s': %m", path);
|
||||
|
||||
_cleanup_close_ int rfd = open(path, O_DIRECTORY|O_CLOEXEC);
|
||||
if (rfd < 0)
|
||||
return log_error_errno(errno, "Failed to open '%s': %m", path);
|
||||
if (scope == RUNTIME_SCOPE_USER && uid_is_foreign(st.st_uid)) {
|
||||
_cleanup_close_ int userns_fd = nsresource_allocate_userns(/* name= */ NULL, NSRESOURCE_UIDS_64K);
|
||||
if (userns_fd < 0)
|
||||
return log_debug_errno(userns_fd, "Failed to allocate user namespace: %m");
|
||||
|
||||
r = extract_now(scope,
|
||||
rfd,
|
||||
path,
|
||||
matches,
|
||||
image_name,
|
||||
path_is_extension,
|
||||
/* relax_extension_release_check= */ false,
|
||||
/* socket_fd= */ -EBADF,
|
||||
&os_release,
|
||||
&unit_files);
|
||||
if (r < 0)
|
||||
return r;
|
||||
_cleanup_close_ int mfd = -EBADF;
|
||||
r = mountfsd_mount_directory_fd(rfd, userns_fd, DISSECT_IMAGE_FOREIGN_UID, &mfd);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to open '%s' via mountfsd: %m", path);
|
||||
|
||||
} else if (r < 0)
|
||||
return log_debug_errno(r, "Failed to set up loopback device for %s: %m", path);
|
||||
else {
|
||||
_cleanup_(verity_settings_done) VeritySettings verity = VERITY_SETTINGS_DEFAULT;
|
||||
_cleanup_close_pair_ int seq[2] = EBADF_PAIR;
|
||||
if (socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0, seq) < 0)
|
||||
return log_debug_errno(errno, "Failed to allocated SOCK_SEQPACKET socket: %m");
|
||||
|
||||
_cleanup_close_pair_ int errno_pipe_fd[2] = EBADF_PAIR;
|
||||
if (pipe2(errno_pipe_fd, O_CLOEXEC|O_NONBLOCK) < 0)
|
||||
return log_debug_errno(errno, "Failed to create pipe: %m");
|
||||
|
||||
_cleanup_(pidref_done_sigkill_wait) PidRef child = PIDREF_NULL;
|
||||
r = pidref_safe_fork("(sd-extract)",
|
||||
FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL|FORK_REOPEN_LOG,
|
||||
&child);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0) {
|
||||
seq[0] = safe_close(seq[0]);
|
||||
errno_pipe_fd[0] = safe_close(errno_pipe_fd[0]);
|
||||
|
||||
if (setns(CLONE_NEWUSER, userns_fd) < 0) {
|
||||
r = log_debug_errno(errno, "Failed to join userns: %m");
|
||||
report_errno_and_exit(errno_pipe_fd[1], r);
|
||||
}
|
||||
|
||||
r = extract_now(scope,
|
||||
mfd,
|
||||
path,
|
||||
matches,
|
||||
image_name,
|
||||
path_is_extension,
|
||||
/* relax_extension_release_check= */ false,
|
||||
seq[1],
|
||||
/* ret_os_release= */ NULL,
|
||||
/* ret_unit_files= */ NULL);
|
||||
report_errno_and_exit(errno_pipe_fd[1], r);
|
||||
}
|
||||
|
||||
seq[1] = safe_close(seq[1]);
|
||||
errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]);
|
||||
|
||||
r = receive_portable_metadata(seq[0], path, &os_release, &unit_files);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = pidref_wait_for_terminate_and_check("(sd-extract)", &child, 0);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r != EXIT_SUCCESS) {
|
||||
if (read(errno_pipe_fd[0], &r, sizeof(r)) == sizeof(r))
|
||||
return log_debug_errno(r, "Failed to extract portable metadata from '%s': %m", path);
|
||||
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EPROTO), "Child failed.");
|
||||
}
|
||||
} else {
|
||||
r = extract_now(scope,
|
||||
rfd,
|
||||
path,
|
||||
matches,
|
||||
image_name,
|
||||
path_is_extension,
|
||||
/* relax_extension_release_check= */ false,
|
||||
/* socket_fd= */ -EBADF,
|
||||
&os_release,
|
||||
&unit_files);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
} else {
|
||||
_cleanup_(dissected_image_unrefp) DissectedImage *m = NULL;
|
||||
_cleanup_(rmdir_and_freep) char *tmpdir = NULL;
|
||||
_cleanup_close_pair_ int seq[2] = EBADF_PAIR, errno_pipe_fd[2] = EBADF_PAIR;
|
||||
_cleanup_(pidref_done_sigkill_wait) PidRef child = PIDREF_NULL;
|
||||
_cleanup_close_ int userns_fd = -EBADF;
|
||||
DissectImageFlags flags =
|
||||
DISSECT_IMAGE_READ_ONLY |
|
||||
DISSECT_IMAGE_GENERIC_ROOT |
|
||||
@@ -518,6 +580,18 @@ static int portable_extract_by_path(
|
||||
else
|
||||
flags |= DISSECT_IMAGE_VALIDATE_OS;
|
||||
|
||||
_cleanup_(verity_settings_done) VeritySettings verity = VERITY_SETTINGS_DEFAULT;
|
||||
r = verity_settings_load(
|
||||
&verity,
|
||||
path,
|
||||
/* root_hash_path= */ NULL,
|
||||
/* root_hash_sig_path= */ NULL);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to read verity artifacts for %s: %m", path);
|
||||
|
||||
if (verity.data_path)
|
||||
flags |= DISSECT_IMAGE_NO_PARTITION_TABLE;
|
||||
|
||||
/* We now have a loopback block device, let's fork off a child in its own mount namespace, mount it
|
||||
* there, and extract the metadata we need. The metadata is sent from the child back to us. */
|
||||
|
||||
@@ -529,38 +603,70 @@ static int portable_extract_by_path(
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to create temporary directory: %m");
|
||||
|
||||
r = dissect_loop_device(
|
||||
d,
|
||||
/* verity= */ NULL,
|
||||
/* mount_options= */ NULL,
|
||||
image_policy,
|
||||
/* image_filter= */ NULL,
|
||||
flags,
|
||||
&m);
|
||||
if (r == -ENOPKG)
|
||||
sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Couldn't identify a suitable partition table or file system in '%s'.", path);
|
||||
else if (r == -EADDRNOTAVAIL)
|
||||
sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "No root partition for specified root hash found in '%s'.", path);
|
||||
else if (r == -ENOTUNIQ)
|
||||
sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Multiple suitable root partitions found in image '%s'.", path);
|
||||
else if (r == -ENXIO)
|
||||
sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "No suitable root partition found in image '%s'.", path);
|
||||
else if (r == -EPROTONOSUPPORT)
|
||||
sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Device '%s' is loopback block device with partition scanning turned off, please turn it on.", path);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (scope == RUNTIME_SCOPE_USER) {
|
||||
userns_fd = nsresource_allocate_userns(/* name= */ NULL, NSRESOURCE_UIDS_64K);
|
||||
if (userns_fd < 0)
|
||||
return log_debug_errno(userns_fd, "Failed to allocate user namespace: %m");
|
||||
|
||||
r = verity_settings_load(&verity, path, NULL, NULL);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to load root hash: %m");
|
||||
r = mountfsd_mount_image_fd(
|
||||
rfd,
|
||||
userns_fd,
|
||||
/* options= */ NULL,
|
||||
image_policy,
|
||||
&verity,
|
||||
flags,
|
||||
&m);
|
||||
if (r < 0)
|
||||
return r;
|
||||
} else {
|
||||
_cleanup_(loop_device_unrefp) LoopDevice *d = NULL;
|
||||
|
||||
r = dissected_image_load_verity_sig_partition(m, d->fd, &verity);
|
||||
if (r < 0)
|
||||
return r;
|
||||
r = loop_device_make_by_path_at(
|
||||
rfd,
|
||||
/* path= */ NULL,
|
||||
O_RDONLY,
|
||||
/* sector_size= */ UINT32_MAX,
|
||||
LO_FLAGS_PARTSCAN,
|
||||
LOCK_SH,
|
||||
&d);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to set up loopback device for %s: %m", path);
|
||||
|
||||
r = dissected_image_guess_verity_roothash(m, &verity);
|
||||
if (r < 0)
|
||||
return r;
|
||||
r = dissect_loop_device(
|
||||
d,
|
||||
&verity,
|
||||
/* mount_options= */ NULL,
|
||||
image_policy,
|
||||
/* image_filter= */ NULL,
|
||||
flags,
|
||||
&m);
|
||||
if (r == -ENOPKG)
|
||||
sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Couldn't identify a suitable partition table or file system in '%s'.", path);
|
||||
else if (r == -EADDRNOTAVAIL)
|
||||
sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "No root partition for specified root hash found in '%s'.", path);
|
||||
else if (r == -ENOTUNIQ)
|
||||
sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Multiple suitable root partitions found in image '%s'.", path);
|
||||
else if (r == -ENXIO)
|
||||
sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "No suitable root partition found in image '%s'.", path);
|
||||
else if (r == -EPROTONOSUPPORT)
|
||||
sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Device '%s' is loopback block device with partition scanning turned off, please turn it on.", path);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = dissected_image_load_verity_sig_partition(m, d->fd, &verity);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to load verity sig partition for '%s': %m", path);
|
||||
|
||||
r = dissected_image_guess_verity_roothash(m, &verity);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to guess verity roothash for '%s': %m", path);
|
||||
}
|
||||
|
||||
if (!m->image_name) {
|
||||
r = dissected_image_name_from_path(path, &m->image_name);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
if (ret_pinned_image_policy) {
|
||||
pinned_image_policy = image_policy_new_from_dissected(m, &verity);
|
||||
@@ -574,33 +680,44 @@ static int portable_extract_by_path(
|
||||
if (pipe2(errno_pipe_fd, O_CLOEXEC|O_NONBLOCK) < 0)
|
||||
return log_debug_errno(errno, "Failed to create pipe: %m");
|
||||
|
||||
r = pidref_safe_fork("(sd-dissect)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_NEW_MOUNTNS|FORK_MOUNTNS_SLAVE|FORK_LOG, &child);
|
||||
r = pidref_safe_fork(
|
||||
"(sd-dissect)",
|
||||
FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL|(scope == RUNTIME_SCOPE_SYSTEM ? FORK_NEW_MOUNTNS|FORK_MOUNTNS_SLAVE : 0),
|
||||
&child);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0) {
|
||||
seq[0] = safe_close(seq[0]);
|
||||
errno_pipe_fd[0] = safe_close(errno_pipe_fd[0]);
|
||||
|
||||
if (scope == RUNTIME_SCOPE_USER) {
|
||||
r = detach_mount_namespace_userns(userns_fd);
|
||||
if (r < 0) {
|
||||
log_debug_errno(r, "Failed to detach mount namespace: %m");
|
||||
report_errno_and_exit(errno_pipe_fd[1], r);
|
||||
}
|
||||
}
|
||||
|
||||
r = dissected_image_mount(
|
||||
m,
|
||||
tmpdir,
|
||||
/* uid_shift= */ UID_INVALID,
|
||||
/* uid_range= */ UID_INVALID,
|
||||
/* userns_fd= */ -EBADF,
|
||||
userns_fd,
|
||||
flags);
|
||||
if (r < 0) {
|
||||
log_debug_errno(r, "Failed to mount dissected image: %m");
|
||||
log_debug_errno(r, "Failed to mount dissected image '%s': %m", path);
|
||||
report_errno_and_exit(errno_pipe_fd[1], r);
|
||||
}
|
||||
|
||||
_cleanup_close_ int rfd = open(tmpdir, O_DIRECTORY|O_CLOEXEC);
|
||||
if (rfd < 0) {
|
||||
_cleanup_close_ int mfd = open(tmpdir, O_DIRECTORY|O_CLOEXEC);
|
||||
if (mfd < 0) {
|
||||
r = log_debug_errno(errno, "Failed to open '%s': %m", tmpdir);
|
||||
report_errno_and_exit(errno_pipe_fd[1], r);
|
||||
}
|
||||
|
||||
r = extract_now(scope,
|
||||
rfd,
|
||||
mfd,
|
||||
path,
|
||||
matches,
|
||||
m->image_name,
|
||||
@@ -772,15 +889,17 @@ static int extract_image_and_extensions(
|
||||
* extension-release metadata match, otherwise reject it immediately as invalid, or it will fail when
|
||||
* the units are started. Also, collect valid portable prefixes if caller requested that. */
|
||||
if (validate_extension || ret_valid_prefixes) {
|
||||
_cleanup_free_ char *prefixes = NULL;
|
||||
_cleanup_free_ char *prefixes = NULL, *portable_scope_str = NULL;
|
||||
|
||||
r = parse_env_file_fd(os_release->fd, os_release->name,
|
||||
"ID", &id,
|
||||
"ID_LIKE", &id_like,
|
||||
"VERSION_ID", &version_id,
|
||||
"SYSEXT_LEVEL", &sysext_level,
|
||||
"CONFEXT_LEVEL", &confext_level,
|
||||
"PORTABLE_PREFIXES", &prefixes);
|
||||
r = parse_env_file_fd(
|
||||
os_release->fd, os_release->name,
|
||||
"ID", &id,
|
||||
"ID_LIKE", &id_like,
|
||||
"VERSION_ID", &version_id,
|
||||
"SYSEXT_LEVEL", &sysext_level,
|
||||
"CONFEXT_LEVEL", &confext_level,
|
||||
"PORTABLE_PREFIXES", &prefixes,
|
||||
"PORTABLE_SCOPE", &portable_scope_str);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (isempty(id))
|
||||
@@ -791,6 +910,31 @@ static int extract_image_and_extensions(
|
||||
if (!valid_prefixes)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
RuntimeScope portable_scope = RUNTIME_SCOPE_SYSTEM;
|
||||
if (portable_scope_str) {
|
||||
if (streq(portable_scope_str, "any"))
|
||||
portable_scope = _RUNTIME_SCOPE_INVALID;
|
||||
else {
|
||||
portable_scope = runtime_scope_from_string(portable_scope_str);
|
||||
if (portable_scope < 0)
|
||||
return sd_bus_error_setf(
|
||||
error,
|
||||
SD_BUS_ERROR_INVALID_ARGS,
|
||||
"Invalid PORTABLE_SCOPE value '%s' in image %s.",
|
||||
portable_scope_str,
|
||||
name_or_path);
|
||||
}
|
||||
}
|
||||
|
||||
if (portable_scope != _RUNTIME_SCOPE_INVALID && portable_scope != scope)
|
||||
return sd_bus_error_setf(
|
||||
error,
|
||||
SD_BUS_ERROR_INVALID_ARGS,
|
||||
"Image %s portable scope '%s' incompatible with portabled runtime scope '%s'.",
|
||||
name_or_path,
|
||||
runtime_scope_to_string(portable_scope),
|
||||
runtime_scope_to_string(scope));
|
||||
}
|
||||
|
||||
ORDERED_HASHMAP_FOREACH(ext, extension_images) {
|
||||
@@ -1377,6 +1521,7 @@ static int install_chroot_dropin(
|
||||
}
|
||||
|
||||
static int install_profile_dropin(
|
||||
RuntimeScope scope,
|
||||
const char *image_path,
|
||||
const PortableMetadata *m,
|
||||
const char *dropin_dir,
|
||||
@@ -1396,7 +1541,7 @@ static int install_profile_dropin(
|
||||
if (!profile)
|
||||
return 0;
|
||||
|
||||
r = find_portable_profile(profile, m->name, &from);
|
||||
r = find_portable_profile(scope, profile, m->name, &from);
|
||||
if (r < 0) {
|
||||
if (r != -ENOENT)
|
||||
return log_debug_errno(errno, "Profile '%s' is not accessible: %m", profile);
|
||||
@@ -1454,6 +1599,7 @@ static const char *attached_path(const LookupPaths *paths, PortableFlags flags)
|
||||
}
|
||||
|
||||
static int attach_unit_file(
|
||||
RuntimeScope scope,
|
||||
const LookupPaths *paths,
|
||||
const char *image_path,
|
||||
ImageType type,
|
||||
@@ -1523,7 +1669,7 @@ static int attach_unit_file(
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = install_profile_dropin(image_path, m, dropin_dir, profile, flags, &profile_dropin, changes, n_changes);
|
||||
r = install_profile_dropin(scope, image_path, m, dropin_dir, profile, flags, &profile_dropin, changes, n_changes);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@@ -1577,13 +1723,11 @@ static int attach_unit_file(
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int image_target_path(
|
||||
const char *image_path,
|
||||
PortableFlags flags,
|
||||
char **ret) {
|
||||
|
||||
const char *fn, *where;
|
||||
static int image_target_path(RuntimeScope scope, const char *image_path, PortableFlags flags, char **ret) {
|
||||
_cleanup_free_ char *where = NULL;
|
||||
const char *fn;
|
||||
char *joined = NULL;
|
||||
int r;
|
||||
|
||||
assert(image_path);
|
||||
assert(ret);
|
||||
@@ -1591,11 +1735,13 @@ static int image_target_path(
|
||||
fn = last_path_component(image_path);
|
||||
|
||||
if (flags & PORTABLE_RUNTIME)
|
||||
where = "/run/portables/";
|
||||
r = runtime_directory_generic(scope, "portables", &where);
|
||||
else
|
||||
where = "/etc/portables/";
|
||||
r = config_directory_generic(scope, "portables", &where);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
joined = strjoin(where, fn);
|
||||
joined = path_join(where, fn);
|
||||
if (!joined)
|
||||
return -ENOMEM;
|
||||
|
||||
@@ -1623,28 +1769,63 @@ static int install_image(
|
||||
if (image_in_search_path(scope, IMAGE_PORTABLE, NULL, image_path))
|
||||
return 0;
|
||||
|
||||
r = image_target_path(image_path, flags, &target);
|
||||
r = image_target_path(scope, image_path, flags, &target);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to generate image symlink path: %m");
|
||||
|
||||
(void) mkdir_parents(target, 0755);
|
||||
|
||||
if (flags & PORTABLE_MIXED_COPY_LINK) {
|
||||
r = copy_tree(image_path,
|
||||
target,
|
||||
UID_INVALID,
|
||||
GID_INVALID,
|
||||
COPY_REFLINK | COPY_FSYNC | COPY_FSYNC_FULL | COPY_SYNCFS,
|
||||
/* denylist= */ NULL,
|
||||
/* subvolumes= */ NULL);
|
||||
if (r < 0)
|
||||
return log_debug_errno(
|
||||
r,
|
||||
"Failed to copy %s %s %s: %m",
|
||||
image_path,
|
||||
glyph(GLYPH_ARROW_RIGHT),
|
||||
target);
|
||||
if (scope == RUNTIME_SCOPE_USER) {
|
||||
_cleanup_close_ int userns_fd = nsresource_allocate_userns(/* name= */ NULL, NSRESOURCE_UIDS_64K);
|
||||
if (userns_fd < 0)
|
||||
return log_debug_errno(userns_fd, "Failed to allocate user namespace: %m");
|
||||
|
||||
_cleanup_close_ int fd = open(image_path, O_DIRECTORY|O_CLOEXEC);
|
||||
if (fd < 0)
|
||||
return log_error_errno(errno, "Failed to open '%s': %m", image_path);
|
||||
|
||||
struct stat st;
|
||||
if (fstat(fd, &st) < 0)
|
||||
return log_error_errno(errno, "Failed to stat '%s': %m", image_path);
|
||||
|
||||
_cleanup_close_ int tree_fd = -EBADF;
|
||||
if (uid_is_foreign(st.st_uid)) {
|
||||
r = mountfsd_mount_directory_fd(fd, userns_fd, DISSECT_IMAGE_FOREIGN_UID, &tree_fd);
|
||||
if (r < 0)
|
||||
return r;
|
||||
} else
|
||||
tree_fd = TAKE_FD(fd);
|
||||
|
||||
_cleanup_close_ int directory_fd = -EBADF;
|
||||
r = mountfsd_make_directory(target, MODE_INVALID, /* flags= */ 0, &directory_fd);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
_cleanup_close_ int copy_fd = -EBADF;
|
||||
r = mountfsd_mount_directory_fd(directory_fd, userns_fd, DISSECT_IMAGE_FOREIGN_UID, ©_fd);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = copy_tree_at_foreign(tree_fd, copy_fd, userns_fd);
|
||||
if (r < 0)
|
||||
return r;
|
||||
} else {
|
||||
r = copy_tree(image_path,
|
||||
target,
|
||||
UID_INVALID,
|
||||
GID_INVALID,
|
||||
COPY_REFLINK | COPY_FSYNC | COPY_FSYNC_FULL | COPY_SYNCFS,
|
||||
/* denylist= */ NULL,
|
||||
/* subvolumes= */ NULL);
|
||||
if (r < 0)
|
||||
return log_debug_errno(
|
||||
r,
|
||||
"Failed to copy %s %s %s: %m",
|
||||
image_path,
|
||||
glyph(GLYPH_ARROW_RIGHT),
|
||||
target);
|
||||
}
|
||||
} else {
|
||||
if (symlink(image_path, target) < 0)
|
||||
return log_debug_errno(
|
||||
@@ -1881,6 +2062,7 @@ int portable_attach(
|
||||
|
||||
HASHMAP_FOREACH(item, unit_files) {
|
||||
r = attach_unit_file(
|
||||
scope,
|
||||
&paths,
|
||||
image->path,
|
||||
image->type,
|
||||
@@ -2187,7 +2369,7 @@ int portable_detach(
|
||||
SET_FOREACH(item, markers) {
|
||||
_cleanup_free_ char *target = NULL;
|
||||
|
||||
r = image_target_path(item, flags, &target);
|
||||
r = image_target_path(scope, item, flags, &target);
|
||||
if (r < 0) {
|
||||
log_debug_errno(r, "Failed to determine image path for '%s', ignoring: %m", item);
|
||||
continue;
|
||||
@@ -2351,10 +2533,17 @@ int portable_get_state(
|
||||
return 0;
|
||||
}
|
||||
|
||||
int portable_get_profiles(char ***ret) {
|
||||
int portable_get_profiles(RuntimeScope scope, char ***ret) {
|
||||
_cleanup_strv_free_ char **dirs = NULL;
|
||||
int r;
|
||||
|
||||
assert(ret);
|
||||
|
||||
return conf_files_list_nulstr(ret, NULL, NULL, CONF_FILES_DIRECTORY|CONF_FILES_BASENAME|CONF_FILES_FILTER_MASKED, PORTABLE_PROFILE_DIRS);
|
||||
r = portable_profile_dirs(scope, &dirs);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return conf_files_list_strv(ret, NULL, NULL, CONF_FILES_DIRECTORY|CONF_FILES_BASENAME|CONF_FILES_FILTER_MASKED, (const char* const*) dirs);
|
||||
}
|
||||
|
||||
static const char* const portable_change_type_table[_PORTABLE_CHANGE_TYPE_MAX] = {
|
||||
|
||||
@@ -110,7 +110,7 @@ int portable_get_state(
|
||||
PortableState *ret,
|
||||
sd_bus_error *error);
|
||||
|
||||
int portable_get_profiles(char ***ret);
|
||||
int portable_get_profiles(RuntimeScope scope, char ***ret);
|
||||
|
||||
void portable_changes_free(PortableChange *changes, size_t n_changes);
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@ static bool arg_no_block = false;
|
||||
static char **arg_extension_images = NULL;
|
||||
static bool arg_force = false;
|
||||
static bool arg_clean = false;
|
||||
static RuntimeScope arg_runtime_scope = RUNTIME_SCOPE_SYSTEM;
|
||||
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_extension_images, strv_freep);
|
||||
|
||||
@@ -224,9 +225,9 @@ static int acquire_bus(sd_bus **bus) {
|
||||
if (*bus)
|
||||
return 0;
|
||||
|
||||
r = bus_connect_transport(arg_transport, arg_host, RUNTIME_SCOPE_SYSTEM, bus);
|
||||
r = bus_connect_transport(arg_transport, arg_host, arg_runtime_scope, bus);
|
||||
if (r < 0)
|
||||
return bus_log_connect_error(r, arg_transport, RUNTIME_SCOPE_SYSTEM);
|
||||
return bus_log_connect_error(r, arg_transport, arg_runtime_scope);
|
||||
|
||||
(void) sd_bus_set_allow_interactive_authorization(*bus, arg_ask_password);
|
||||
|
||||
@@ -1335,6 +1336,8 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
ARG_EXTENSION,
|
||||
ARG_FORCE,
|
||||
ARG_CLEAN,
|
||||
ARG_USER,
|
||||
ARG_SYSTEM,
|
||||
};
|
||||
|
||||
static const struct option options[] = {
|
||||
@@ -1357,6 +1360,8 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
{ "extension", required_argument, NULL, ARG_EXTENSION },
|
||||
{ "force", no_argument, NULL, ARG_FORCE },
|
||||
{ "clean", no_argument, NULL, ARG_CLEAN },
|
||||
{ "user", no_argument, NULL, ARG_USER },
|
||||
{ "system", no_argument, NULL, ARG_SYSTEM },
|
||||
{}
|
||||
};
|
||||
|
||||
@@ -1468,6 +1473,14 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
arg_clean = true;
|
||||
break;
|
||||
|
||||
case ARG_USER:
|
||||
arg_runtime_scope = RUNTIME_SCOPE_USER;
|
||||
break;
|
||||
|
||||
case ARG_SYSTEM:
|
||||
arg_runtime_scope = RUNTIME_SCOPE_SYSTEM;
|
||||
break;
|
||||
|
||||
case '?':
|
||||
return -EINVAL;
|
||||
|
||||
|
||||
@@ -3,12 +3,10 @@
|
||||
#include "sd-bus.h"
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "btrfs-util.h"
|
||||
#include "bus-error.h"
|
||||
#include "bus-object.h"
|
||||
#include "bus-polkit.h"
|
||||
#include "discover-image.h"
|
||||
#include "fd-util.h"
|
||||
#include "hashmap.h"
|
||||
#include "io-util.h"
|
||||
#include "log.h"
|
||||
@@ -28,10 +26,15 @@ static int property_get_pool_path(
|
||||
void *userdata,
|
||||
sd_bus_error *error) {
|
||||
|
||||
_cleanup_free_ char *dir = NULL;
|
||||
Manager *m = ASSERT_PTR(userdata);
|
||||
|
||||
assert(bus);
|
||||
assert(reply);
|
||||
|
||||
return sd_bus_message_append(reply, "s", "/var/lib/portables");
|
||||
(void) image_get_pool_path(m->runtime_scope, IMAGE_PORTABLE, &dir);
|
||||
|
||||
return sd_bus_message_append(reply, "s", strempty(dir));
|
||||
}
|
||||
|
||||
static int property_get_pool_usage(
|
||||
@@ -43,19 +46,13 @@ static int property_get_pool_usage(
|
||||
void *userdata,
|
||||
sd_bus_error *error) {
|
||||
|
||||
_cleanup_close_ int fd = -EBADF;
|
||||
uint64_t usage = UINT64_MAX;
|
||||
Manager *m = ASSERT_PTR(userdata);
|
||||
|
||||
assert(bus);
|
||||
assert(reply);
|
||||
|
||||
fd = open("/var/lib/portables", O_RDONLY|O_CLOEXEC|O_DIRECTORY);
|
||||
if (fd >= 0) {
|
||||
BtrfsQuotaInfo q;
|
||||
|
||||
if (btrfs_subvol_get_subtree_quota_fd(fd, 0, &q) >= 0)
|
||||
usage = q.referenced;
|
||||
}
|
||||
(void) image_get_pool_usage(m->runtime_scope, IMAGE_PORTABLE, &usage);
|
||||
|
||||
return sd_bus_message_append(reply, "t", usage);
|
||||
}
|
||||
@@ -69,19 +66,13 @@ static int property_get_pool_limit(
|
||||
void *userdata,
|
||||
sd_bus_error *error) {
|
||||
|
||||
_cleanup_close_ int fd = -EBADF;
|
||||
Manager *m = ASSERT_PTR(userdata);
|
||||
uint64_t size = UINT64_MAX;
|
||||
|
||||
assert(bus);
|
||||
assert(reply);
|
||||
|
||||
fd = open("/var/lib/portables", O_RDONLY|O_CLOEXEC|O_DIRECTORY);
|
||||
if (fd >= 0) {
|
||||
BtrfsQuotaInfo q;
|
||||
|
||||
if (btrfs_subvol_get_subtree_quota_fd(fd, 0, &q) >= 0)
|
||||
size = q.referenced_max;
|
||||
}
|
||||
(void) image_get_pool_limit(m->runtime_scope, IMAGE_PORTABLE, &size);
|
||||
|
||||
return sd_bus_message_append(reply, "t", size);
|
||||
}
|
||||
@@ -96,12 +87,13 @@ static int property_get_profiles(
|
||||
sd_bus_error *error) {
|
||||
|
||||
_cleanup_strv_free_ char **l = NULL;
|
||||
Manager *m = ASSERT_PTR(userdata);
|
||||
int r;
|
||||
|
||||
assert(bus);
|
||||
assert(reply);
|
||||
|
||||
r = portable_get_profiles(&l);
|
||||
r = portable_get_profiles(m->runtime_scope, &l);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@@ -319,16 +311,18 @@ static int method_detach_image(sd_bus_message *message, void *userdata, sd_bus_e
|
||||
flags |= PORTABLE_RUNTIME;
|
||||
}
|
||||
|
||||
r = bus_verify_polkit_async(
|
||||
message,
|
||||
"org.freedesktop.portable1.attach-images",
|
||||
/* details= */ NULL,
|
||||
&m->polkit_registry,
|
||||
error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0)
|
||||
return 1; /* Will call us back */
|
||||
if (m->runtime_scope != RUNTIME_SCOPE_USER) {
|
||||
r = bus_verify_polkit_async(
|
||||
message,
|
||||
"org.freedesktop.portable1.attach-images",
|
||||
/* details= */ NULL,
|
||||
&m->polkit_registry,
|
||||
error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0)
|
||||
return 1; /* Will call us back */
|
||||
}
|
||||
|
||||
r = portable_detach(
|
||||
m->runtime_scope,
|
||||
@@ -374,21 +368,21 @@ static int method_set_pool_limit(sd_bus_message *message, void *userdata, sd_bus
|
||||
if (!FILE_SIZE_VALID_OR_INFINITY(limit))
|
||||
return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "New limit out of range");
|
||||
|
||||
r = bus_verify_polkit_async(
|
||||
message,
|
||||
"org.freedesktop.portable1.manage-images",
|
||||
/* details= */ NULL,
|
||||
&m->polkit_registry,
|
||||
error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0)
|
||||
return 1; /* Will call us back */
|
||||
if (m->runtime_scope != RUNTIME_SCOPE_USER) {
|
||||
r = bus_verify_polkit_async(
|
||||
message,
|
||||
"org.freedesktop.portable1.manage-images",
|
||||
/* details= */ NULL,
|
||||
&m->polkit_registry,
|
||||
error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0)
|
||||
return 1; /* Will call us back */
|
||||
}
|
||||
|
||||
(void) btrfs_qgroup_set_limit("/var/lib/portables", 0, limit);
|
||||
|
||||
r = btrfs_subvol_set_subtree_quota_limit("/var/lib/portables", 0, limit);
|
||||
if (r == -ENOTTY)
|
||||
r = image_set_pool_limit(m->runtime_scope, IMAGE_MACHINE, limit);
|
||||
if (ERRNO_IS_NEG_NOT_SUPPORTED(r))
|
||||
return sd_bus_error_set(error, SD_BUS_ERROR_NOT_SUPPORTED, "Quota is only supported on btrfs.");
|
||||
if (r < 0)
|
||||
return sd_bus_error_set_errnof(error, r, "Failed to adjust quota limit: %m");
|
||||
|
||||
@@ -454,16 +454,18 @@ static int bus_image_method_detach(
|
||||
flags |= PORTABLE_RUNTIME;
|
||||
}
|
||||
|
||||
r = bus_verify_polkit_async(
|
||||
message,
|
||||
"org.freedesktop.portable1.attach-images",
|
||||
/* details= */ NULL,
|
||||
&m->polkit_registry,
|
||||
error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0)
|
||||
return 1; /* Will call us back */
|
||||
if (m->runtime_scope != RUNTIME_SCOPE_USER) {
|
||||
r = bus_verify_polkit_async(
|
||||
message,
|
||||
"org.freedesktop.portable1.attach-images",
|
||||
/* details= */ NULL,
|
||||
&m->polkit_registry,
|
||||
error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0)
|
||||
return 1; /* Will call us back */
|
||||
}
|
||||
|
||||
r = portable_detach(
|
||||
m->runtime_scope,
|
||||
@@ -1015,7 +1017,7 @@ int bus_image_acquire(
|
||||
|
||||
/* Acquires an 'Image' object if not acquired yet, and enforces necessary authentication while doing so. */
|
||||
|
||||
if (mode == BUS_IMAGE_AUTHENTICATE_ALL) {
|
||||
if (mode == BUS_IMAGE_AUTHENTICATE_ALL && m->runtime_scope != RUNTIME_SCOPE_USER) {
|
||||
r = bus_verify_polkit_async(
|
||||
message,
|
||||
polkit_action,
|
||||
@@ -1066,7 +1068,7 @@ int bus_image_acquire(
|
||||
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
|
||||
"Image path '%s' is not normalized.", name_or_path);
|
||||
|
||||
if (mode == BUS_IMAGE_AUTHENTICATE_BY_PATH) {
|
||||
if (mode == BUS_IMAGE_AUTHENTICATE_BY_PATH && m->runtime_scope != RUNTIME_SCOPE_USER) {
|
||||
r = bus_verify_polkit_async(
|
||||
message,
|
||||
polkit_action,
|
||||
|
||||
+26
-13
@@ -15,6 +15,7 @@
|
||||
#include "hashmap.h"
|
||||
#include "log.h"
|
||||
#include "main-func.h"
|
||||
#include "path-lookup.h"
|
||||
#include "portabled.h"
|
||||
#include "service-util.h"
|
||||
#include "signal-util.h"
|
||||
@@ -22,7 +23,7 @@
|
||||
static Manager* manager_unref(Manager *m);
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_unref);
|
||||
|
||||
static int manager_new(Manager **ret) {
|
||||
static int manager_new(RuntimeScope scope, Manager **ret) {
|
||||
_cleanup_(manager_unrefp) Manager *m = NULL;
|
||||
int r;
|
||||
|
||||
@@ -33,9 +34,13 @@ static int manager_new(Manager **ret) {
|
||||
return -ENOMEM;
|
||||
|
||||
*m = (Manager) {
|
||||
.runtime_scope = RUNTIME_SCOPE_SYSTEM,
|
||||
.runtime_scope = scope,
|
||||
};
|
||||
|
||||
r = runtime_directory_generic(scope, "systemd/portables", &m->state_dir);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = sd_event_default(&m->event);
|
||||
if (r < 0)
|
||||
return r;
|
||||
@@ -70,6 +75,8 @@ static Manager* manager_unref(Manager *m) {
|
||||
sd_bus_flush_close_unref(m->bus);
|
||||
sd_event_unref(m->event);
|
||||
|
||||
free(m->state_dir);
|
||||
|
||||
return mfree(m);
|
||||
}
|
||||
|
||||
@@ -79,9 +86,21 @@ static int manager_connect_bus(Manager *m) {
|
||||
assert(m);
|
||||
assert(!m->bus);
|
||||
|
||||
r = sd_bus_default_system(&m->bus);
|
||||
if (m->runtime_scope == RUNTIME_SCOPE_SYSTEM) {
|
||||
r = sd_bus_default_system(&m->bus);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to connect to system bus: %m");
|
||||
} else {
|
||||
assert(m->runtime_scope == RUNTIME_SCOPE_USER);
|
||||
|
||||
r = sd_bus_default_user(&m->bus);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to connect to user bus: %m");
|
||||
}
|
||||
|
||||
r = sd_bus_attach_event(m->bus, m->event, 0);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to connect to system bus: %m");
|
||||
return log_error_errno(r, "Failed to attach user bus to event loop: %m");
|
||||
|
||||
r = bus_add_implementation(m->bus, &manager_object, m);
|
||||
if (r < 0)
|
||||
@@ -95,10 +114,6 @@ static int manager_connect_bus(Manager *m) {
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to request name: %m");
|
||||
|
||||
r = sd_bus_attach_event(m->bus, m->event, 0);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to attach bus to event loop: %m");
|
||||
|
||||
(void) sd_bus_set_exit_on_disconnect(m->bus, true);
|
||||
|
||||
return 0;
|
||||
@@ -125,6 +140,7 @@ static bool check_idle(void *userdata) {
|
||||
|
||||
static int run(int argc, char *argv[]) {
|
||||
_cleanup_(manager_unrefp) Manager *m = NULL;
|
||||
RuntimeScope scope = RUNTIME_SCOPE_SYSTEM;
|
||||
int r;
|
||||
|
||||
log_setup();
|
||||
@@ -133,17 +149,14 @@ static int run(int argc, char *argv[]) {
|
||||
"Manage registrations of portable images.",
|
||||
BUS_IMPLEMENTATIONS(&manager_object,
|
||||
&log_control_object),
|
||||
/* runtime_scope= */ NULL,
|
||||
&scope,
|
||||
argc, argv);
|
||||
if (r <= 0)
|
||||
return r;
|
||||
|
||||
umask(0022);
|
||||
|
||||
if (argc != 1)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program takes no arguments.");
|
||||
|
||||
r = manager_new(&m);
|
||||
r = manager_new(scope, &m);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to allocate manager object: %m");
|
||||
|
||||
|
||||
@@ -17,7 +17,8 @@ typedef struct Manager {
|
||||
LIST_HEAD(Operation, operations);
|
||||
unsigned n_operations;
|
||||
|
||||
RuntimeScope runtime_scope; /* for now always RUNTIME_SCOPE_SYSTEM */
|
||||
RuntimeScope runtime_scope;
|
||||
char *state_dir;
|
||||
} Manager;
|
||||
|
||||
extern const BusObjectImplementation manager_object;
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
# The "default" security profile for services, i.e. a number of useful restrictions
|
||||
|
||||
[Service]
|
||||
MountAPIVFS=yes
|
||||
BindLogSockets=yes
|
||||
BindReadOnlyPaths=/etc/machine-id
|
||||
BindReadOnlyPaths=-/etc/resolv.conf
|
||||
BindReadOnlyPaths=/run/dbus/system_bus_socket
|
||||
RemoveIPC=yes
|
||||
CapabilityBoundingSet=CAP_CHOWN CAP_DAC_OVERRIDE CAP_DAC_READ_SEARCH CAP_FOWNER \
|
||||
CAP_FSETID CAP_IPC_LOCK CAP_IPC_OWNER CAP_KILL CAP_MKNOD CAP_NET_ADMIN \
|
||||
CAP_NET_BIND_SERVICE CAP_NET_BROADCAST CAP_SETGID CAP_SETPCAP \
|
||||
CAP_SETUID CAP_SYS_ADMIN CAP_SYS_CHROOT CAP_SYS_NICE CAP_SYS_RESOURCE
|
||||
PrivateDevices=yes
|
||||
PrivateUsers=yes
|
||||
ProtectSystem=strict
|
||||
ProtectKernelTunables=yes
|
||||
ProtectKernelModules=yes
|
||||
ProtectControlGroups=yes
|
||||
RestrictAddressFamilies=AF_UNIX AF_NETLINK AF_INET AF_INET6
|
||||
LockPersonality=yes
|
||||
MemoryDenyWriteExecute=yes
|
||||
RestrictRealtime=yes
|
||||
RestrictNamespaces=yes
|
||||
DelegateNamespaces=no
|
||||
SystemCallFilter=@system-service
|
||||
SystemCallErrorNumber=EPERM
|
||||
SystemCallArchitectures=native
|
||||
@@ -0,0 +1,28 @@
|
||||
# The "nonetwork" security profile for services, i.e. like "default" but without networking
|
||||
|
||||
[Service]
|
||||
MountAPIVFS=yes
|
||||
BindLogSockets=yes
|
||||
BindReadOnlyPaths=/etc/machine-id
|
||||
BindReadOnlyPaths=/run/dbus/system_bus_socket
|
||||
RemoveIPC=yes
|
||||
CapabilityBoundingSet=CAP_CHOWN CAP_DAC_OVERRIDE CAP_DAC_READ_SEARCH CAP_FOWNER \
|
||||
CAP_FSETID CAP_IPC_LOCK CAP_IPC_OWNER CAP_KILL CAP_MKNOD CAP_SETGID CAP_SETPCAP \
|
||||
CAP_SETUID CAP_SYS_ADMIN CAP_SYS_CHROOT CAP_SYS_NICE CAP_SYS_RESOURCE
|
||||
PrivateDevices=yes
|
||||
PrivateUsers=yes
|
||||
ProtectSystem=strict
|
||||
ProtectKernelTunables=yes
|
||||
ProtectKernelModules=yes
|
||||
ProtectControlGroups=yes
|
||||
RestrictAddressFamilies=AF_UNIX AF_NETLINK
|
||||
LockPersonality=yes
|
||||
MemoryDenyWriteExecute=yes
|
||||
RestrictRealtime=yes
|
||||
RestrictNamespaces=yes
|
||||
DelegateNamespaces=no
|
||||
SystemCallFilter=@system-service
|
||||
SystemCallErrorNumber=EPERM
|
||||
SystemCallArchitectures=native
|
||||
PrivateNetwork=yes
|
||||
IPAddressDeny=any
|
||||
@@ -0,0 +1,27 @@
|
||||
# The "strict" security profile for services, all options turned on
|
||||
|
||||
[Service]
|
||||
MountAPIVFS=yes
|
||||
BindLogSockets=yes
|
||||
BindReadOnlyPaths=/etc/machine-id
|
||||
RemoveIPC=yes
|
||||
CapabilityBoundingSet=
|
||||
PrivateDevices=yes
|
||||
PrivateUsers=yes
|
||||
ProtectSystem=strict
|
||||
ProtectKernelTunables=yes
|
||||
ProtectKernelModules=yes
|
||||
ProtectControlGroups=yes
|
||||
RestrictAddressFamilies=AF_UNIX
|
||||
LockPersonality=yes
|
||||
NoNewPrivileges=yes
|
||||
MemoryDenyWriteExecute=yes
|
||||
RestrictRealtime=yes
|
||||
RestrictNamespaces=yes
|
||||
DelegateNamespaces=no
|
||||
SystemCallFilter=@system-service
|
||||
SystemCallErrorNumber=EPERM
|
||||
SystemCallArchitectures=native
|
||||
PrivateNetwork=yes
|
||||
IPAddressDeny=any
|
||||
TasksMax=4
|
||||
@@ -0,0 +1,8 @@
|
||||
# The "trusted" profile for services, i.e. no restrictions are applied apart from a private /tmp
|
||||
|
||||
[Service]
|
||||
MountAPIVFS=yes
|
||||
PrivateTmp=yes
|
||||
BindPaths=/run
|
||||
BindReadOnlyPaths=/etc/machine-id
|
||||
BindReadOnlyPaths=-/etc/resolv.conf
|
||||
@@ -663,7 +663,7 @@ static int pick_image_search_path(
|
||||
}
|
||||
|
||||
case RUNTIME_SCOPE_USER: {
|
||||
if (class != IMAGE_MACHINE)
|
||||
if (!IN_SET(class, IMAGE_MACHINE, IMAGE_PORTABLE))
|
||||
break;
|
||||
|
||||
static const uint64_t dirs[] = {
|
||||
@@ -676,7 +676,7 @@ static int pick_image_search_path(
|
||||
FOREACH_ELEMENT(d, dirs) {
|
||||
_cleanup_free_ char *p = NULL;
|
||||
|
||||
r = sd_path_lookup(*d, "machines", &p);
|
||||
r = sd_path_lookup(*d, image_dirname_to_string(class), &p);
|
||||
if (r == -ENXIO) /* No XDG_RUNTIME_DIR set */
|
||||
continue;
|
||||
if (r < 0)
|
||||
|
||||
@@ -4512,7 +4512,7 @@ Architecture dissected_image_architecture(DissectedImage *m) {
|
||||
}
|
||||
|
||||
bool dissected_image_is_portable(DissectedImage *m) {
|
||||
return m && strv_env_pairs_get(m->os_release, "PORTABLE_PREFIXES");
|
||||
return m && (strv_env_pairs_get(m->os_release, "PORTABLE_PREFIXES") || strv_env_pairs_get(m->os_release, "PORTABLE_SCOPE"));
|
||||
}
|
||||
|
||||
bool dissected_image_is_initrd(DissectedImage *m) {
|
||||
|
||||
@@ -5,10 +5,70 @@
|
||||
#include "alloc-util.h"
|
||||
#include "fs-util.h"
|
||||
#include "nulstr-util.h"
|
||||
#include "path-lookup.h"
|
||||
#include "portable-util.h"
|
||||
#include "string-util.h"
|
||||
#include "strv.h"
|
||||
|
||||
int find_portable_profile(const char *name, const char *unit, char **ret_path) {
|
||||
int portable_profile_dirs(RuntimeScope scope, char ***ret) {
|
||||
_cleanup_strv_free_ char **dirs = NULL;
|
||||
int r;
|
||||
|
||||
assert(ret);
|
||||
|
||||
switch (scope) {
|
||||
|
||||
case RUNTIME_SCOPE_SYSTEM:
|
||||
r = strv_from_nulstr(&dirs, PORTABLE_PROFILE_DIRS);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
break;
|
||||
|
||||
case RUNTIME_SCOPE_USER: {
|
||||
_cleanup_free_ char *d = NULL;
|
||||
|
||||
r = xdg_user_config_dir("systemd/portable/profile", &d);
|
||||
if (r < 0 && r != -ENXIO)
|
||||
return r;
|
||||
if (r >= 0) {
|
||||
r = strv_consume(&dirs, TAKE_PTR(d));
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
r = xdg_user_runtime_dir("systemd/portable/profile", &d);
|
||||
if (r < 0 && r != -ENXIO)
|
||||
return r;
|
||||
if (r >= 0) {
|
||||
r = strv_consume(&dirs, TAKE_PTR(d));
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
_fallthrough_;
|
||||
}
|
||||
|
||||
case RUNTIME_SCOPE_GLOBAL:
|
||||
r = strv_extend_strv(
|
||||
&dirs,
|
||||
CONF_PATHS_STRV("systemd/user/portable/profile"),
|
||||
/* filter_duplicates= */ false);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
*ret = TAKE_PTR(dirs);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int find_portable_profile(RuntimeScope scope, const char *name, const char *unit, char **ret_path) {
|
||||
_cleanup_strv_free_ char **dirs = NULL;
|
||||
const char *dot;
|
||||
int r;
|
||||
|
||||
@@ -17,10 +77,14 @@ int find_portable_profile(const char *name, const char *unit, char **ret_path) {
|
||||
|
||||
assert_se(dot = strrchr(unit, '.'));
|
||||
|
||||
NULSTR_FOREACH(p, PORTABLE_PROFILE_DIRS) {
|
||||
r = portable_profile_dirs(scope, &dirs);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
STRV_FOREACH(p, dirs) {
|
||||
_cleanup_free_ char *joined = NULL;
|
||||
|
||||
joined = strjoin(p, "/", name, "/", dot + 1, ".conf");
|
||||
joined = strjoin(*p, "/", name, "/", dot + 1, ".conf");
|
||||
if (!joined)
|
||||
return -ENOMEM;
|
||||
|
||||
|
||||
@@ -6,4 +6,5 @@
|
||||
|
||||
#define PORTABLE_PROFILE_DIRS CONF_PATHS_NULSTR("systemd/portable/profile")
|
||||
|
||||
int find_portable_profile(const char *name, const char *unit, char **ret_path);
|
||||
int portable_profile_dirs(RuntimeScope scope, char ***ret);
|
||||
int find_portable_profile(RuntimeScope scope, const char *name, const char *unit, char **ret_path);
|
||||
|
||||
Executable
+324
@@ -0,0 +1,324 @@
|
||||
#!/usr/bin/env bash
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*-
|
||||
# ex: ts=8 sw=4 sts=4 et filetype=sh
|
||||
# shellcheck disable=SC2233,SC2235
|
||||
set -eux
|
||||
set -o pipefail
|
||||
|
||||
# shellcheck source=test/units/util.sh
|
||||
. "$(dirname "$0")"/util.sh
|
||||
|
||||
if [[ ! -f /usr/lib/systemd/system/systemd-mountfsd.socket ]] ||
|
||||
[[ ! -f /usr/lib/systemd/system/systemd-nsresourced.socket ]] ||
|
||||
! command -v mksquashfs ||
|
||||
! grep -q bpf /sys/kernel/security/lsm ||
|
||||
! find /usr/lib* -name libbpf.so.1 2>/dev/null | grep . ||
|
||||
systemd-analyze compare-versions "$(uname -r)" lt 6.5 ||
|
||||
systemd-analyze compare-versions "$(pkcheck --version | awk '{print $3}')" lt 124 ||
|
||||
systemctl --version | grep -- "-BTF" >/dev/null; then
|
||||
echo "Skipping mountfsd/nsresourced tests"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
systemctl start systemd-mountfsd.socket systemd-nsresourced.socket
|
||||
|
||||
# Arrays cannot be exported, so redefine in each test script
|
||||
ARGS=()
|
||||
if [[ -v ASAN_OPTIONS || -v UBSAN_OPTIONS ]]; then
|
||||
# If we're running under sanitizers, we need to use a less restrictive
|
||||
# profile, otherwise LSan syscall would get blocked by seccomp
|
||||
ARGS+=(--profile=trusted)
|
||||
fi
|
||||
|
||||
# To be able to mount images as an unprivileged user we need verity sidecars so generate them for app1 which
|
||||
# doesn't have them by default.
|
||||
veritysetup format /tmp/app1.raw /tmp/app1.verity --root-hash-file /tmp/app1.roothash
|
||||
openssl smime -sign -nocerts -noattr -binary \
|
||||
-in /tmp/app1.roothash \
|
||||
-inkey /usr/share/mkosi.key \
|
||||
-signer /usr/share/mkosi.crt \
|
||||
-outform der \
|
||||
-out /tmp/app1.roothash.p7s
|
||||
chmod go+r /tmp/app1*
|
||||
|
||||
at_exit() {
|
||||
set +e
|
||||
|
||||
rm -f /tmp/app1.verity /tmp/app1.roothash /tmp/app1.roothash.p7s
|
||||
loginctl disable-linger testuser
|
||||
}
|
||||
|
||||
trap at_exit EXIT
|
||||
|
||||
# For unprivileged user manager
|
||||
loginctl enable-linger testuser
|
||||
|
||||
systemctl start user@4711.service
|
||||
|
||||
portablectl_user() {
|
||||
runas testuser env XDG_RUNTIME_DIR=/run/user/4711 portablectl --user "$@"
|
||||
}
|
||||
|
||||
busctl_user() {
|
||||
runas testuser env XDG_RUNTIME_DIR=/run/user/4711 busctl --user "$@"
|
||||
}
|
||||
|
||||
systemctl_user() {
|
||||
runas testuser env XDG_RUNTIME_DIR=/run/user/4711 systemctl --user "$@"
|
||||
}
|
||||
|
||||
runas_user() {
|
||||
runas testuser env XDG_RUNTIME_DIR=/run/user/4711 "$@"
|
||||
}
|
||||
|
||||
# Start the user portable daemon
|
||||
systemctl_user start dbus-org.freedesktop.portable1.service
|
||||
|
||||
: "Test basic attach, reattach and detach for user portable services"
|
||||
|
||||
portablectl_user "${ARGS[@]}" attach --now --runtime /usr/share/minimal_0.raw minimal-app0
|
||||
|
||||
portablectl_user is-attached minimal-app0
|
||||
portablectl_user inspect /usr/share/minimal_0.raw minimal-app0.service
|
||||
systemctl_user is-active minimal-app0.service
|
||||
systemctl_user is-active minimal-app0-foo.service
|
||||
systemctl_user is-active minimal-app0-bar.service && exit 1
|
||||
|
||||
# Ensure pinning by policy works
|
||||
cat /run/user/4711/systemd/user.attached/minimal-app0-foo.service.d/20-portable.conf
|
||||
grep -q -F 'root=signed+squashfs:' /run/user/4711/systemd/user.attached/minimal-app0-foo.service.d/20-portable.conf
|
||||
|
||||
portablectl_user "${ARGS[@]}" reattach --now --runtime /usr/share/minimal_1.raw minimal-app0
|
||||
|
||||
portablectl_user is-attached minimal-app0
|
||||
portablectl_user inspect /usr/share/minimal_0.raw minimal-app0.service
|
||||
systemctl_user is-active minimal-app0.service
|
||||
systemctl_user is-active minimal-app0-bar.service
|
||||
systemctl_user is-active minimal-app0-foo.service && exit 1
|
||||
|
||||
portablectl_user list | grep -F "minimal_1" >/dev/null
|
||||
busctl_user tree org.freedesktop.portable1 --no-pager | grep -F '/org/freedesktop/portable1/image/minimal_5f1' >/dev/null
|
||||
|
||||
portablectl_user detach --now --runtime /usr/share/minimal_1.raw minimal-app0
|
||||
|
||||
portablectl_user list | grep -F "No images." >/dev/null
|
||||
busctl_user tree org.freedesktop.portable1 --no-pager | grep -F '/org/freedesktop/portable1/image/minimal_5f1' && exit 1 >/dev/null
|
||||
|
||||
: "Test --force for user portable services"
|
||||
|
||||
runas_user mkdir -p /run/user/4711/systemd/user.attached/minimal-app0.service.d/
|
||||
runas_user tee /run/user/4711/systemd/user.attached/minimal-app0.service >/dev/null <<EOF
|
||||
[Unit]
|
||||
Description=Minimal App 0
|
||||
EOF
|
||||
runas_user tee /run/user/4711/systemd/user.attached/minimal-app0.service.d/10-profile.conf >/dev/null <<EOF
|
||||
[Unit]
|
||||
Description=Minimal App 0
|
||||
EOF
|
||||
runas_user tee /run/user/4711/systemd/user.attached/minimal-app0.service.d/20-portable.conf >/dev/null <<EOF
|
||||
[Unit]
|
||||
Description=Minimal App 0
|
||||
EOF
|
||||
systemctl_user daemon-reload
|
||||
|
||||
portablectl_user "${ARGS[@]}" attach --force --now --runtime /usr/share/minimal_0.raw minimal-app0
|
||||
|
||||
portablectl_user is-attached --force minimal-app0
|
||||
portablectl_user inspect --force /usr/share/minimal_0.raw minimal-app0.service
|
||||
systemctl_user is-active minimal-app0.service
|
||||
systemctl_user is-active minimal-app0-foo.service
|
||||
systemctl_user is-active minimal-app0-bar.service && exit 1
|
||||
|
||||
portablectl_user "${ARGS[@]}" reattach --force --now --runtime /usr/share/minimal_1.raw minimal-app0
|
||||
|
||||
portablectl_user is-attached --force minimal-app0
|
||||
portablectl_user inspect --force /usr/share/minimal_0.raw minimal-app0.service
|
||||
systemctl_user is-active minimal-app0.service
|
||||
systemctl_user is-active minimal-app0-bar.service
|
||||
systemctl_user is-active minimal-app0-foo.service && exit 1
|
||||
|
||||
portablectl_user list | grep -F "minimal_1" >/dev/null
|
||||
busctl_user tree org.freedesktop.portable1 --no-pager | grep -F '/org/freedesktop/portable1/image/minimal_5f1' >/dev/null
|
||||
|
||||
portablectl_user detach --force --now --runtime /usr/share/minimal_1.raw minimal-app0
|
||||
|
||||
portablectl_user list | grep -F "No images." >/dev/null
|
||||
busctl_user tree org.freedesktop.portable1 --no-pager | grep -F '/org/freedesktop/portable1/image/minimal_5f1' && exit 1 >/dev/null
|
||||
|
||||
: "Test extension images for user portable services"
|
||||
|
||||
portablectl_user "${ARGS[@]}" attach --now --runtime --extension /tmp/app0.raw /usr/share/minimal_0.raw app0
|
||||
|
||||
systemctl_user is-active app0.service
|
||||
status="$(portablectl_user is-attached --extension app0 minimal_0)"
|
||||
[[ "${status}" == "running-runtime" ]]
|
||||
|
||||
grep -q -F "LogExtraFields=PORTABLE_ROOT=minimal_0.raw" /run/user/4711/systemd/user.attached/app0.service.d/20-portable.conf
|
||||
grep -q -F "LogExtraFields=PORTABLE_EXTENSION=app0.raw" /run/user/4711/systemd/user.attached/app0.service.d/20-portable.conf
|
||||
grep -q -F "LogExtraFields=PORTABLE_EXTENSION_NAME_AND_VERSION=app" /run/user/4711/systemd/user.attached/app0.service.d/20-portable.conf
|
||||
# Ensure pinning by policy works
|
||||
grep -q -F 'RootImagePolicy=root=signed+squashfs:' /run/user/4711/systemd/user.attached/app0.service.d/20-portable.conf >/dev/null
|
||||
grep -q -F 'ExtensionImagePolicy=root=signed+squashfs:' /run/user/4711/systemd/user.attached/app0.service.d/20-portable.conf >/dev/null
|
||||
|
||||
portablectl_user "${ARGS[@]}" reattach --now --runtime --extension /tmp/app0.raw /usr/share/minimal_1.raw app0
|
||||
|
||||
systemctl_user is-active app0.service
|
||||
status="$(portablectl_user is-attached --extension app0 minimal_1)"
|
||||
[[ "${status}" == "running-runtime" ]]
|
||||
|
||||
grep -q -F "LogExtraFields=PORTABLE_ROOT=minimal_1.raw" /run/user/4711/systemd/user.attached/app0.service.d/20-portable.conf
|
||||
grep -q -F "LogExtraFields=PORTABLE_EXTENSION=app0.raw" /run/user/4711/systemd/user.attached/app0.service.d/20-portable.conf
|
||||
grep -q -F "LogExtraFields=PORTABLE_EXTENSION_NAME_AND_VERSION=app" /run/user/4711/systemd/user.attached/app0.service.d/20-portable.conf
|
||||
|
||||
portablectl_user detach --now --runtime --extension /tmp/app0.raw /usr/share/minimal_1.raw app0
|
||||
|
||||
: "Test versioned extension images for user portable services"
|
||||
|
||||
cp /tmp/app0.raw /tmp/app0_1.0.raw
|
||||
cp /tmp/app0.verity /tmp/app0_1.0.verity
|
||||
cp /tmp/app0.roothash /tmp/app0_1.0.roothash
|
||||
cp /tmp/app0.roothash.p7s /tmp/app0_1.0.roothash.p7s
|
||||
portablectl_user "${ARGS[@]}" attach --now --runtime --extension /tmp/app0_1.0.raw /usr/share/minimal_0.raw app0
|
||||
|
||||
systemctl_user is-active app0.service
|
||||
status="$(portablectl_user is-attached --extension app0_1 minimal_0)"
|
||||
[[ "${status}" == "running-runtime" ]]
|
||||
|
||||
portablectl_user detach --now --runtime --extension /tmp/app0_1.0.raw /usr/share/minimal_1.raw app0
|
||||
rm -f /tmp/app0_1.0*
|
||||
|
||||
: "Test reattach with version changes for user portable services"
|
||||
|
||||
portablectl_user "${ARGS[@]}" attach --now --runtime --extension /tmp/app1.raw /usr/share/minimal_0.raw app1
|
||||
|
||||
systemctl_user is-active app1.service
|
||||
status="$(portablectl_user is-attached --extension app1 minimal_0)"
|
||||
[[ "${status}" == "running-runtime" ]]
|
||||
|
||||
# Ensure that adding or removing a version to the image doesn't break reattaching
|
||||
cp /tmp/app1.raw /tmp/app1_2.raw
|
||||
cp /tmp/app1.verity /tmp/app1_2.verity
|
||||
cp /tmp/app1.roothash /tmp/app1_2.roothash
|
||||
cp /tmp/app1.roothash.p7s /tmp/app1_2.roothash.p7s
|
||||
portablectl_user "${ARGS[@]}" reattach --now --runtime --extension /tmp/app1_2.raw /usr/share/minimal_1.raw app1
|
||||
|
||||
systemctl_user is-active app1.service
|
||||
status="$(portablectl_user is-attached --extension app1_2 minimal_1)"
|
||||
[[ "${status}" == "running-runtime" ]]
|
||||
|
||||
portablectl_user "${ARGS[@]}" reattach --now --runtime --extension /tmp/app1.raw /usr/share/minimal_1.raw app1
|
||||
|
||||
systemctl_user is-active app1.service
|
||||
status="$(portablectl_user is-attached --extension app1 minimal_1)"
|
||||
[[ "${status}" == "running-runtime" ]]
|
||||
|
||||
portablectl_user detach --now --runtime --extension /tmp/app1.raw /usr/share/minimal_1.raw app1
|
||||
|
||||
: "Test --no-reload for user portable services"
|
||||
|
||||
portablectl_user "${ARGS[@]}" attach --now --runtime --extension /tmp/app1.raw /usr/share/minimal_0.raw app1
|
||||
|
||||
systemctl_user is-active app1.service
|
||||
status="$(portablectl_user is-attached --extension app1 minimal_0)"
|
||||
[[ "${status}" == "running-runtime" ]]
|
||||
|
||||
portablectl_user detach --force --no-reload --runtime --extension /tmp/app1.raw /usr/share/minimal_0.raw app1
|
||||
portablectl_user "${ARGS[@]}" attach --force --no-reload --runtime --extension /tmp/app1.raw /usr/share/minimal_0.raw app1
|
||||
systemctl_user daemon-reload
|
||||
systemctl_user restart app1.service
|
||||
|
||||
systemctl_user is-active app1.service
|
||||
status="$(portablectl_user is-attached --extension app1 minimal_0)"
|
||||
[[ "${status}" == "running-runtime" ]]
|
||||
|
||||
portablectl_user detach --now --runtime --extension /tmp/app1.raw /usr/share/minimal_0.raw app1
|
||||
|
||||
# : "Test vpick for user portable services"
|
||||
|
||||
mkdir -p /tmp/app1.v/
|
||||
cp /tmp/app1.raw /tmp/app1.v/app1_1.0.raw
|
||||
cp /tmp/app1.verity /tmp/app1.v/app1_1.0.verity
|
||||
cp /tmp/app1.roothash /tmp/app1.v/app1_1.0.roothash
|
||||
cp /tmp/app1.roothash.p7s /tmp/app1.v/app1_1.0.roothash.p7s
|
||||
cp /tmp/app1_2.raw /tmp/app1.v/app1_2.0.raw
|
||||
cp /tmp/app1_2.verity /tmp/app1.v/app1_2.0.verity
|
||||
cp /tmp/app1_2.roothash /tmp/app1.v/app1_2.0.roothash
|
||||
cp /tmp/app1_2.roothash.p7s /tmp/app1.v/app1_2.0.roothash.p7s
|
||||
portablectl_user "${ARGS[@]}" attach --now --runtime --extension /tmp/app1.v/ /usr/share/minimal_1.raw app1
|
||||
|
||||
systemctl_user is-active app1.service
|
||||
status="$(portablectl_user is-attached --extension app1_2.0.raw minimal_1)"
|
||||
[[ "${status}" == "running-runtime" ]]
|
||||
|
||||
rm -f /tmp/app1.v/app1_2.0*
|
||||
portablectl_user "${ARGS[@]}" reattach --now --runtime --extension /tmp/app1.v/ /usr/share/minimal_1.raw app1
|
||||
|
||||
systemctl_user is-active app1.service
|
||||
status="$(portablectl_user is-attached --extension app1_1.0.raw minimal_1)"
|
||||
[[ "${status}" == "running-runtime" ]]
|
||||
|
||||
portablectl_user detach --now --runtime --extension /tmp/app1.v/ /usr/share/minimal_0.raw app1
|
||||
rm -f /tmp/app1.v/app1_1.0*
|
||||
|
||||
: "Test extension-release.NAME override for user portable services"
|
||||
|
||||
cp /tmp/app0.raw /tmp/app10.raw
|
||||
cp /tmp/app0.verity /tmp/app10.verity
|
||||
cp /tmp/app0.roothash /tmp/app10.roothash
|
||||
cp /tmp/app0.roothash.p7s /tmp/app10.roothash.p7s
|
||||
portablectl_user "${ARGS[@]}" attach --force --now --runtime --extension /tmp/app10.raw /usr/share/minimal_0.raw app0
|
||||
|
||||
systemctl_user is-active app0.service
|
||||
status="$(portablectl_user is-attached --extension /tmp/app10.raw /usr/share/minimal_0.raw)"
|
||||
[[ "${status}" == "running-runtime" ]]
|
||||
|
||||
portablectl_user inspect --force --cat --extension /tmp/app10.raw /usr/share/minimal_0.raw app0 | grep -F "Extension Release: /tmp/app10.raw" >/dev/null
|
||||
|
||||
# Ensure that we can detach even when an image has been deleted already (stop the unit manually as
|
||||
# portablectl won't find it)
|
||||
rm -f /tmp/app10*
|
||||
systemctl_user stop app0.service
|
||||
portablectl_user detach --force --runtime --extension /tmp/app10.raw /usr/share/minimal_0.raw app0
|
||||
|
||||
: "Test confext images for user portable services"
|
||||
|
||||
portablectl_user "${ARGS[@]}" attach --now --runtime --extension /tmp/app0.raw --extension /tmp/conf0.raw /usr/share/minimal_0.raw app0
|
||||
|
||||
systemctl_user is-active app0.service
|
||||
status="$(portablectl_user is-attached --extension /tmp/app0.raw --extension /tmp/conf0.raw /usr/share/minimal_0.raw)"
|
||||
[[ "${status}" == "running-runtime" ]]
|
||||
|
||||
portablectl_user inspect --force --cat --extension /tmp/app0.raw --extension /tmp/conf0.raw /usr/share/minimal_0.raw app0 | grep -F "Extension Release: /tmp/conf0.raw" >/dev/null
|
||||
|
||||
portablectl_user detach --now --runtime --extension /tmp/app0.raw --extension /tmp/conf0.raw /usr/share/minimal_0.raw app0
|
||||
|
||||
: "Test multiple portables sharing the same base image for user portable services"
|
||||
|
||||
portablectl_user "${ARGS[@]}" attach --runtime --extension /tmp/app0.raw /usr/share/minimal_0.raw app0
|
||||
portablectl_user "${ARGS[@]}" attach --runtime --extension /tmp/app1.raw /usr/share/minimal_0.raw app1
|
||||
|
||||
status="$(portablectl_user is-attached --extension app0 minimal_0)"
|
||||
[[ "${status}" == "attached-runtime" ]]
|
||||
status="$(portablectl_user is-attached --extension app1 minimal_0)"
|
||||
[[ "${status}" == "attached-runtime" ]]
|
||||
|
||||
(! portablectl_user detach --runtime /usr/share/minimal_0.raw app)
|
||||
|
||||
status="$(portablectl_user is-attached --extension app0 minimal_0)"
|
||||
[[ "${status}" == "attached-runtime" ]]
|
||||
status="$(portablectl_user is-attached --extension app1 minimal_0)"
|
||||
[[ "${status}" == "attached-runtime" ]]
|
||||
|
||||
# Ensure 'portablectl list' shows the correct status for both images
|
||||
portablectl_user list
|
||||
portablectl_user list | grep -F "minimal_0" | grep -F "attached-runtime" >/dev/null
|
||||
portablectl_user list | grep -F "app0" | grep -F "attached-runtime" >/dev/null
|
||||
portablectl_user list | grep -F "app1" | grep -F "attached-runtime" >/dev/null
|
||||
|
||||
portablectl_user detach --runtime --extension /tmp/app0.raw /usr/share/minimal_0.raw app
|
||||
|
||||
status="$(portablectl_user is-attached --extension app1 minimal_0)"
|
||||
[[ "${status}" == "attached-runtime" ]]
|
||||
|
||||
portablectl_user detach --runtime --extension /tmp/app1.raw /usr/share/minimal_0.raw app
|
||||
+12
-5
@@ -296,22 +296,25 @@ install_extension_images() {
|
||||
fi
|
||||
|
||||
local initdir="/var/tmp/app0"
|
||||
mkdir -p "$initdir/usr/lib/extension-release.d" "$initdir/usr/lib/systemd/system" "$initdir/opt"
|
||||
mkdir -p "$initdir/usr/lib/extension-release.d" "$initdir/opt"
|
||||
grep "^ID=" "$os_release" >"$initdir/usr/lib/extension-release.d/extension-release.app0"
|
||||
echo "$version_id" >>"$initdir/usr/lib/extension-release.d/extension-release.app0"
|
||||
(
|
||||
echo "$version_id"
|
||||
echo "SYSEXT_IMAGE_ID=app"
|
||||
) >>"$initdir/usr/lib/extension-release.d/extension-release.app0"
|
||||
cat >"$initdir/usr/lib/systemd/system/app0.service" <<EOF
|
||||
for scope in system user; do
|
||||
mkdir -p "$initdir/usr/lib/systemd/$scope"
|
||||
cat >"$initdir/usr/lib/systemd/$scope/app0.service" <<EOF
|
||||
[Service]
|
||||
Type=oneshot
|
||||
RemainAfterExit=yes
|
||||
ExecStart=/opt/script0.sh
|
||||
TemporaryFileSystem=/var/lib
|
||||
TemporaryFileSystem=/var/lib /home
|
||||
StateDirectory=app0
|
||||
RuntimeDirectory=app0
|
||||
EOF
|
||||
done
|
||||
cat >"$initdir/opt/script0.sh" <<EOF
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
@@ -351,7 +354,7 @@ EOF
|
||||
chmod go+r /tmp/conf0*
|
||||
|
||||
initdir="/var/tmp/app1"
|
||||
mkdir -p "$initdir/usr/lib/extension-release.d" "$initdir/usr/lib/systemd/system" "$initdir/opt"
|
||||
mkdir -p "$initdir/usr/lib/extension-release.d" "$initdir/opt"
|
||||
grep "^ID=" "$os_release" >"$initdir/usr/lib/extension-release.d/extension-release.app2"
|
||||
(
|
||||
echo "$version_id"
|
||||
@@ -361,14 +364,18 @@ EOF
|
||||
echo "PORTABLE_PREFIXES=app1"
|
||||
) >>"$initdir/usr/lib/extension-release.d/extension-release.app2"
|
||||
setfattr -n user.extension-release.strict -v false "$initdir/usr/lib/extension-release.d/extension-release.app2"
|
||||
cat >"$initdir/usr/lib/systemd/system/app1.service" <<EOF
|
||||
for scope in system user; do
|
||||
mkdir -p "$initdir/usr/lib/systemd/$scope"
|
||||
cat >"$initdir/usr/lib/systemd/$scope/app1.service" <<EOF
|
||||
[Service]
|
||||
Type=oneshot
|
||||
RemainAfterExit=yes
|
||||
ExecStart=/opt/script1.sh
|
||||
TemporaryFileSystem=/home
|
||||
StateDirectory=app1
|
||||
RuntimeDirectory=app1
|
||||
EOF
|
||||
done
|
||||
cat >"$initdir/opt/script1.sh" <<EOF
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
@@ -38,6 +38,11 @@ units = [
|
||||
'conditions' : ['ENABLE_IMPORTD'],
|
||||
'symlinks' : ['sockets.target.wants/'],
|
||||
},
|
||||
{
|
||||
'file' : 'systemd-portabled.service.in',
|
||||
'conditions' : ['ENABLE_PORTABLED'],
|
||||
'symlinks' : ['dbus-org.freedesktop.portable1.service'],
|
||||
},
|
||||
{ 'file' : 'paths.target' },
|
||||
{ 'file' : 'printer.target' },
|
||||
{ 'file' : 'session.slice' },
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
#
|
||||
# This file is part of systemd.
|
||||
#
|
||||
# systemd is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation; either version 2.1 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
[Unit]
|
||||
Description=Portable Service Manager
|
||||
Documentation=man:systemd-portabled.service(8)
|
||||
Documentation=man:org.freedesktop.portable1(5)
|
||||
|
||||
[Service]
|
||||
ExecStart={{LIBEXECDIR}}/systemd-portabled --user
|
||||
BusName=org.freedesktop.portable1
|
||||
MemoryDenyWriteExecute=yes
|
||||
RestrictRealtime=yes
|
||||
RestrictAddressFamilies=AF_UNIX AF_NETLINK AF_INET AF_INET6
|
||||
SystemCallFilter=@system-service @mount
|
||||
SystemCallErrorNumber=EPERM
|
||||
SystemCallArchitectures=native
|
||||
LockPersonality=yes
|
||||
IPAddressDeny=any
|
||||
{{SERVICE_WATCHDOG}}
|
||||
Reference in New Issue
Block a user