1
0
mirror of https://github.com/systemd/systemd synced 2025-11-06 10:24:44 +01:00

Compare commits

..

15 Commits

Author SHA1 Message Date
jouyouyun
05b880ac46 nss-myhostname: use FAMILY_ADDRESS_SIZE instead of the integer 16 when copying ip addr
To avoid copying extra characters when using IPv4.
2025-11-05 01:33:31 +09:00
Daan De Meyer
9d129ea34e
machined/import: allow running in per-user mode (#38728) 2025-11-04 17:27:43 +01:00
Quentin Deslandes
6971798864 core: use proper service type of TEST-07-PID.user-namespace-path.sh
TEST-07-PID.user-namespace-path.sh is flaky as Type=simple is used
(implicitly), explicitly use Type=exec instead to ensure the namespaces
are created before starting another service reusing the same namespaces.

Fixes #39546.
2025-11-04 15:54:06 +00:00
Luca Boccassi
22311a1291 test: stop piping post-test journalctl commands to /failed
We can't see what the actual issues are when tests fail at that point, eg:

https://github.com/systemd/systemd/actions/runs/19034752357/job/54356278052
2025-11-04 15:53:23 +00:00
Lennart Poettering
9a1d72fe99 update TODO 2025-11-04 14:12:39 +01:00
Lennart Poettering
4248b02c44 test: add test case 2025-11-04 14:12:39 +01:00
Lennart Poettering
78a738f4cf tar-util: squash high UIDs in user mode 2025-11-04 14:12:39 +01:00
Lennart Poettering
4ded7f7a43 tar-util: add support for acls 2025-11-04 14:12:39 +01:00
Lennart Poettering
e1e170feca tar-util: add support for file flags 2025-11-04 14:12:39 +01:00
Lennart Poettering
a54f4520f3 tar-util: properly deal with sparse files
The extractor already deals with sparse files properly (because
archive_read_data_into_fd() does).

Let's also make sure the archiver also does this, and attaches the
necessary sparse file metadata to each file.
2025-11-04 14:12:39 +01:00
Lennart Poettering
63bf3ca8b0 tar-util: recognize hardlinks when generating tarballs 2025-11-04 14:12:39 +01:00
Lennart Poettering
d4d94fceba tar-util: include xattrs in generated tarballs
We can already unpack them, let's pack them up to.
2025-11-04 14:12:39 +01:00
Lennart Poettering
f89c914968 acl-util: add new acl_set_perm() helper 2025-11-04 14:12:39 +01:00
Lennart Poettering
030f239a19 varlink-idl: add infra to test our enum parsers against varlink IDL enums
In many cases we want to expose enums for which we have the usual
xyz_to_string()/xyz_from_string() via Varlink as enums. Let's add some
infra to test the tables against each other, to automatically detect
when they deviate.

In order to implement this properly, let's export/introduce clean
json_underscorefy()/json_dashify(), for dealing with the fact that our
enums usually use dash separates ames, but Varlink doesn't allow that.

(This does not add the test cases for all enum types we expose right
now, but only adds the general infra).
2025-11-04 11:46:17 +00:00
Quentin Deslandes
79dd24cf14 core: Add UserNamespacePath=
This allows a service to reuse the user namespace created for an
existing service, similarly to NetworkNamespacePath=. The configuration
is the initial user namespace (e.g. ID mapping) is preserved.
2025-11-04 10:55:04 +01:00
34 changed files with 1172 additions and 69 deletions

25
TODO
View File

@ -63,6 +63,12 @@ Regularly:
Janitorial Clean-ups:
* machined: make remaining machine bus calls compatible with unpriv machined +
unpriv npsawn: GetAddresses(), GetSSHInfo(), GetOSRelease(), OpenPTY(),
OpenLogin(), OpenShell(), BindMount(), CopyFrom(), CopyTo(),
OpenRootDirectory(). Similar for images: GetHostname(), GetMachineID(),
GetMachineInfo(), GetOSRelease().
* rework mount.c and swap.c to follow proper state enumeration/deserialization
semantics, like we do for device.c now
@ -469,18 +475,10 @@ Features:
* allow dynamic modifications of ConcurrencyHardMax= and ConcurrencySoftMax=
via DBus (and with that also by daemon-reload)
* importd: introduce a per-user instance, that downloads into per-user DDI dirs
* sysupdated: similar
* sysupdated: introduce per-user version that can update per-user installed dDIs
* portabled: similar
* machined: implement a per-user instance, that manages per-user DDI dirs for
images. systemd-nspawn/systemd-vmspawn should probably register with both the
system and the user scoped machined instance. The former to get the machine
name registered as hostname, and the latter so that the image stuff is nicely
per-user managed.
* resolved: make resolved process DNR DHCP info
* maybe introduce an OSC sequence that signals when we ask for a password, so
@ -490,14 +488,8 @@ Features:
* start using STATX_SUBVOL in btrfs_is_subvol(). Also, make use of it
generically, so that image discovery recognizes bcachefs subvols too.
* "systemd-export tar" should reuse the libarchive export code from systemd-dissect
--archive.
* "systemd-import tar" should be moved to libarchive
* foreign uid:
- add support to export-fs, import-fs, import-tar, export-tar
- add tool for deleting foreign UID held container images
- add support to export-fs, import-fs
- systemd-dissect should learn mappings, too, when doing mtree and such
* format-table: introduce new cell type for strings with ansi sequences in
@ -720,7 +712,6 @@ Features:
from, tracing through overlayfs, DM, loopback block device.
* importd/importctl
- port tar handling to libarchive
- complete varlink interface
- download images into .v/ dirs

View File

@ -3408,6 +3408,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly b MemoryKSM = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s UserNamespacePath = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s NetworkNamespacePath = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s IPCNamespacePath = '...';
@ -4019,6 +4021,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
<!--property MemoryKSM is not documented!-->
<!--property UserNamespacePath is not documented!-->
<!--property NetworkNamespacePath is not documented!-->
<!--property IPCNamespacePath is not documented!-->
@ -4759,6 +4763,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
<variablelist class="dbus-property" generated="True" extra-ref="MemoryKSM"/>
<variablelist class="dbus-property" generated="True" extra-ref="UserNamespacePath"/>
<variablelist class="dbus-property" generated="True" extra-ref="NetworkNamespacePath"/>
<variablelist class="dbus-property" generated="True" extra-ref="IPCNamespacePath"/>
@ -5655,6 +5661,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly b MemoryKSM = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s UserNamespacePath = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s NetworkNamespacePath = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s IPCNamespacePath = '...';
@ -6286,6 +6294,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
<!--property MemoryKSM is not documented!-->
<!--property UserNamespacePath is not documented!-->
<!--property NetworkNamespacePath is not documented!-->
<!--property IPCNamespacePath is not documented!-->
@ -7006,6 +7016,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
<variablelist class="dbus-property" generated="True" extra-ref="MemoryKSM"/>
<variablelist class="dbus-property" generated="True" extra-ref="UserNamespacePath"/>
<variablelist class="dbus-property" generated="True" extra-ref="NetworkNamespacePath"/>
<variablelist class="dbus-property" generated="True" extra-ref="IPCNamespacePath"/>
@ -7726,6 +7738,8 @@ node /org/freedesktop/systemd1/unit/home_2emount {
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly b MemoryKSM = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s UserNamespacePath = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s NetworkNamespacePath = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s IPCNamespacePath = '...';
@ -8279,6 +8293,8 @@ node /org/freedesktop/systemd1/unit/home_2emount {
<!--property MemoryKSM is not documented!-->
<!--property UserNamespacePath is not documented!-->
<!--property NetworkNamespacePath is not documented!-->
<!--property IPCNamespacePath is not documented!-->
@ -8907,6 +8923,8 @@ node /org/freedesktop/systemd1/unit/home_2emount {
<variablelist class="dbus-property" generated="True" extra-ref="MemoryKSM"/>
<variablelist class="dbus-property" generated="True" extra-ref="UserNamespacePath"/>
<variablelist class="dbus-property" generated="True" extra-ref="NetworkNamespacePath"/>
<variablelist class="dbus-property" generated="True" extra-ref="IPCNamespacePath"/>
@ -9760,6 +9778,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly b MemoryKSM = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s UserNamespacePath = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s NetworkNamespacePath = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s IPCNamespacePath = '...';
@ -10295,6 +10315,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
<!--property MemoryKSM is not documented!-->
<!--property UserNamespacePath is not documented!-->
<!--property NetworkNamespacePath is not documented!-->
<!--property IPCNamespacePath is not documented!-->
@ -10905,6 +10927,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
<variablelist class="dbus-property" generated="True" extra-ref="MemoryKSM"/>
<variablelist class="dbus-property" generated="True" extra-ref="UserNamespacePath"/>
<variablelist class="dbus-property" generated="True" extra-ref="NetworkNamespacePath"/>
<variablelist class="dbus-property" generated="True" extra-ref="IPCNamespacePath"/>
@ -12519,7 +12543,8 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
<varname>LogsDirectoryQuotaUsage</varname>,
<varname>LogsDirectoryAccounting</varname>, and
<function>KillSubgroup()</function> were added in version 258.</para>
<para><varname>OOMKills</varname>, and
<para><varname>UserNamespacePath</varname>,
<varname>OOMKills</varname>, and
<varname>ManagedOOMKills</varname> were added in 259.</para>
</refsect2>
<refsect2>
@ -12587,6 +12612,7 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
<varname>LogsDirectoryAccounting</varname>, and
<function>KillSubgroup()</function> were added in version 258.</para>
<para><varname>OOMKills</varname>, and
<varname>UserNamespacePath</varname>, and
<varname>ManagedOOMKills</varname> were added in 259.</para>
</refsect2>
<refsect2>
@ -12648,7 +12674,8 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
<varname>LogsDirectoryQuotaUsage</varname>,
<varname>LogsDirectoryAccounting</varname>, and
<function>KillSubgroup()</function> were added in version 258.</para>
<para><varname>OOMKills</varname>, and
<para><varname>UserNamespacePath</varname>,
<varname>OOMKills</varname>, and
<varname>ManagedOOMKills</varname> were added in 259.</para>
</refsect2>
<refsect2>
@ -12708,7 +12735,8 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
<varname>LogsDirectoryQuotaUsage</varname>,
<varname>LogsDirectoryAccounting</varname>, and
<function>KillSubgroup()</function> were added in version 258.</para>
<para><varname>OOMKills</varname>, and
<para><varname>UserNamespacePath</varname>,
<varname>OOMKills</varname>, and
<varname>ManagedOOMKills</varname> were added in 259.</para>
</refsect2>
<refsect2>

View File

@ -2026,6 +2026,20 @@ BindReadOnlyPaths=/var/lib/systemd</programlisting>
<xi:include href="system-or-user-ns.xml" xpointer="singular"/></listitem>
</varlistentry>
<varlistentry>
<term><varname>UserNamespacePath=</varname></term>
<listitem><para>Takes an absolute file system path referring to a Linux user namespace
pseudo-file (i.e. a file like <filename>/proc/$PID/ns/user</filename> or a bind mount or symlink to
one). When set the invoked processes are added to the user namespace referenced by that path. The
path has to point to a valid namespace file at the moment the processes are forked off. If this
option is used <varname>PrivateUsers=</varname> has no effect.</para>
<para>This option is only available for system services.</para>
<xi:include href="version-info.xml" xpointer="v259"/></listitem>
</varlistentry>
<varlistentry>
<term><varname>NetworkNamespacePath=</varname></term>

View File

@ -1407,6 +1407,7 @@ const sd_bus_vtable bus_exec_vtable[] = {
SD_BUS_PROPERTY("BPFDelegatePrograms", "s", property_get_bpf_delegate_programs, offsetof(ExecContext, bpf_delegate_programs), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("BPFDelegateAttachments", "s", property_get_bpf_delegate_attachments, offsetof(ExecContext, bpf_delegate_attachments), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("MemoryKSM", "b", bus_property_get_tristate, offsetof(ExecContext, memory_ksm), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("UserNamespacePath", "s", NULL, offsetof(ExecContext, user_namespace_path), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("NetworkNamespacePath", "s", NULL, offsetof(ExecContext, network_namespace_path), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("IPCNamespacePath", "s", NULL, offsetof(ExecContext, ipc_namespace_path), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RootImagePolicy", "s", property_get_image_policy, offsetof(ExecContext, root_image_policy), SD_BUS_VTABLE_PROPERTY_CONST),
@ -2500,6 +2501,9 @@ int bus_exec_context_set_transient_property(
if (streq(name, "NetworkNamespacePath"))
return bus_set_transient_path(u, name, &c->network_namespace_path, message, flags, error);
if (streq(name, "UserNamespacePath"))
return bus_set_transient_path(u, name, &c->user_namespace_path, message, flags, error);
if (streq(name, "IPCNamespacePath"))
return bus_set_transient_path(u, name, &c->ipc_namespace_path, message, flags, error);

View File

@ -4159,6 +4159,7 @@ static int close_remaining_fds(
append_socket_pair(dont_close, &n_dont_close, runtime->ephemeral_storage_socket);
if (runtime->shared) {
append_socket_pair(dont_close, &n_dont_close, runtime->shared->userns_storage_socket);
append_socket_pair(dont_close, &n_dont_close, runtime->shared->netns_storage_socket);
append_socket_pair(dont_close, &n_dont_close, runtime->shared->ipcns_storage_socket);
}
@ -4464,6 +4465,7 @@ static bool exec_needs_cap_sys_admin(const ExecContext *context, const ExecParam
context->private_tmp != PRIVATE_TMP_NO ||
context->private_devices ||
context->private_network ||
context->user_namespace_path ||
context->network_namespace_path ||
context->private_ipc ||
context->ipc_namespace_path ||
@ -4768,6 +4770,7 @@ static void exec_shared_runtime_close(ExecSharedRuntime *shared) {
if (!shared)
return;
safe_close_pair(shared->userns_storage_socket);
safe_close_pair(shared->netns_storage_socket);
safe_close_pair(shared->ipcns_storage_socket);
}
@ -5322,6 +5325,14 @@ int exec_invoke(
}
}
if (context->user_namespace_path && runtime->shared && runtime->shared->userns_storage_socket[0] >= 0) {
r = open_shareable_ns_path(runtime->shared->userns_storage_socket, context->user_namespace_path, CLONE_NEWUSER);
if (r < 0) {
*exit_status = EXIT_NAMESPACE;
return log_error_errno(r, "Failed to open user namespace path %s: %m", context->user_namespace_path);
}
}
if (context->network_namespace_path && runtime->shared && runtime->shared->netns_storage_socket[0] >= 0) {
r = open_shareable_ns_path(runtime->shared->netns_storage_socket, context->network_namespace_path, CLONE_NEWNET);
if (r < 0) {
@ -5758,6 +5769,10 @@ int exec_invoke(
/* If we're unprivileged, set up the user namespace first to enable use of the other namespaces.
* Users with CAP_SYS_ADMIN can set up user namespaces last because they will be able to
* set up all of the other namespaces (i.e. network, mount, UTS) without a user namespace. */
if (context->user_namespace_path && runtime->shared->userns_storage_socket[0] >= 0)
return log_error_errno(SYNTHETIC_ERRNO(EPERM), "UserNamespacePath= is configured, but user namespace setup not permitted");
PrivateUsers pu = exec_context_get_effective_private_users(context, params);
if (pu == PRIVATE_USERS_NO)
pu = PRIVATE_USERS_SELF;
@ -5828,8 +5843,20 @@ int exec_invoke(
* restricted by rules pertaining to combining user namespaces with other namespaces (e.g. in the
* case of mount namespaces being less privileged when the mount point list is copied from a
* different user namespace). */
if (needs_sandboxing && context->user_namespace_path && runtime->shared && runtime->shared->userns_storage_socket[0] >= 0) {
if (!namespace_type_supported(NAMESPACE_USER))
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "UserNamespacePath= is not supported, refusing.");
if (needs_sandboxing && !userns_set_up) {
r = setup_shareable_ns(runtime->shared->userns_storage_socket, CLONE_NEWUSER);
if (ERRNO_IS_NEG_PRIVILEGE(r))
return log_notice_errno(r, "PrivateUsers= is configured, but user namespace setup not permitted, refusing.");
if (r < 0) {
*exit_status = EXIT_USER;
return log_error_errno(r, "Failed to set up user namespacing: %m");
}
log_debug("Set up existing user namespace");
} else if (needs_sandboxing && !userns_set_up) {
PrivateUsers pu = exec_context_get_effective_private_users(context, params);
r = setup_private_users(pu, saved_uid, saved_gid, uid, gid,

View File

@ -938,6 +938,12 @@ static int exec_runtime_serialize(const ExecRuntime *rt, FILE *f, FDSet *fds) {
if (r < 0)
return r;
if (rt->shared->userns_storage_socket[0] >= 0 && rt->shared->userns_storage_socket[1] >= 0) {
r = serialize_fd_many(f, fds, "exec-runtime-userns-storage-socket", rt->shared->userns_storage_socket, 2);
if (r < 0)
return r;
}
if (rt->shared->netns_storage_socket[0] >= 0 && rt->shared->netns_storage_socket[1] >= 0) {
r = serialize_fd_many(f, fds, "exec-runtime-netns-storage-socket", rt->shared->netns_storage_socket, 2);
if (r < 0)
@ -1013,6 +1019,12 @@ static int exec_runtime_deserialize(ExecRuntime *rt, FILE *f, FDSet *fds) {
r = free_and_strdup(&rt->shared->var_tmp_dir, val);
if (r < 0)
return r;
} else if ((val = startswith(l, "exec-runtime-userns-storage-socket="))) {
r = deserialize_fd_many(fds, val, 2, rt->shared->userns_storage_socket);
if (r < 0)
continue;
} else if ((val = startswith(l, "exec-runtime-netns-storage-socket="))) {
r = deserialize_fd_many(fds, val, 2, rt->shared->netns_storage_socket);
@ -2362,6 +2374,10 @@ static int exec_context_serialize(const ExecContext *c, FILE *f) {
if (r < 0)
return r;
r = serialize_item(f, "exec-context-user-namespace-path", c->user_namespace_path);
if (r < 0)
return r;
r = serialize_item(f, "exec-context-network-namespace-path", c->network_namespace_path);
if (r < 0)
return r;
@ -3493,6 +3509,10 @@ static int exec_context_deserialize(ExecContext *c, FILE *f) {
r = free_and_strdup(&c->network_namespace_path, val);
if (r < 0)
return r;
} else if ((val = startswith(l, "exec-context-user-namespace-path="))) {
r = free_and_strdup(&c->user_namespace_path, val);
if (r < 0)
return r;
} else if ((val = startswith(l, "exec-context-ipc-namespace-path="))) {
r = free_and_strdup(&c->ipc_namespace_path, val);
if (r < 0)

View File

@ -742,6 +742,7 @@ void exec_context_done(ExecContext *c) {
c->stdin_data = mfree(c->stdin_data);
c->stdin_data_size = 0;
c->user_namespace_path = mfree(c->user_namespace_path);
c->network_namespace_path = mfree(c->network_namespace_path);
c->ipc_namespace_path = mfree(c->ipc_namespace_path);
@ -1554,6 +1555,11 @@ void exec_context_dump(const ExecContext *c, FILE* f, const char *prefix) {
}
#endif
if (c->user_namespace_path)
fprintf(f,
"%sUserNamespacePath: %s\n",
prefix, c->user_namespace_path);
if (c->network_namespace_path)
fprintf(f,
"%sNetworkNamespacePath: %s\n",
@ -2286,6 +2292,7 @@ void exec_shared_runtime_done(ExecSharedRuntime *rt) {
rt->id = mfree(rt->id);
rt->tmp_dir = mfree(rt->tmp_dir);
rt->var_tmp_dir = mfree(rt->var_tmp_dir);
safe_close_pair(rt->userns_storage_socket);
safe_close_pair(rt->netns_storage_socket);
safe_close_pair(rt->ipcns_storage_socket);
}
@ -2333,6 +2340,7 @@ static int exec_shared_runtime_allocate(ExecSharedRuntime **ret, const char *id)
*n = (ExecSharedRuntime) {
.id = TAKE_PTR(id_copy),
.userns_storage_socket = EBADF_PAIR,
.netns_storage_socket = EBADF_PAIR,
.ipcns_storage_socket = EBADF_PAIR,
};
@ -2346,6 +2354,7 @@ static int exec_shared_runtime_add(
const char *id,
char **tmp_dir,
char **var_tmp_dir,
int userns_storage_socket[2],
int netns_storage_socket[2],
int ipcns_storage_socket[2],
ExecSharedRuntime **ret) {
@ -2370,6 +2379,11 @@ static int exec_shared_runtime_add(
rt->tmp_dir = TAKE_PTR(*tmp_dir);
rt->var_tmp_dir = TAKE_PTR(*var_tmp_dir);
if (userns_storage_socket) {
rt->userns_storage_socket[0] = TAKE_FD(userns_storage_socket[0]);
rt->userns_storage_socket[1] = TAKE_FD(userns_storage_socket[1]);
}
if (netns_storage_socket) {
rt->netns_storage_socket[0] = TAKE_FD(netns_storage_socket[0]);
rt->netns_storage_socket[1] = TAKE_FD(netns_storage_socket[1]);
@ -2396,7 +2410,7 @@ static int exec_shared_runtime_make(
ExecSharedRuntime **ret) {
_cleanup_(namespace_cleanup_tmpdirp) char *tmp_dir = NULL, *var_tmp_dir = NULL;
_cleanup_close_pair_ int netns_storage_socket[2] = EBADF_PAIR, ipcns_storage_socket[2] = EBADF_PAIR;
_cleanup_close_pair_ int userns_storage_socket[2] = EBADF_PAIR, netns_storage_socket[2] = EBADF_PAIR, ipcns_storage_socket[2] = EBADF_PAIR;
int r;
assert(m);
@ -2418,6 +2432,10 @@ static int exec_shared_runtime_make(
return r;
}
if (c->user_namespace_path)
if (socketpair(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, userns_storage_socket) < 0)
return -errno;
if (exec_needs_network_namespace(c))
if (socketpair(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, netns_storage_socket) < 0)
return -errno;
@ -2426,7 +2444,7 @@ static int exec_shared_runtime_make(
if (socketpair(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, ipcns_storage_socket) < 0)
return -errno;
r = exec_shared_runtime_add(m, id, &tmp_dir, &var_tmp_dir, netns_storage_socket, ipcns_storage_socket, ret);
r = exec_shared_runtime_add(m, id, &tmp_dir, &var_tmp_dir, userns_storage_socket, netns_storage_socket, ipcns_storage_socket, ret);
if (r < 0)
return r;
@ -2484,6 +2502,26 @@ int exec_shared_runtime_serialize(const Manager *m, FILE *f, FDSet *fds) {
if (rt->var_tmp_dir)
fprintf(f, " var-tmp-dir=%s", rt->var_tmp_dir);
if (rt->userns_storage_socket[0] >= 0) {
int copy;
copy = fdset_put_dup(fds, rt->userns_storage_socket[0]);
if (copy < 0)
return copy;
fprintf(f, " userns-socket-0=%i", copy);
}
if (rt->userns_storage_socket[1] >= 0) {
int copy;
copy = fdset_put_dup(fds, rt->userns_storage_socket[1]);
if (copy < 0)
return copy;
fprintf(f, " userns-socket-1=%i", copy);
}
if (rt->netns_storage_socket[0] >= 0) {
int copy;
@ -2608,7 +2646,7 @@ int exec_shared_runtime_deserialize_compat(Unit *u, const char *key, const char
int exec_shared_runtime_deserialize_one(Manager *m, const char *value, FDSet *fds) {
_cleanup_free_ char *tmp_dir = NULL, *var_tmp_dir = NULL;
char *id = NULL;
int r, netns_fdpair[] = {-1, -1}, ipcns_fdpair[] = {-1, -1};
int r, userns_fdpair[] = {-1, -1}, netns_fdpair[] = {-1, -1}, ipcns_fdpair[] = {-1, -1};
const char *p, *v = ASSERT_PTR(value);
size_t n;
@ -2643,6 +2681,36 @@ int exec_shared_runtime_deserialize_one(Manager *m, const char *value, FDSet *fd
p = v + n + 1;
}
v = startswith(p, "userns-socket-0=");
if (v) {
char *buf;
n = strcspn(v, " ");
buf = strndupa_safe(v, n);
userns_fdpair[0] = deserialize_fd(fds, buf);
if (userns_fdpair[0] < 0)
return userns_fdpair[0];
if (v[n] != ' ')
goto finalize;
p = v + n + 1;
}
v = startswith(p, "userns-socket-1=");
if (v) {
char *buf;
n = strcspn(v, " ");
buf = strndupa_safe(v, n);
userns_fdpair[1] = deserialize_fd(fds, buf);
if (userns_fdpair[1] < 0)
return userns_fdpair[1];
if (v[n] != ' ')
goto finalize;
p = v + n + 1;
}
v = startswith(p, "netns-socket-0=");
if (v) {
char *buf;
@ -2701,7 +2769,7 @@ int exec_shared_runtime_deserialize_one(Manager *m, const char *value, FDSet *fd
}
finalize:
r = exec_shared_runtime_add(m, id, &tmp_dir, &var_tmp_dir, netns_fdpair, ipcns_fdpair, NULL);
r = exec_shared_runtime_add(m, id, &tmp_dir, &var_tmp_dir, userns_fdpair, netns_fdpair, ipcns_fdpair, NULL);
if (r < 0)
return log_debug_errno(r, "Failed to add exec-runtime: %m");
return 0;

View File

@ -111,6 +111,9 @@ typedef struct ExecSharedRuntime {
/* Like netns_storage_socket, but the file descriptor is referring to the IPC namespace. */
int ipcns_storage_socket[2];
/* Like netns_storage_socket, but the file descriptor is referring to the user namespace. */
int userns_storage_socket[2];
} ExecSharedRuntime;
typedef struct ExecRuntime {
@ -353,6 +356,7 @@ typedef struct ExecContext {
bool address_families_allow_list:1;
Set *address_families;
char *user_namespace_path;
char *network_namespace_path;
char *ipc_namespace_path;

View File

@ -183,6 +183,7 @@ static int run(int argc, char *argv[]) {
_cleanup_(exec_command_done) ExecCommand command = {};
_cleanup_(exec_params_deep_clear) ExecParameters params = EXEC_PARAMETERS_INIT(/* flags= */ 0);
_cleanup_(exec_shared_runtime_done) ExecSharedRuntime shared = {
.userns_storage_socket = EBADF_PAIR,
.netns_storage_socket = EBADF_PAIR,
.ipcns_storage_socket = EBADF_PAIR,
};

View File

@ -35,6 +35,7 @@ static void exec_fuzz_one(FILE *f, FDSet *fdset) {
DynamicCreds dynamic_creds = {};
ExecCommand command = {};
ExecSharedRuntime shared = {
.userns_storage_socket = EBADF_PAIR,
.netns_storage_socket = EBADF_PAIR,
.ipcns_storage_socket = EBADF_PAIR,
};

View File

@ -132,6 +132,7 @@
{{type}}.ProtectKernelLogs, config_parse_bool, 0, offsetof({{type}}, exec_context.protect_kernel_logs)
{{type}}.ProtectClock, config_parse_bool, 0, offsetof({{type}}, exec_context.protect_clock)
{{type}}.ProtectControlGroups, config_parse_protect_control_groups, 0, offsetof({{type}}, exec_context.protect_control_groups)
{{type}}.UserNamespacePath, config_parse_unit_path_printf, 0, offsetof({{type}}, exec_context.user_namespace_path)
{{type}}.NetworkNamespacePath, config_parse_unit_path_printf, 0, offsetof({{type}}, exec_context.network_namespace_path)
{{type}}.IPCNamespacePath, config_parse_unit_path_printf, 0, offsetof({{type}}, exec_context.ipc_namespace_path)
{{type}}.LogNamespace, config_parse_log_namespace, 0, offsetof({{type}}, exec_context)

View File

@ -1580,6 +1580,15 @@ static int socket_address_listen_in_cgroup(
if (r < 0)
return log_unit_error_errno(UNIT(s), r, "Failed to acquire runtime: %m");
if (s->exec_context.user_namespace_path &&
s->exec_runtime &&
s->exec_runtime->shared &&
s->exec_runtime->shared->userns_storage_socket[0] >= 0) {
r = open_shareable_ns_path(s->exec_runtime->shared->userns_storage_socket, s->exec_context.user_namespace_path, CLONE_NEWUSER);
if (r < 0)
return log_unit_error_errno(UNIT(s), r, "Failed to open user namespace path %s: %m", s->exec_context.user_namespace_path);
}
if (s->exec_context.network_namespace_path &&
s->exec_runtime &&
s->exec_runtime->shared &&

View File

@ -885,6 +885,7 @@ int unit_exec_context_build_json(sd_json_variant **ret, const char *name, void *
JSON_BUILD_PAIR_TRISTATE_NON_NULL("MemoryKSM", c->memory_ksm),
SD_JSON_BUILD_PAIR_STRING("PrivatePIDs", private_pids_to_string(c->private_pids)),
SD_JSON_BUILD_PAIR_STRING("PrivateUsers", private_users_to_string(c->private_users)),
JSON_BUILD_PAIR_STRING_NON_EMPTY("UserNamespacePath", c->user_namespace_path),
SD_JSON_BUILD_PAIR_STRING("ProtectHostname", protect_hostname_to_string(c->protect_hostname)),
JSON_BUILD_PAIR_YES_NO("ProtectClock", c->protect_clock),
JSON_BUILD_PAIR_YES_NO("ProtectKernelTunables", c->protect_kernel_tunables),

View File

@ -35,7 +35,9 @@ int import_fork_tar_x(int tree_fd, int userns_fd, PidRef *ret_pid) {
if (r < 0)
return r;
TarFlags flags = mac_selinux_use() ? TAR_SELINUX : 0;
TarFlags flags =
(userns_fd >= 0 ? TAR_SQUASH_UIDS_ABOVE_64K : 0) |
(mac_selinux_use() ? TAR_SELINUX : 0);
_cleanup_close_pair_ int pipefd[2] = EBADF_PAIR;
if (pipe2(pipefd, O_CLOEXEC) < 0)
@ -100,7 +102,9 @@ int import_fork_tar_c(int tree_fd, int userns_fd, PidRef *ret_pid) {
if (r < 0)
return r;
TarFlags flags = mac_selinux_use() ? TAR_SELINUX : 0;
TarFlags flags =
(userns_fd >= 0 ? TAR_SQUASH_UIDS_ABOVE_64K : 0) |
(mac_selinux_use() ? TAR_SELINUX : 0);
_cleanup_close_pair_ int pipefd[2] = EBADF_PAIR;
if (pipe2(pipefd, O_CLOEXEC) < 0)

View File

@ -64,11 +64,11 @@ struct json_variant_foreach_state {
type cc = func(sd_json_variant_string(variant)); \
if (cc < 0) { \
/* Maybe this enum is recognizable if we replace "_" (i.e. Varlink syntax) with "-" (how we usually prefer it). */ \
_cleanup_free_ char *z = strreplace(sd_json_variant_string(variant), "_", "-"); \
_cleanup_free_ char *z = strdup(sd_json_variant_string(variant)); \
if (!z) \
return json_log_oom(variant, flags); \
\
cc = func(z); \
cc = func(json_dashify(z)); \
if (cc < 0) \
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "Value of JSON field '%s' not recognized: %s", strna(n), sd_json_variant_string(variant)); \
} \
@ -261,3 +261,6 @@ enum {
int json_variant_new_pidref(sd_json_variant **ret, PidRef *pidref);
int json_variant_new_devnum(sd_json_variant **ret, dev_t devnum);
int json_variant_new_fd_info(sd_json_variant **ret, int fd);
char *json_underscorify(char *p);
char *json_dashify(char *p);

View File

@ -3474,8 +3474,9 @@ _public_ int sd_json_parse_file(
return sd_json_parse_file_at(f, AT_FDCWD, path, flags, ret, reterr_line, reterr_column);
}
static char *underscorify(char *p) {
assert(p);
char *json_underscorify(char *p) {
if (!p)
return NULL;
/* Replaces "-", "+" by "_", to deal with the usual enum naming rules we have. */
@ -3485,6 +3486,18 @@ static char *underscorify(char *p) {
return p;
}
char *json_dashify(char *p) {
if (!p)
return NULL;
/* Replaces "-", "+" by "-", to (somewhat) undo what json_underscorify() does */
for (char *q = p; *q; q++)
*q = IN_SET(*q, '_', '-', '+') ? '-' : *q;
return p;
}
_public_ int sd_json_buildv(sd_json_variant **ret, va_list ap) {
JsonStack *stack = NULL;
size_t n_stack = 1;
@ -3538,7 +3551,7 @@ _public_ int sd_json_buildv(sd_json_variant **ret, va_list ap) {
goto finish;
}
p = underscorify(c);
p = json_underscorify(c);
}
r = sd_json_variant_new_string(&add, p);

View File

@ -8,6 +8,7 @@
#include "errno-util.h"
#include "hostname-setup.h"
#include "hostname-util.h"
#include "in-addr-util.h"
#include "local-addresses.h"
#include "nss-util.h"
#include "resolve-util.h"
@ -116,7 +117,7 @@ enum nss_status _nss_myhostname_gethostbyname4_r(
r_tuple->next = r_tuple_prev;
r_tuple->name = r_name;
r_tuple->family = AF_INET6;
memcpy(r_tuple->addr, LOCALADDRESS_IPV6, 16);
memcpy(r_tuple->addr, LOCALADDRESS_IPV6, FAMILY_ADDRESS_SIZE(AF_INET6));
r_tuple->scopeid = 0;
idx += ALIGN(sizeof(struct gaih_addrtuple));
@ -144,7 +145,7 @@ enum nss_status _nss_myhostname_gethostbyname4_r(
r_tuple->name = r_name;
r_tuple->family = a->family;
r_tuple->scopeid = a->family == AF_INET6 && in6_addr_is_link_local(&a->address.in6) ? a->ifindex : 0;
memcpy(r_tuple->addr, &a->address, 16);
memcpy(r_tuple->addr, &a->address, FAMILY_ADDRESS_SIZE(a->family));
idx += ALIGN(sizeof(struct gaih_addrtuple));
r_tuple_prev = r_tuple;
@ -263,7 +264,7 @@ static enum nss_status fill_in_hostent(
*(uint32_t*) r_addr = local_address_ipv4;
idx += ALIGN(alen);
} else if (socket_ipv6_is_enabled()) {
memcpy(r_addr, LOCALADDRESS_IPV6, 16);
memcpy(r_addr, LOCALADDRESS_IPV6, FAMILY_ADDRESS_SIZE(AF_INET6));
idx += ALIGN(alen);
}
@ -463,7 +464,7 @@ enum nss_status _nss_myhostname_gethostbyaddr2_r(
if (!socket_ipv6_is_enabled())
goto not_found;
if (memcmp(addr, LOCALADDRESS_IPV6, 16) == 0) {
if (memcmp(addr, LOCALADDRESS_IPV6, FAMILY_ADDRESS_SIZE(AF_INET6)) == 0) {
canonical = "localhost";
additional_from_hostname = true;
goto found;

View File

@ -23,6 +23,7 @@ DLSYM_PROTOTYPE(acl_delete_entry);
DLSYM_PROTOTYPE(acl_delete_perm);
DLSYM_PROTOTYPE(acl_dup);
DLSYM_PROTOTYPE(acl_entries);
DLSYM_PROTOTYPE(acl_extended_file);
DLSYM_PROTOTYPE(acl_free);
DLSYM_PROTOTYPE(acl_from_mode);
DLSYM_PROTOTYPE(acl_from_text);
@ -36,6 +37,7 @@ DLSYM_PROTOTYPE(acl_get_tag_type);
DLSYM_PROTOTYPE(acl_init);
DLSYM_PROTOTYPE(acl_set_fd);
DLSYM_PROTOTYPE(acl_set_file);
DLSYM_PROTOTYPE(acl_set_permset);
DLSYM_PROTOTYPE(acl_set_qualifier);
DLSYM_PROTOTYPE(acl_set_tag_type);
DLSYM_PROTOTYPE(acl_to_any_text);
@ -58,6 +60,7 @@ int dlopen_libacl(void) {
DLSYM_ARG(acl_delete_perm),
DLSYM_ARG(acl_dup),
DLSYM_ARG(acl_entries),
DLSYM_ARG(acl_extended_file),
DLSYM_ARG(acl_free),
DLSYM_ARG(acl_from_mode),
DLSYM_ARG(acl_from_text),
@ -71,6 +74,7 @@ int dlopen_libacl(void) {
DLSYM_ARG(acl_init),
DLSYM_ARG(acl_set_fd),
DLSYM_ARG(acl_set_file),
DLSYM_ARG(acl_set_permset),
DLSYM_ARG(acl_set_qualifier),
DLSYM_ARG(acl_set_tag_type),
DLSYM_ARG(acl_to_any_text));

View File

@ -20,6 +20,7 @@ extern DLSYM_PROTOTYPE(acl_delete_entry);
extern DLSYM_PROTOTYPE(acl_delete_perm);
extern DLSYM_PROTOTYPE(acl_dup);
extern DLSYM_PROTOTYPE(acl_entries);
extern DLSYM_PROTOTYPE(acl_extended_file);
extern DLSYM_PROTOTYPE(acl_free);
extern DLSYM_PROTOTYPE(acl_from_mode);
extern DLSYM_PROTOTYPE(acl_from_text);
@ -33,6 +34,7 @@ extern DLSYM_PROTOTYPE(acl_get_tag_type);
extern DLSYM_PROTOTYPE(acl_init);
extern DLSYM_PROTOTYPE(acl_set_fd);
extern DLSYM_PROTOTYPE(acl_set_file);
extern DLSYM_PROTOTYPE(acl_set_permset);
extern DLSYM_PROTOTYPE(acl_set_qualifier);
extern DLSYM_PROTOTYPE(acl_set_tag_type);
extern DLSYM_PROTOTYPE(acl_to_any_text);
@ -62,6 +64,10 @@ DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(char*, sym_acl_free, acl_free_charpp, NU
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(uid_t*, sym_acl_free, acl_free_uid_tpp, NULL);
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(gid_t*, sym_acl_free, acl_free_gid_tpp, NULL);
static inline int acl_set_perm(acl_permset_t ps, acl_perm_t p, bool b) {
return (b ? sym_acl_add_perm : sym_acl_delete_perm)(ps, p);
}
#else
#define ACL_READ 0x04

View File

@ -2419,6 +2419,7 @@ static const BusProperty execute_properties[] = {
{ "ProtectProc", bus_append_string },
{ "ProcSubset", bus_append_string },
{ "NetworkNamespacePath", bus_append_string },
{ "UserNamespacePath", bus_append_string },
{ "IPCNamespacePath", bus_append_string },
{ "LogNamespace", bus_append_string },
{ "RootImagePolicy", bus_append_string },

View File

@ -7,6 +7,10 @@
#if HAVE_LIBARCHIVE
static void *libarchive_dl = NULL;
DLSYM_PROTOTYPE(archive_entry_acl_add_entry) = NULL;
DLSYM_PROTOTYPE(archive_entry_acl_next) = NULL;
DLSYM_PROTOTYPE(archive_entry_acl_reset) = NULL;
DLSYM_PROTOTYPE(archive_entry_fflags) = NULL;
DLSYM_PROTOTYPE(archive_entry_filetype) = NULL;
DLSYM_PROTOTYPE(archive_entry_free) = NULL;
DLSYM_PROTOTYPE(archive_entry_gid) = NULL;
@ -26,8 +30,10 @@ DLSYM_PROTOTYPE(archive_entry_pathname) = NULL;
DLSYM_PROTOTYPE(archive_entry_rdevmajor) = NULL;
DLSYM_PROTOTYPE(archive_entry_rdevminor) = NULL;
DLSYM_PROTOTYPE(archive_entry_set_ctime) = NULL;
DLSYM_PROTOTYPE(archive_entry_set_fflags) = NULL;
DLSYM_PROTOTYPE(archive_entry_set_filetype) = NULL;
DLSYM_PROTOTYPE(archive_entry_set_gid) = NULL;
DLSYM_PROTOTYPE(archive_entry_set_hardlink) = NULL;
DLSYM_PROTOTYPE(archive_entry_set_mtime) = NULL;
DLSYM_PROTOTYPE(archive_entry_set_pathname) = NULL;
DLSYM_PROTOTYPE(archive_entry_set_perm) = NULL;
@ -36,11 +42,13 @@ DLSYM_PROTOTYPE(archive_entry_set_rdevminor) = NULL;
DLSYM_PROTOTYPE(archive_entry_set_size) = NULL;
DLSYM_PROTOTYPE(archive_entry_set_symlink) = NULL;
DLSYM_PROTOTYPE(archive_entry_set_uid) = NULL;
DLSYM_PROTOTYPE(archive_entry_sparse_add_entry) = NULL;
DLSYM_PROTOTYPE(archive_entry_symlink) = NULL;
DLSYM_PROTOTYPE(archive_entry_uid) = NULL;
#if HAVE_LIBARCHIVE_UID_IS_SET
DLSYM_PROTOTYPE(archive_entry_uid_is_set) = NULL;
#endif
DLSYM_PROTOTYPE(archive_entry_xattr_add_entry) = NULL;
DLSYM_PROTOTYPE(archive_entry_xattr_next) = NULL;
DLSYM_PROTOTYPE(archive_entry_xattr_reset) = NULL;
DLSYM_PROTOTYPE(archive_error_string) = NULL;
@ -59,7 +67,7 @@ DLSYM_PROTOTYPE(archive_write_new) = NULL;
DLSYM_PROTOTYPE(archive_write_open_FILE) = NULL;
DLSYM_PROTOTYPE(archive_write_open_fd) = NULL;
DLSYM_PROTOTYPE(archive_write_set_format_filter_by_ext) = NULL;
DLSYM_PROTOTYPE(archive_write_set_format_gnutar) = NULL;
DLSYM_PROTOTYPE(archive_write_set_format_pax) = NULL;
int dlopen_libarchive(void) {
ELF_NOTE_DLOPEN("archive",
@ -71,6 +79,10 @@ int dlopen_libarchive(void) {
&libarchive_dl,
"libarchive.so.13",
LOG_DEBUG,
DLSYM_ARG(archive_entry_acl_add_entry),
DLSYM_ARG(archive_entry_acl_next),
DLSYM_ARG(archive_entry_acl_reset),
DLSYM_ARG(archive_entry_fflags),
DLSYM_ARG(archive_entry_filetype),
DLSYM_ARG(archive_entry_free),
DLSYM_ARG(archive_entry_gid),
@ -90,8 +102,10 @@ int dlopen_libarchive(void) {
DLSYM_ARG(archive_entry_rdevmajor),
DLSYM_ARG(archive_entry_rdevminor),
DLSYM_ARG(archive_entry_set_ctime),
DLSYM_ARG(archive_entry_set_fflags),
DLSYM_ARG(archive_entry_set_filetype),
DLSYM_ARG(archive_entry_set_gid),
DLSYM_ARG(archive_entry_set_hardlink),
DLSYM_ARG(archive_entry_set_mtime),
DLSYM_ARG(archive_entry_set_pathname),
DLSYM_ARG(archive_entry_set_perm),
@ -100,11 +114,13 @@ int dlopen_libarchive(void) {
DLSYM_ARG(archive_entry_set_size),
DLSYM_ARG(archive_entry_set_symlink),
DLSYM_ARG(archive_entry_set_uid),
DLSYM_ARG(archive_entry_sparse_add_entry),
DLSYM_ARG(archive_entry_symlink),
DLSYM_ARG(archive_entry_uid),
#if HAVE_LIBARCHIVE_UID_IS_SET
DLSYM_ARG(archive_entry_uid_is_set),
#endif
DLSYM_ARG(archive_entry_xattr_add_entry),
DLSYM_ARG(archive_entry_xattr_next),
DLSYM_ARG(archive_entry_xattr_reset),
DLSYM_ARG(archive_error_string),
@ -123,8 +139,7 @@ int dlopen_libarchive(void) {
DLSYM_ARG(archive_write_open_FILE),
DLSYM_ARG(archive_write_open_fd),
DLSYM_ARG(archive_write_set_format_filter_by_ext),
DLSYM_ARG(archive_write_set_format_gnutar)
);
DLSYM_ARG(archive_write_set_format_pax));
}
/* libarchive uses its own file type macros. They happen to be defined the same way as the Linux ones, and

View File

@ -9,6 +9,10 @@
#include "dlfcn-util.h"
extern DLSYM_PROTOTYPE(archive_entry_acl_add_entry);
extern DLSYM_PROTOTYPE(archive_entry_acl_next);
extern DLSYM_PROTOTYPE(archive_entry_acl_reset);
extern DLSYM_PROTOTYPE(archive_entry_fflags);
extern DLSYM_PROTOTYPE(archive_entry_filetype);
extern DLSYM_PROTOTYPE(archive_entry_free);
extern DLSYM_PROTOTYPE(archive_entry_gid);
@ -22,8 +26,10 @@ extern DLSYM_PROTOTYPE(archive_entry_pathname);
extern DLSYM_PROTOTYPE(archive_entry_rdevmajor);
extern DLSYM_PROTOTYPE(archive_entry_rdevminor);
extern DLSYM_PROTOTYPE(archive_entry_set_ctime);
extern DLSYM_PROTOTYPE(archive_entry_set_fflags);
extern DLSYM_PROTOTYPE(archive_entry_set_filetype);
extern DLSYM_PROTOTYPE(archive_entry_set_gid);
extern DLSYM_PROTOTYPE(archive_entry_set_hardlink);
extern DLSYM_PROTOTYPE(archive_entry_set_mtime);
extern DLSYM_PROTOTYPE(archive_entry_set_pathname);
extern DLSYM_PROTOTYPE(archive_entry_set_perm);
@ -32,8 +38,10 @@ extern DLSYM_PROTOTYPE(archive_entry_set_rdevminor);
extern DLSYM_PROTOTYPE(archive_entry_set_size);
extern DLSYM_PROTOTYPE(archive_entry_set_symlink);
extern DLSYM_PROTOTYPE(archive_entry_set_uid);
extern DLSYM_PROTOTYPE(archive_entry_sparse_add_entry);
extern DLSYM_PROTOTYPE(archive_entry_symlink);
extern DLSYM_PROTOTYPE(archive_entry_uid);
extern DLSYM_PROTOTYPE(archive_entry_xattr_add_entry);
extern DLSYM_PROTOTYPE(archive_entry_xattr_next);
extern DLSYM_PROTOTYPE(archive_entry_xattr_reset);
extern DLSYM_PROTOTYPE(archive_error_string);
@ -52,7 +60,7 @@ extern DLSYM_PROTOTYPE(archive_write_new);
extern DLSYM_PROTOTYPE(archive_write_open_FILE);
extern DLSYM_PROTOTYPE(archive_write_open_fd);
extern DLSYM_PROTOTYPE(archive_write_set_format_filter_by_ext);
extern DLSYM_PROTOTYPE(archive_write_set_format_gnutar);
extern DLSYM_PROTOTYPE(archive_write_set_format_pax);
#if HAVE_LIBARCHIVE_UID_IS_SET
extern DLSYM_PROTOTYPE(archive_entry_gid_is_set);

View File

@ -5,16 +5,25 @@
#include "tar-util.h"
#if HAVE_LIBARCHIVE
#include <sys/mount.h>
#include <sys/sysmacros.h>
#include "acl-util.h"
#include "alloc-util.h"
#include "chase.h"
#include "chattr-util.h"
#include "fd-util.h"
#include "fs-util.h"
#include "hexdecoct.h"
#include "iovec-util.h"
#include "libarchive-util.h"
#include "mountpoint-util.h"
#include "nsresource.h"
#include "nulstr-util.h"
#include "path-util.h"
#include "recurse-dir.h"
#include "rm-rf.h"
#include "sha256.h"
#include "stat-util.h"
#include "string-util.h"
#include "tmpfile-util.h"
@ -23,6 +32,15 @@
#define DEPTH_MAX 128U
/* We are a bit conservative with the flags we save/restore in tar files */
#define CHATTR_TAR_FL \
(FS_NOATIME_FL | \
FS_NOCOW_FL | \
FS_PROJINHERIT_FL | \
FS_NODUMP_FL | \
FS_SYNC_FL | \
FS_DIRSYNC_FL)
typedef struct XAttr {
char *name;
struct iovec data;
@ -38,8 +56,10 @@ typedef struct OpenInode {
struct timespec mtime;
uid_t uid;
gid_t gid;
unsigned fflags;
XAttr *xattr;
size_t n_xattr;
acl_t acl_access, acl_default;
} OpenInode;
static void xattr_done(XAttr *xa) {
@ -68,6 +88,10 @@ static void open_inode_done(OpenInode *of) {
of->path = mfree(of->path);
}
xattr_done_many(of->xattr, of->n_xattr);
if (of->acl_access)
sym_acl_free(of->acl_access);
if (of->acl_default)
sym_acl_free(of->acl_default);
}
static void open_inode_done_many(OpenInode *array, size_t n) {
@ -79,6 +103,29 @@ static void open_inode_done_many(OpenInode *array, size_t n) {
free(array);
}
static int open_inode_apply_acl(OpenInode *of) {
int r = 0;
assert(of);
assert(of->fd >= 0);
if (!inode_type_can_acl(of->filetype))
return 0;
if (of->acl_access) {
if (sym_acl_set_fd(of->fd, of->acl_access) < 0)
RET_GATHER(r, log_error_errno(errno, "Failed to adjust ACLs of '%s': %m", of->path));
}
if (of->filetype == S_IFDIR && of->acl_default) {
/* There's no API to set default ACLs by fd, hence go by /proc/self/fd/ path */
if (sym_acl_set_file(FORMAT_PROC_FD_PATH(of->fd), ACL_TYPE_DEFAULT, of->acl_default) < 0)
RET_GATHER(r, log_error_errno(errno, "Failed to adjust default ACLs of '%s': %m", of->path));
}
return r;
}
static int open_inode_finalize(OpenInode *of) {
int r = 0;
@ -99,6 +146,24 @@ static int open_inode_finalize(OpenInode *of) {
RET_GATHER(r, log_error_errno(k, "Failed to adjust ownership/mode of '%s': %m", of->path));
}
k = open_inode_apply_acl(of);
if (k < 0)
RET_GATHER(r, log_error_errno(k, "Failed to adjust ACL of '%s': %m", of->path));
if ((of->fflags & ~CHATTR_EARLY_FL) != 0 && inode_type_can_chattr(of->filetype)) {
k = chattr_full(of->fd,
/* path= */ NULL,
/* value= */ of->fflags,
/* mask= */ of->fflags & ~CHATTR_EARLY_FL,
/* ret_previous= */ NULL,
/* ret_final= */ NULL,
CHATTR_FALLBACK_BITWISE);
if (ERRNO_IS_NEG_NOT_SUPPORTED(k))
log_warning_errno(k, "Failed to apply chattr of '%s', ignoring: %m", of->path);
else if (k < 0)
RET_GATHER(r, log_error_errno(k, "Failed to adjust chattr of '%s': %m", of->path));
}
/* We also adjust the mtime only after leaving a dir, since it might otherwise change again
* because we make modifications inside it */
if (of->mtime.tv_nsec != UTIME_OMIT) {
@ -149,7 +214,8 @@ static int archive_unpack_regular(
struct archive_entry *entry,
int parent_fd,
const char *filename,
const char *path) {
const char *path,
unsigned fflags) {
int r;
@ -164,6 +230,22 @@ static int archive_unpack_regular(
if (fd < 0)
return log_error_errno(fd, "Failed to create regular file '%s': %m", path);
if ((fflags & CHATTR_EARLY_FL) != 0) {
r = chattr_full(fd,
/* path= */ NULL,
/* value= */ fflags,
/* mask= */ fflags & CHATTR_EARLY_FL,
/* ret_previous= */ NULL,
/* ret_final= */ NULL,
CHATTR_FALLBACK_BITWISE);
if (ERRNO_IS_NEG_NOT_SUPPORTED(r))
log_warning_errno(r, "Failed to apply chattr of '%s', ignoring: %m", path);
else if (r < 0) {
log_error_errno(r, "Failed to adjust chattr of '%s': %m", path);
goto fail;
}
}
r = sym_archive_read_data_into_fd(a, fd);
if (r != ARCHIVE_OK) {
r = log_error_errno(
@ -204,7 +286,10 @@ static int archive_unpack_directory(
struct archive_entry *entry,
int parent_fd,
const char *filename,
const char *path) {
const char *path,
unsigned fflags) {
int r;
assert(a);
assert(entry);
@ -220,6 +305,20 @@ static int archive_unpack_directory(
if (fd < 0)
return log_error_errno(fd, "Failed to create directory '%s': %m", path);
if ((fflags & CHATTR_EARLY_FL) != 0) {
r = chattr_full(fd,
/* path= */ NULL,
/* value= */ fflags,
/* mask= */ fflags & CHATTR_EARLY_FL,
/* ret_previous= */ NULL,
/* ret_final= */ NULL,
CHATTR_FALLBACK_BITWISE);
if (ERRNO_IS_NEG_NOT_SUPPORTED(r))
log_warning_errno(r, "Failed to apply chattr of '%s', ignoring: %m", path);
else if (r < 0)
return log_error_errno(r, "Failed to adjust chattr of '%s': %m", path);
}
return TAKE_FD(fd);
}
@ -321,6 +420,145 @@ static int archive_entry_pathname_safe(struct archive_entry *entry, const char *
return 0;
}
static int archive_entry_read_acl(
struct archive_entry *entry,
acl_type_t ntype,
acl_t *acl,
TarFlags flags) {
int r;
assert(entry);
assert(acl);
int type;
if (ntype == ACL_TYPE_ACCESS)
type = ARCHIVE_ENTRY_ACL_TYPE_ACCESS;
else if (ntype == ACL_TYPE_DEFAULT)
type = ARCHIVE_ENTRY_ACL_TYPE_DEFAULT;
else
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Unexpected ACL type");
int c = sym_archive_entry_acl_reset(entry, type);
if (c == 0)
return 0;
assert(c > 0);
r = dlopen_libacl();
if (r < 0) {
log_debug_errno(r, "Not restoring ACL data on inode as libacl is not available: %m");
return 0;
}
_cleanup_(acl_freep) acl_t a = NULL;
a = sym_acl_init(c);
if (!a)
return log_oom();
for (;;) {
int rtype, permset, tag, qual;
const char *name;
r = sym_archive_entry_acl_next(
entry,
type,
&rtype,
&permset,
&tag,
&qual,
&name);
if (r == ARCHIVE_EOF)
break;
if (r != ARCHIVE_OK)
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Unexpected error while iterating through ACLs.");
assert(rtype == type);
acl_entry_t e;
if (sym_acl_create_entry(&a, &e) < 0)
return log_error_errno(errno, "Failed to create ACL entry: %m");
static const struct {
int libarchive;
acl_tag_t libacl;
} tag_map[] = {
{ ARCHIVE_ENTRY_ACL_USER, ACL_USER },
{ ARCHIVE_ENTRY_ACL_GROUP, ACL_GROUP },
{ ARCHIVE_ENTRY_ACL_USER_OBJ, ACL_USER_OBJ },
{ ARCHIVE_ENTRY_ACL_GROUP_OBJ, ACL_GROUP_OBJ },
{ ARCHIVE_ENTRY_ACL_MASK, ACL_MASK },
{ ARCHIVE_ENTRY_ACL_OTHER, ACL_OTHER },
};
acl_tag_t ntag = ACL_UNDEFINED_TAG;
FOREACH_ELEMENT(t, tag_map)
if (t->libarchive == tag) {
ntag = t->libacl;
break;
}
if (ntag == ACL_UNDEFINED_TAG)
continue;
if (sym_acl_set_tag_type(e, ntag) < 0)
return log_error_errno(errno, "Failed to set ACL entry tag: %m");
if (IN_SET(ntag, ACL_USER, ACL_GROUP)) {
id_t id = qual;
/* Suppress ACL entries for invalid UIDs/GIDS */
if (!uid_is_valid(id))
continue;
/* Suppress ACL entries for UIDs/GIDs to squash */
if (FLAGS_SET(flags, TAR_SQUASH_UIDS_ABOVE_64K) && id >= NSRESOURCE_UIDS_64K)
continue;
if (sym_acl_set_qualifier(e, &id) < 0)
return log_error_errno(errno, "Failed to set ACL entry qualifier: %m");
}
acl_permset_t p;
if (sym_acl_get_permset(e, &p) < 0)
return log_error_errno(errno, "Failed to get ACL entry permission set: %m");
r = acl_set_perm(p, ACL_READ, permset & ARCHIVE_ENTRY_ACL_READ);
if (r < 0)
return log_error_errno(r, "Failed to set ACL entry read bit: %m");
r = acl_set_perm(p, ACL_WRITE, permset & ARCHIVE_ENTRY_ACL_WRITE);
if (r < 0)
return log_error_errno(r, "Failed to set ACL entry write bit: %m");
r = acl_set_perm(p, ACL_EXECUTE, permset & ARCHIVE_ENTRY_ACL_EXECUTE);
if (r < 0)
return log_error_errno(r, "Failed to set ACL entry excute bit: %m");
if (sym_acl_set_permset(e, p) < 0)
return log_error_errno(errno, "Failed to set ACL entry permission set: %m");
}
if (*acl)
sym_acl_free(*acl);
*acl = TAKE_PTR(a);
return 0;
}
static uid_t maybe_squash_uid(uid_t uid, TarFlags flags) {
if (FLAGS_SET(flags, TAR_SQUASH_UIDS_ABOVE_64K) &&
uid_is_valid(uid) &&
uid >= NSRESOURCE_UIDS_64K)
return UID_NOBODY;
return uid;
}
static uid_t maybe_squash_gid(uid_t gid, TarFlags flags) {
if (FLAGS_SET(flags, TAR_SQUASH_UIDS_ABOVE_64K) &&
gid_is_valid(gid) &&
gid >= NSRESOURCE_UIDS_64K)
return GID_NOBODY;
return gid;
}
static int archive_entry_read_stat(
struct archive_entry *entry,
mode_t *filetype,
@ -328,10 +566,15 @@ static int archive_entry_read_stat(
struct timespec *mtime,
uid_t *uid,
gid_t *gid,
unsigned *fflags,
acl_t *acl_access,
acl_t *acl_default,
XAttr **xa,
size_t *n_xa,
TarFlags flags) {
int r;
assert(entry);
/* Fills in all fields that are present in the archive entry. Doesn't change the fields if the entry
@ -349,24 +592,40 @@ static int archive_entry_read_stat(
sym_archive_entry_mtime_nsec(entry),
};
if (uid && sym_archive_entry_uid_is_set(entry))
*uid = sym_archive_entry_uid(entry);
*uid = maybe_squash_uid(sym_archive_entry_uid(entry), flags);
if (gid && sym_archive_entry_gid_is_set(entry))
*gid = sym_archive_entry_gid(entry);
*gid = maybe_squash_gid(sym_archive_entry_gid(entry), flags);
if (fflags) {
unsigned long fs = 0, fc = 0;
sym_archive_entry_fflags(entry, &fs, &fc);
*fflags = (fs & ~fc) & CHATTR_TAR_FL;
}
(void) sym_archive_entry_xattr_reset(entry);
for (;;) {
const char *name = NULL;
struct iovec data;
(void) sym_archive_entry_xattr_next(entry, &name, (const void**) &data.iov_base, &data.iov_len);
if (!name)
r = sym_archive_entry_xattr_next(entry, &name, (const void**) &data.iov_base, &data.iov_len);
if (r != ARCHIVE_OK)
break;
assert(name);
if (xattr_is_acl(name))
continue;
if (!FLAGS_SET(flags, TAR_SELINUX) && xattr_is_selinux(name))
continue;
bool duplicate = false;
FOREACH_ARRAY(i, *xa, *n_xa)
if (streq(i->name, name)) {
duplicate = true;
break;
}
if (duplicate)
continue;
_cleanup_free_ char *n = strdup(name);
if (!n)
return log_oom();
@ -384,6 +643,18 @@ static int archive_entry_read_stat(
};
}
if (acl_access) {
r = archive_entry_read_acl(entry, ACL_TYPE_ACCESS, acl_access, flags);
if (r < 0)
return r;
}
if (acl_default) {
r = archive_entry_read_acl(entry, ACL_TYPE_DEFAULT, acl_default, flags);
if (r < 0)
return r;
}
return 0;
}
@ -458,6 +729,9 @@ int tar_x(int input_fd, int tree_fd, TarFlags flags) {
&open_inodes[0].mtime,
&open_inodes[0].uid,
&open_inodes[0].gid,
&open_inodes[0].fflags,
&open_inodes[0].acl_access,
&open_inodes[0].acl_default,
&open_inodes[0].xattr,
&open_inodes[0].n_xattr,
flags);
@ -528,6 +802,8 @@ int tar_x(int input_fd, int tree_fd, TarFlags flags) {
uid_t uid = UID_INVALID;
gid_t gid = GID_INVALID;
struct timespec mtime = { .tv_nsec = UTIME_OMIT };
unsigned fflags = 0;
_cleanup_(acl_freep) acl_t acl_access = NULL, acl_default = NULL;
XAttr *xa = NULL;
size_t n_xa = 0;
CLEANUP_ARRAY(xa, n_xa, xattr_done_many);
@ -602,6 +878,9 @@ int tar_x(int input_fd, int tree_fd, TarFlags flags) {
&mtime,
&uid,
&gid,
&fflags,
&acl_access,
&acl_default,
&xa,
&n_xa,
flags);
@ -611,11 +890,11 @@ int tar_x(int input_fd, int tree_fd, TarFlags flags) {
switch (filetype) {
case S_IFREG:
fd = archive_unpack_regular(a, entry, parent_fd, e, j);
fd = archive_unpack_regular(a, entry, parent_fd, e, j, fflags);
break;
case S_IFDIR:
fd = archive_unpack_directory(a, entry, parent_fd, e, j);
fd = archive_unpack_directory(a, entry, parent_fd, e, j, fflags);
break;
case S_IFLNK:
@ -660,6 +939,9 @@ int tar_x(int input_fd, int tree_fd, TarFlags flags) {
.mtime = mtime,
.uid = uid,
.gid = gid,
.fflags = fflags,
.acl_access = TAKE_PTR(acl_access),
.acl_default = TAKE_PTR(acl_default),
.xattr = TAKE_PTR(xa),
.n_xattr = n_xa,
};
@ -675,6 +957,258 @@ int tar_x(int input_fd, int tree_fd, TarFlags flags) {
return 0;
}
static int make_tmpfs(void) {
/* Creates a tmpfs superblock to store our hardlink db in. We can do this if we run in our own
* userns, or if we are privileged. This is preferable, since it means the db is cleaned up
* automatically once we are done. Moreover, since this is a new superblock owned by us, we do not
* need to set up any uid mapping shenanigans */
_cleanup_close_ int superblock_fd = fsopen("tmpfs", FSOPEN_CLOEXEC);
if (superblock_fd < 0)
return log_debug_errno(errno, "Failed to allocate tmpfs superblock: %m");
(void) fsconfig(superblock_fd, FSCONFIG_SET_STRING, "source", "hardlink", /* aux= */ 0);
(void) fsconfig(superblock_fd, FSCONFIG_SET_STRING, "mode", "0700", /* aux= */ 0);
if (fsconfig(superblock_fd, FSCONFIG_CMD_CREATE, /* key= */ NULL, /* value= */ NULL, /* aux= */ 0) < 0)
return log_debug_errno(errno, "Failed to finalize superblock: %m");
_cleanup_close_ int mount_fd = fsmount(superblock_fd, FSMOUNT_CLOEXEC, MS_NODEV|MS_NOEXEC|MS_NOSUID);
if (mount_fd < 0)
return log_debug_errno(errno, "Failed to turn tmpfs superblock into mount: %m");
return TAKE_FD(mount_fd);
}
struct make_archive_data {
struct archive *archive;
TarFlags flags;
int hardlink_db_fd;
char *hardlink_db_path;
};
static int hardlink_lookup(
struct make_archive_data *d,
int inode_fd,
const struct statx *sx,
const char *path,
char **ret) {
_cleanup_free_ struct file_handle *handle = NULL;
_cleanup_free_ char *m = NULL, *n = NULL;
int r;
assert(d);
assert(inode_fd >= 0);
assert(sx);
/* If we know the hardlink count, and it's 1, then don't bother */
if (FLAGS_SET(sx->stx_mask, STATX_NLINK) && sx->stx_nlink == 1)
goto bypass;
/* If this is a directory, then don't bother */
if (FLAGS_SET(sx->stx_mask, STATX_TYPE) && !inode_type_can_hardlink(sx->stx_mode))
goto bypass;
int mnt_id;
r = name_to_handle_at_try_fid(inode_fd, /* path= */ NULL, &handle, &mnt_id, /* flags= */ AT_EMPTY_PATH);
if (r < 0)
return log_error_errno(r, "Failed to get file handle of file: %m");
m = hexmem(SHA256_DIRECT(handle->f_handle, handle->handle_bytes), SHA256_DIGEST_SIZE);
if (!m)
return log_oom();
if (asprintf(&n, "%i:%i:%s", mnt_id, handle->handle_type, m) < 0)
return log_oom();
if (d->hardlink_db_fd < 0) {
assert(!d->hardlink_db_path);
/* We first try to create our own superblock, which works if we are in a userns, and which
* doesn't require explicit clean-up */
d->hardlink_db_fd = make_tmpfs();
if (d->hardlink_db_fd < 0) {
log_debug_errno(d->hardlink_db_fd, "Failed to allocate tmpfs superblock for hardlink db, falling back to temporary directory: %m");
const char *vt;
r = var_tmp_dir(&vt);
if (r < 0)
return log_error_errno(r, "Failed to determine /var/tmp/ directory: %m");
_cleanup_free_ char *j = path_join(vt, "make-tar-XXXXXX");
if (!j)
return log_oom();
d->hardlink_db_fd = mkdtemp_open(j, /* flags= */ 0, &d->hardlink_db_path);
if (d->hardlink_db_fd < 0)
return log_error_errno(d->hardlink_db_fd, "Failed to make hardlink database directory: %m");
}
} else {
_cleanup_free_ char *p = NULL;
r = readlinkat_malloc(d->hardlink_db_fd, n, &p);
if (r >= 0) {
/* Found previous hit! */
log_debug("hardlinkdb: found %s → %s", n, p);
*ret = TAKE_PTR(p);
return 1;
}
if (r != -ENOENT)
return log_error_errno(r, "Failed to read symlink '%s': %m", n);
}
/* Store information about this inode */
if (symlinkat(path, d->hardlink_db_fd, n) < 0)
return log_error_errno(errno, "Failed to create symlink '%s' → '%s': %m", n, path);
log_debug("hardlinkdb: created %s → %s", n, path);
bypass:
*ret = NULL;
return 0;
}
static int archive_generate_sparse(struct archive_entry *entry, int fd) {
assert(entry);
assert(fd);
off_t c = 0;
for (;;) {
/* Look for the next hole */
off_t h = lseek(fd, c, SEEK_HOLE);
if (h < 0) {
if (errno != ENXIO)
return log_error_errno(errno, "Failed to issue SEEK_HOLE: %m");
/* If errno == ENXIO, that means we've reached the final data of the file and
* that data isn't followed by anything more */
/* Figure out where the end of the file is */
off_t e = lseek(fd, 0, SEEK_END);
if (e < 0)
return log_error_errno(errno, "Failed to issue SEEK_END: %m");
/* Generate sparse entry for final block */
if (e > c && c != 0) {
log_debug("final sparse block %" PRIu64 "…%" PRIu64, (uint64_t) c, (uint64_t) e);
sym_archive_entry_sparse_add_entry(entry, c, e - c);
}
break;
}
if (h > c) {
log_debug("inner sparse block %" PRIu64 "…%" PRIu64 " (%" PRIu64 ")", (uint64_t) c, (uint64_t) h, (uint64_t) h - (uint64_t) c);
sym_archive_entry_sparse_add_entry(entry, c, h - c);
}
/* Now look for the next data after the hole */
c = lseek(fd, h, SEEK_DATA);
if (c < 0) {
if (errno != ENXIO)
return log_error_errno(errno, "Failed to issue SEEK_DATA: %m");
/* No data anymore */
break;
}
}
if (lseek(fd, 0, SEEK_SET) < 0)
return log_error_errno(errno, "Failed to reset seek offset: %m");
return 0;
}
static int archive_write_acl(
struct archive_entry *entry,
acl_type_t ntype,
acl_t acl,
TarFlags flags) {
int r;
assert(entry);
assert(acl);
int type;
if (ntype == ACL_TYPE_ACCESS)
type = ARCHIVE_ENTRY_ACL_TYPE_ACCESS;
else if (ntype == ACL_TYPE_DEFAULT)
type = ARCHIVE_ENTRY_ACL_TYPE_DEFAULT;
else
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Unexpected ACL type");
acl_entry_t e;
r = sym_acl_get_entry(acl, ACL_FIRST_ENTRY, &e);
for (;;) {
if (r < 0)
return log_error_errno(errno, "Failed to get ACL entry: %m");
if (r == 0)
break;
acl_tag_t ntag;
if (sym_acl_get_tag_type(e, &ntag) < 0)
return log_error_errno(errno, "Failed to get ACL entry tag: %m");
static const int tag_map[] = {
[ACL_USER] = ARCHIVE_ENTRY_ACL_USER,
[ACL_GROUP] = ARCHIVE_ENTRY_ACL_GROUP,
[ACL_USER_OBJ] = ARCHIVE_ENTRY_ACL_USER_OBJ,
[ACL_GROUP_OBJ] = ARCHIVE_ENTRY_ACL_GROUP_OBJ,
[ACL_MASK] = ARCHIVE_ENTRY_ACL_MASK,
[ACL_OTHER] = ARCHIVE_ENTRY_ACL_OTHER,
};
assert_cc(ACL_UNDEFINED_TAG == 0); /* safety check, we assume that holes are filled with ACL_UNDEFINED_TAG */
assert_cc(ELEMENTSOF(tag_map) <= 64); /* safety check, we assume that the tag ids are all packed and low */
int tag = ntag >= 0 && ntag <= (acl_tag_t) ELEMENTSOF(tag_map) ? tag_map[ntag] : ACL_UNDEFINED_TAG;
bool skip = false;
id_t qualifier = UID_INVALID;
if (IN_SET(ntag, ACL_USER, ACL_GROUP)) {
id_t *q = sym_acl_get_qualifier(e);
if (!q)
return log_error_errno(errno, "Failed to get ACL entry qualifier: %m");
qualifier = *q;
sym_acl_free(q);
/* Suppress invalid UIDs or those that shall be squashed */
skip = !(uid_is_valid(qualifier) &&
(!FLAGS_SET(flags, TAR_SQUASH_UIDS_ABOVE_64K) || qualifier < NSRESOURCE_UIDS_64K));
}
if (!skip) {
acl_permset_t p;
if (sym_acl_get_permset(e, &p) < 0)
return log_error_errno(errno, "Failed to get ACL entry permission set: %m");
int permset = 0;
r = sym_acl_get_perm(p, ACL_READ);
if (r < 0)
return log_error_errno(r, "Failed to get ACL entry read bit: %m");
SET_FLAG(permset, ARCHIVE_ENTRY_ACL_READ, r);
r = sym_acl_get_perm(p, ACL_WRITE);
if (r < 0)
return log_error_errno(r, "Failed to get ACL entry write bit: %m");
SET_FLAG(permset, ARCHIVE_ENTRY_ACL_WRITE, r);
r = sym_acl_get_perm(p, ACL_EXECUTE);
if (r < 0)
return log_error_errno(r, "Failed to get ACL entry execute bit: %m");
SET_FLAG(permset, ARCHIVE_ENTRY_ACL_EXECUTE, r);
r = sym_archive_entry_acl_add_entry(entry, type, permset, tag, qualifier, /* name= */ NULL);
if (r != ARCHIVE_OK)
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to add ACL entry.");
}
r = sym_acl_get_entry(acl, ACL_NEXT_ENTRY, &e);
}
return 0;
}
static int archive_item(
RecurseDirEvent event,
const char *path,
@ -684,7 +1218,7 @@ static int archive_item(
const struct statx *sx,
void *userdata) {
struct archive *a = ASSERT_PTR(userdata);
struct make_archive_data *d = ASSERT_PTR(userdata);
int r;
assert(path);
@ -702,17 +1236,31 @@ static int archive_item(
if (!entry)
return log_oom();
assert(FLAGS_SET(sx->stx_mask, STATX_TYPE|STATX_MODE));
sym_archive_entry_set_pathname(entry, path);
_cleanup_free_ char *hardlink = NULL;
r = hardlink_lookup(d, inode_fd, sx, path, &hardlink);
if (r < 0)
return r;
if (r > 0) {
sym_archive_entry_set_hardlink(entry, hardlink);
if (sym_archive_write_header(d->archive, entry) != ARCHIVE_OK)
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to write archive entry header: %s", sym_archive_error_string(d->archive));
return RECURSE_DIR_CONTINUE;
}
assert(FLAGS_SET(sx->stx_mask, STATX_TYPE|STATX_MODE));
sym_archive_entry_set_filetype(entry, sx->stx_mode);
if (!S_ISLNK(sx->stx_mode))
sym_archive_entry_set_perm(entry, sx->stx_mode);
if (FLAGS_SET(sx->stx_mask, STATX_UID))
sym_archive_entry_set_uid(entry, sx->stx_uid);
sym_archive_entry_set_uid(entry, maybe_squash_uid(sx->stx_uid, d->flags));
if (FLAGS_SET(sx->stx_mask, STATX_GID))
sym_archive_entry_set_gid(entry, sx->stx_gid);
sym_archive_entry_set_gid(entry, maybe_squash_gid(sx->stx_gid, d->flags));
if (S_ISREG(sx->stx_mode)) {
if (!FLAGS_SET(sx->stx_mask, STATX_SIZE))
@ -745,17 +1293,90 @@ static int archive_item(
sym_archive_entry_set_symlink(entry, s);
}
if (sym_archive_write_header(a, entry) != ARCHIVE_OK)
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to write archive entry header: %s", sym_archive_error_string(a));
if (inode_type_can_acl(sx->stx_mode)) {
r = dlopen_libacl();
if (r < 0)
log_debug_errno(r, "No trying to read ACL off inode, as libacl support is not available: %m");
else {
r = sym_acl_extended_file(FORMAT_PROC_FD_PATH(inode_fd));
if (r < 0 && !ERRNO_IS_NOT_SUPPORTED(errno))
return log_error_errno(errno, "Failed check if '%s' has ACLs: %m", path);
if (r > 0) {
_cleanup_(acl_freep) acl_t acl = NULL;
acl = sym_acl_get_file(FORMAT_PROC_FD_PATH(inode_fd), ACL_TYPE_ACCESS);
if (!acl)
return log_error_errno(errno, "Failed read access ACLs of '%s': %m", path);
archive_write_acl(entry, ACL_TYPE_ACCESS, acl, d->flags);
if (S_ISDIR(sx->stx_mode)) {
sym_acl_free(acl);
acl = sym_acl_get_file(FORMAT_PROC_FD_PATH(inode_fd), ACL_TYPE_DEFAULT);
if (!acl)
return log_error_errno(errno, "Failed to read default ACLs of '%s': %m", path);
archive_write_acl(entry, ACL_TYPE_DEFAULT, acl, d->flags);
}
}
}
}
_cleanup_free_ char *xattrs = NULL;
r = flistxattr_malloc(inode_fd, &xattrs);
if (r < 0 && !ERRNO_IS_NEG_NOT_SUPPORTED(r) && r != -ENODATA)
return log_error_errno(r, "Failed to read xattr list of '%s': %m", path);
NULSTR_FOREACH(xa, xattrs) {
_cleanup_free_ char *buf = NULL;
size_t size;
if (xattr_is_acl(xa))
continue;
if (!FLAGS_SET(d->flags, TAR_SELINUX) && xattr_is_selinux(xa))
continue;
r = fgetxattr_malloc(inode_fd, xa, &buf, &size);
if (r == -ENODATA) /* deleted by now? ignore... */
continue;
if (r < 0)
return log_error_errno(r, "Failed to read xattr '%s' of '%s': %m", xa, path);
sym_archive_entry_xattr_add_entry(entry, xa, buf, size);
}
_cleanup_close_ int data_fd = -EBADF;
if (S_ISREG(sx->stx_mode)) {
_cleanup_close_ int data_fd = -EBADF;
/* Convert the O_PATH fd in a proper fd */
/* Convert the O_PATH fd into a proper fd */
data_fd = fd_reopen(inode_fd, O_RDONLY|O_CLOEXEC);
if (data_fd < 0)
return log_error_errno(data_fd, "Failed to open '%s': %m", path);
r = archive_generate_sparse(entry, data_fd);
if (r < 0)
return r;
}
if (inode_type_can_chattr(sx->stx_mode)) {
unsigned f = 0;
r = read_attr_fd(data_fd >= 0 ? data_fd : inode_fd, &f);
if (r < 0 && !ERRNO_IS_NEG_NOT_SUPPORTED(r))
return log_error_errno(r, "Failed to read file flags of '%s': %m", path);
f &= CHATTR_TAR_FL;
if (f != 0)
sym_archive_entry_set_fflags(entry, f, /* clear= */ 0);
}
if (sym_archive_write_header(d->archive, entry) != ARCHIVE_OK)
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to write archive entry header: %s", sym_archive_error_string(d->archive));
if (S_ISREG(sx->stx_mode)) {
assert(data_fd >= 0);
for (;;) {
char buffer[64*1024];
ssize_t l;
@ -767,15 +1388,24 @@ static int archive_item(
break;
la_ssize_t k;
k = sym_archive_write_data(a, buffer, l);
k = sym_archive_write_data(d->archive, buffer, l);
if (k < 0)
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to write archive data: %s", sym_archive_error_string(a));
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to write archive data: %s", sym_archive_error_string(d->archive));
}
}
return RECURSE_DIR_CONTINUE;
}
static void make_archive_data_done(struct make_archive_data *d) {
assert(d);
if (d->hardlink_db_fd >= 0)
(void) rm_rf_children(d->hardlink_db_fd, REMOVE_PHYSICAL, /* root_dev= */ NULL);
unlink_and_free(d->hardlink_db_path);
}
int tar_c(int tree_fd, int output_fd, const char *filename, TarFlags flags) {
int r;
@ -789,7 +1419,7 @@ int tar_c(int tree_fd, int output_fd, const char *filename, TarFlags flags) {
if (filename)
r = sym_archive_write_set_format_filter_by_ext(a, filename);
else
r = sym_archive_write_set_format_gnutar(a);
r = sym_archive_write_set_format_pax(a);
if (r != ARCHIVE_OK)
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to set libarchive output format: %s", sym_archive_error_string(a));
@ -797,13 +1427,19 @@ int tar_c(int tree_fd, int output_fd, const char *filename, TarFlags flags) {
if (r != ARCHIVE_OK)
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to set libarchive output file: %s", sym_archive_error_string(a));
_cleanup_(make_archive_data_done) struct make_archive_data data = {
.archive = a,
.flags = flags,
.hardlink_db_fd = -EBADF,
};
r = recurse_dir(tree_fd,
".",
STATX_TYPE|STATX_MODE|STATX_UID|STATX_GID|STATX_SIZE|STATX_ATIME|STATX_CTIME,
UINT_MAX,
RECURSE_DIR_SORT|RECURSE_DIR_INODE_FD|RECURSE_DIR_TOPLEVEL,
archive_item,
a);
&data);
if (r < 0)
return log_error_errno(r, "Failed to make archive: %m");

View File

@ -2,7 +2,8 @@
#pragma once
typedef enum TarFlags {
TAR_SELINUX = 1 << 0,
TAR_SELINUX = 1 << 0, /* Include SELinux xattr in tarball, or unpack it */
TAR_SQUASH_UIDS_ABOVE_64K = 1 << 1, /* Squash UIDs/GIDs above 64K when packing/unpacking to the nobody user */
} TarFlags;
int tar_x(int input_fd, int tree_fd, TarFlags flags);

View File

@ -2,7 +2,7 @@
#include "varlink-io.systemd.BootControl.h"
static SD_VARLINK_DEFINE_ENUM_TYPE(
SD_VARLINK_DEFINE_ENUM_TYPE(
BootEntryType,
SD_VARLINK_FIELD_COMMENT("Boot Loader Specification Type #1 entries (.conf files)"),
SD_VARLINK_DEFINE_ENUM_VALUE(type1),
@ -13,7 +13,7 @@ static SD_VARLINK_DEFINE_ENUM_TYPE(
SD_VARLINK_FIELD_COMMENT("Automatically generated entries"),
SD_VARLINK_DEFINE_ENUM_VALUE(auto));
static SD_VARLINK_DEFINE_ENUM_TYPE(
SD_VARLINK_DEFINE_ENUM_TYPE(
BootEntrySource,
SD_VARLINK_FIELD_COMMENT("Boot entry found in EFI system partition (ESP)"),
SD_VARLINK_DEFINE_ENUM_VALUE(esp),

View File

@ -3,4 +3,7 @@
#include "sd-varlink-idl.h"
extern const sd_varlink_symbol vl_type_BootEntryType;
extern const sd_varlink_symbol vl_type_BootEntrySource;
extern const sd_varlink_interface vl_interface_io_systemd_BootControl;

View File

@ -3,7 +3,7 @@
#include "bus-polkit.h"
#include "varlink-io.systemd.MountFileSystem.h"
static SD_VARLINK_DEFINE_ENUM_TYPE(
SD_VARLINK_DEFINE_ENUM_TYPE(
PartitionDesignator,
SD_VARLINK_DEFINE_ENUM_VALUE(root),
SD_VARLINK_DEFINE_ENUM_VALUE(usr),

View File

@ -3,4 +3,6 @@
#include "sd-varlink-idl.h"
extern const sd_varlink_symbol vl_type_PartitionDesignator;
extern const sd_varlink_interface vl_interface_io_systemd_MountFileSystem;

View File

@ -582,6 +582,8 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE(
SD_VARLINK_DEFINE_FIELD(PrivatePIDs, SD_VARLINK_STRING, 0),
SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#PrivateUsers="),
SD_VARLINK_DEFINE_FIELD(PrivateUsers, SD_VARLINK_STRING, 0),
SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#UserNamespacePath="),
SD_VARLINK_DEFINE_FIELD(UserNamespacePath, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#ProtectHostname="),
SD_VARLINK_DEFINE_FIELD(ProtectHostname, SD_VARLINK_STRING, 0),
SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#ProtectClock="),

View File

@ -7,7 +7,10 @@
#include "sd-varlink.h"
#include "sd-varlink-idl.h"
#include "bootspec.h"
#include "dissect-image.h"
#include "fd-util.h"
#include "json-util.h"
#include "pretty-print.h"
#include "tests.h"
#include "varlink-idl-util.h"
@ -457,4 +460,58 @@ TEST(validate_method_call) {
assert_se(pthread_join(t, NULL) == 0);
}
static void test_enum_to_string_name(const char *n, const sd_varlink_symbol *symbol) {
assert(n);
assert(symbol);
assert(symbol->symbol_type == SD_VARLINK_ENUM_TYPE);
_cleanup_free_ char *m = ASSERT_PTR(json_underscorify(strdup(n)));
bool found = false;
for (const sd_varlink_field *f = symbol->fields; f->name; f++) {
if (f->field_type == _SD_VARLINK_FIELD_COMMENT)
continue;
assert(f->field_type == SD_VARLINK_ENUM_VALUE);
if (streq(m, f->name)) {
found = true;
break;
}
}
log_debug("'%s' found in '%s': %s", m, strna(symbol->name), yes_no(found));
assert(found);
}
#define TEST_IDL_ENUM_TO_STRING(type, ename, symbol) \
for (type t = 0;; t++) { \
const char *n = ename##_to_string(t); \
if (!n) \
break; \
test_enum_to_string_name(n, &(symbol)); \
}
#define TEST_IDL_ENUM_FROM_STRING(type, ename, symbol) \
for (const sd_varlink_field *f = (symbol).fields; f->name; f++) { \
if (f->field_type == _SD_VARLINK_FIELD_COMMENT) \
continue; \
assert(f->field_type == SD_VARLINK_ENUM_VALUE); \
_cleanup_free_ char *m = ASSERT_PTR(json_dashify(strdup(f->name))); \
type t = ename##_from_string(m); \
log_debug("'%s' of '%s' translates: %s", f->name, strna((symbol).name), yes_no(t >= 0)); \
assert(t >= 0); \
}
#define TEST_IDL_ENUM(type, name, symbol) \
do { \
TEST_IDL_ENUM_TO_STRING(type, name, symbol); \
TEST_IDL_ENUM_FROM_STRING(type, name, symbol); \
} while (false)
TEST(enums_idl) {
TEST_IDL_ENUM(BootEntryType, boot_entry_type, vl_type_BootEntryType);
TEST_IDL_ENUM_TO_STRING(BootEntrySource, boot_entry_source, vl_type_BootEntrySource);
TEST_IDL_ENUM(PartitionDesignator, partition_designator, vl_type_PartitionDesignator);
}
DEFINE_TEST_MAIN(LOG_DEBUG);

View File

@ -276,6 +276,7 @@ Unit=
UpheldBy=
Upholds=
User=
UserNamespacePath=
WakeSystem=
WantedBy=
Wants=

View File

@ -0,0 +1,52 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: LGPL-2.1-or-later
set -eux
set -o pipefail
# shellcheck source=test/units/util.sh
. "$(dirname "$0")"/util.sh
# When sanitizers are used, export LD_PRELOAD with the sanitizers path,
# lsns doesn't work otherwise.
if [ -f /usr/lib/systemd/systemd-asan-env ]; then
# shellcheck source=/dev/null
. /usr/lib/systemd/systemd-asan-env
export LD_PRELOAD
export ASAN_OPTIONS
fi
# Only reuse the user namespace
systemd-run --unit=oldservice --property=Type=exec --property=PrivateUsers=true sleep 3600
OLD_PID=$(systemctl show oldservice -p MainPID | awk -F= '{print $2}')
systemd-run --unit=newservice --property=Type=exec --property=UserNamespacePath=/proc/"$OLD_PID"/ns/user --property=PrivateNetwork=true sleep 3600
NEW_PID=$(systemctl show newservice -p MainPID | awk -F= '{print $2}')
assert_neq "$(lsns -p "$OLD_PID" -o NS -t net -n)" "$(lsns -p "$NEW_PID" -o NS -t net -n)"
assert_eq "$(lsns -p "$OLD_PID" -o NS -t user -n)" "$(lsns -p "$NEW_PID" -o NS -t user -n)"
systemctl stop oldservice newservice
# Reuse the user and network namespaces
systemd-run --unit=oldservice --property=Type=exec --property=PrivateUsers=true --property=PrivateNetwork=true sleep 3600
OLD_PID=$(systemctl show oldservice -p MainPID | awk -F= '{print $2}')
systemd-run --unit=newservice --property=Type=exec --property=UserNamespacePath=/proc/"$OLD_PID"/ns/user --property=NetworkNamespacePath=/proc/"$OLD_PID"/ns/net sleep 3600
NEW_PID=$(systemctl show newservice -p MainPID | awk -F= '{print $2}')
assert_eq "$(lsns -p "$OLD_PID" -o NS -t net -n)" "$(lsns -p "$NEW_PID" -o NS -t net -n)"
assert_eq "$(lsns -p "$OLD_PID" -o NS -t user -n)" "$(lsns -p "$NEW_PID" -o NS -t user -n)"
systemctl stop oldservice newservice
# Delegate the network namespace
systemd-run --unit=oldservice --property=Type=exec --property=PrivateUsers=true sleep 3600
OLD_PID=$(systemctl show oldservice -p MainPID | awk -F= '{print $2}')
systemd-run --unit=newservice --property=Type=exec --property=UserNamespacePath=/proc/"$OLD_PID"/ns/user --property=DelegateNamespaces=net --property=PrivateNetwork=true sleep 3600
NEW_PID=$(systemctl show newservice -p MainPID | awk -F= '{print $2}')
assert_neq "$(lsns -p "$OLD_PID" -o NS -t net -n)" "$(lsns -p "$NEW_PID" -o NS -t net -n)"
assert_eq "$(lsns -p "$OLD_PID" -o NS -t user -n)" "$(lsns -p "$NEW_PID" -o NS -t user -n)"
systemctl stop oldservice newservice

View File

@ -14,8 +14,21 @@ fi
at_exit() {
rm -rf /home/testuser/.local/state/machines/zurps ||:
rm -rf /home/testuser/.local/state/machines/nurps ||:
rm -rf /home/testuser/.local/state/machines/kurps ||:
rm -rf /home/testuser/.local/state/machines/wumms ||:
rm -rf /home/testuser/.local/state/machines/wamms ||:
rm -rf /home/testuser/.local/state/machines/inodetest ||:
rm -rf /home/testuser/.local/state/machines/inodetest2 ||:
machinectl terminate zurps ||:
rm -f /etc/polkit-1/rules.d/registermachinetest.rules
machinectl terminate nurps ||:
machinectl terminate kurps ||:
machinectl terminate wumms ||:
machinectl terminate wamms ||:
rm -f /usr/share/polkit-1/rules.d/registermachinetest.rules
rm -rf /var/tmp/mangletest
rm -f /var/tmp/mangletest.tar.gz
}
trap at_exit EXIT
@ -45,6 +58,8 @@ EOF
loginctl enable-linger testuser
run0 -u testuser mkdir -p .config/systemd/nspawn/
run0 -u testuser -i "echo -e \"[Exec]\nKillSignal=SIGKILL\n\" > .config/systemd/nspawn/zurps.nspawn"
run0 -u testuser systemctl start --user systemd-nspawn@zurps.service
machinectl status zurps
@ -87,4 +102,114 @@ run0 -u testuser \
(! run0 -u testuser machinectl shell 0@shouldnotwork2 /usr/bin/id -u)
(! run0 -u testuser machinectl shell testuser@shouldnotwork2 /usr/bin/id -u)
run0 -u testuser mkdir /var/tmp/image-tar
run0 -u testuser importctl --user export-tar zurps /var/tmp/image-tar/kurps.tar.gz -m
run0 -u testuser importctl --user import-tar /var/tmp/image-tar/kurps.tar.gz -m
run0 -u testuser -i "echo -e \"[Exec]\nKillSignal=SIGKILL\n\" > .config/systemd/nspawn/kurps.nspawn"
run0 -u testuser systemctl start --user systemd-nspawn@kurps.service
machinectl terminate kurps
run0 -u testuser -D /var/tmp/image-tar/ bash -c 'sha256sum kurps.tar.gz > SHA256SUMS'
run0 -u testuser importctl --user pull-tar file:///var/tmp/image-tar/kurps.tar.gz nurps --verify=checksum -m
run0 -u testuser -i "echo -e \"[Exec]\nKillSignal=SIGKILL\n\" > .config/systemd/nspawn/nurps.nspawn"
run0 -u testuser systemctl start --user systemd-nspawn@nurps.service
machinectl terminate nurps
run0 -u testuser rm -r /var/tmp/image-tar
run0 -u testuser importctl --user list-images
run0 -u testuser machinectl --user list-images
assert_in 'zurps' "$(run0 --pipe -u testuser machinectl --user list-images)"
assert_in 'nurps' "$(run0 --pipe -u testuser machinectl --user list-images)"
assert_in 'kurps' "$(run0 --pipe -u testuser machinectl --user list-images)"
run0 -u testuser machinectl --user image-status zurps
run0 -u testuser machinectl --user image-status nurps
run0 -u testuser machinectl --user image-status kurps
run0 -u testuser machinectl --user show-image zurps
run0 -u testuser machinectl --user show-image nurps
run0 -u testuser machinectl --user show-image kurps
run0 -u testuser machinectl --user clone zurps wumms
assert_in 'wumms' "$(run0 -u testuser machinectl --user list-images)"
run0 -u testuser machinectl --user image-status wumms
run0 -u testuser machinectl --user show-image wumms
run0 -u testuser machinectl --user rename wumms wamms
assert_not_in 'wumms' "$(run0 -u testuser machinectl --user list-images)"
assert_in 'wamms' "$(run0 -u testuser machinectl --user list-images)"
run0 -u testuser machinectl --user image-status wamms
run0 -u testuser machinectl --user show-image wamms
run0 -u testuser -i "echo -e \"[Exec]\nKillSignal=SIGKILL\n\" > .config/systemd/nspawn/wamms.nspawn"
run0 -u testuser systemctl start --user systemd-nspawn@wamms.service
run0 -u testuser systemctl stop --user systemd-nspawn@zurps.service
run0 -u testuser systemctl stop --user systemd-nspawn@nurps.service
run0 -u testuser systemctl stop --user systemd-nspawn@kurps.service
run0 -u testuser systemctl stop --user systemd-nspawn@wamms.service
run0 -u testuser machinectl --user remove zurps
run0 -u testuser machinectl --user remove kurps
run0 -u testuser machinectl --user remove nurps
run0 -u testuser machinectl --user remove wamms
assert_not_in 'zurps' "$(run0 --pipe -u testuser machinectl --user list-images)"
assert_not_in 'nurps' "$(run0 --pipe -u testuser machinectl --user list-images)"
assert_not_in 'kurps' "$(run0 --pipe -u testuser machinectl --user list-images)"
mkdir /home/testuser/.local/state/machines/inodetest
echo hallo > /home/testuser/.local/state/machines/inodetest/testfile
# Make the file sparse, set an xattr, set an ACL, set a chattr flag, and make it hardlink
ln /home/testuser/.local/state/machines/inodetest/testfile /home/testuser/.local/state/machines/inodetest/testfile.hard
truncate -s 1M /home/testuser/.local/state/machines/inodetest/testfile
setfattr -n "user.piff" -v "paff" /home/testuser/.local/state/machines/inodetest/testfile
setfacl -m g:foreign-47:rwx /home/testuser/.local/state/machines/inodetest/testfile
chattr +A /home/testuser/.local/state/machines/inodetest/testfile
chown foreign-0:foreign-0 /home/testuser/.local/state/machines/inodetest/testfile.hard /home/testuser/.local/state/machines/inodetest
ls -al /home/testuser/.local/state/machines/inodetest
echo gaga > /home/testuser/.local/state/machines/inodetest/squashtest
chown 1000:1000 /home/testuser/.local/state/machines/inodetest/squashtest
run0 --pipe -u testuser importctl -m --user export-tar inodetest |
run0 --pipe -u testuser importctl -m --user import-tar - inodetest2
ls -al /home/testuser/.local/state/machines/inodetest2
cmp /home/testuser/.local/state/machines/inodetest/testfile /home/testuser/.local/state/machines/inodetest2/testfile.hard
cmp <(stat -c"%s %b %B" /home/testuser/.local/state/machines/inodetest/testfile) <(stat -c"%s %b %B" /home/testuser/.local/state/machines/inodetest2/testfile)
cmp <(stat -c"%i" /home/testuser/.local/state/machines/inodetest2/testfile) <(stat -c"%i" /home/testuser/.local/state/machines/inodetest2/testfile.hard)
getfattr -d /home/testuser/.local/state/machines/inodetest/testfile
getfattr -d /home/testuser/.local/state/machines/inodetest2/testfile
getfacl /home/testuser/.local/state/machines/inodetest/testfile
getfacl /home/testuser/.local/state/machines/inodetest2/testfile
lsattr /home/testuser/.local/state/machines/inodetest/testfile
lsattr /home/testuser/.local/state/machines/inodetest2/testfile
cmp <(getfattr -d /home/testuser/.local/state/machines/inodetest/testfile | grep -v ^#) <(getfattr -d /home/testuser/.local/state/machines/inodetest2/testfile | grep -v ^#)
cmp <(getfacl /home/testuser/.local/state/machines/inodetest/testfile | grep -v ^#) <(getfacl /home/testuser/.local/state/machines/inodetest2/testfile | grep -v ^#)
cmp <(lsattr /home/testuser/.local/state/machines/inodetest/testfile | cut -d " " -f1) <(lsattr /home/testuser/.local/state/machines/inodetest2/testfile | cut -d " " -f1)
# verify that squashing outside of 64K works
test "$(stat -c'%U:%G' /home/testuser/.local/state/machines/inodetest2/squashtest)" = "foreign-65534:foreign-65534"
# chown to foreing UID range, so that removal works
chown foreign-4711:foreign-4711 /home/testuser/.local/state/machines/inodetest/squashtest
run0 -u testuser machinectl --user remove inodetest
run0 -u testuser machinectl --user remove inodetest2
# Test tree mangling (i.e. moving the root dir one level up on extract)
mkdir -p /var/tmp/mangletest/mangletest-0.1/usr/lib
echo "ID=brumm" > /var/tmp/mangletest/mangletest-0.1/usr/lib/os-release
tar -C /var/tmp/mangletest/ -cvzf /var/tmp/mangletest.tar.gz mangletest-0.1
run0 --pipe -u testuser importctl -m --user import-tar /var/tmp/mangletest.tar.gz
cmp /var/tmp/mangletest/mangletest-0.1/usr/lib/os-release /home/testuser/.local/state/machines/mangletest/usr/lib/os-release
loginctl disable-linger testuser

View File

@ -3,11 +3,11 @@
set -eux
set -o pipefail
(! journalctl -q -o short-monotonic --grep "didn't pass validation" | grep -v "test-varlink-idl" >>/failed)
(! journalctl -q -o short-monotonic --grep "didn't pass validation" | grep -v "test-varlink-idl")
# Here, the redundant '[ ]' in the pattern is required in order not to match the logged command itself.
(! journalctl -q -o short-monotonic --grep 'Warning: cannot close sd-bus connection[ ].*after fork' >>/failed)
(! journalctl -q -o short-monotonic --grep 'Warning: cannot close sd-bus connection[ ].*after fork')
# Check if sd-executor doesn't complain about not being able to (de)serialize stuff
(! journalctl -q -o short-monotonic --grep "[F]ailed to parse serialized line" >>/failed)
(! journalctl -q -o short-monotonic --grep "[F]ailed to (de)?serialize \w+" >>/failed)
(! journalctl -q -o short-monotonic --grep "[F]ailed to parse serialized line")
(! journalctl -q -o short-monotonic --grep "[F]ailed to (de)?serialize \w+")