1
0
mirror of https://github.com/systemd/systemd synced 2025-11-20 17:24:45 +01:00

Compare commits

..

14 Commits

Author SHA1 Message Date
Luca Boccassi
de2276cdcd Revert "machine: restrict register-machine action again"
Now that we have landed several fixes, this should be safe to do
again, so allow logged in users to register machines without
authentication prompts

This reverts commit 65badde82e0c77875a23f084cf3251c052e042e2.
2025-10-15 12:02:34 +02:00
Luca Boccassi
9dd61cfbe7
Use verity sharing for user services and nspawn too (#39313)
https://github.com/systemd/systemd/pull/39168 made verity sharing
opt-in, and enabled it for system services.
Also enable it for user services for RootImage/etc, and for nspawn, for
the same reasons.
2025-10-15 11:01:57 +01:00
Govind Venugopal
3eb7b881bd
network: add DHCP server domain name option support (#39260)
Implements DHCP option 15 (Domain Name) for systemd-networkd's DHCP
server, allowing administrators to configure the DNS default domain that
clients should use.

This addresses the feature request in issue #37077, where users needed
to manually configure domain names using
SendOption=15:string:example.com as a workaround.

This adds two new configuration options to the [DHCPServer] section:
- EmitDomain= (boolean): whether to send domain name to clients
- Domain= (string): the domain name to send (e.g., "example.com")

Example configuration:
  [DHCPServer] EmitDomain=yes Domain=example.com

This eliminates the need for manual workarounds using
SendOption=15:string:...

Fixes #37077
2025-10-15 11:20:41 +02:00
Lennart Poettering
4cae0e9a78
importd: change untar logic to be based on libarchive rather than shelling out to gnu tar (#39143)
Let's use libarchive consistently everywhere, both for tarring and
untarring.

Note that there's an existing test case that validates untarring. Now,
it will validate libarchive rather than gnu tar.

Split out of #38728
2025-10-15 11:12:23 +02:00
Lennart Poettering
9f69ff69f7 mountfsd: make MountDirectory() work with systemd-homed
systemd-homed already applies an idmap to its mounts, hence we need to
undo it before we can create our own.
2025-10-15 10:21:59 +02:00
Yu Watanabe
0d87de0b8e nsresource: fix varlink method dispatch table
Follow-up for 716bf93c4bde07c1870b9b0837f2ec33f36686f1 (v258).
2025-10-15 10:16:06 +02:00
Luca Boccassi
566a4bbbbf nspawn: enable verity sharing
Just like RootImage=, ExtensionImages= etc, nspawn can make use of
this to save a lot of time when starting containers that use an already
open image, since the default was changed to disabled.

Follow-up for 57d1ceffb3d98f69c2da511ed59a420a1cfa7e40
2025-10-14 20:49:12 +01:00
Luca Boccassi
ce7a5d6026 dissect-image: pass through DISSECT_IMAGE_VERITY_SHARE to mountfsd via varlink if set
This ensures user services using RootImage=, ExtensionImages= etc. also
try to reuse existing verity devices, like system services.

Follow-up for 57d1ceffb3d98f69c2da511ed59a420a1cfa7e40
2025-10-14 20:49:12 +01:00
Luca Boccassi
a9b1e35a32 mountfsd: add boolean parameter to let callers enable verity sharing 2025-10-14 20:49:11 +01:00
Luca Boccassi
d29a2cd2d4 mountfsd: json format unsigned types as unsigned
sizes/offsets are unsigned ints, so use the appropriate macros
to build the json messages, otherwise UINT64T_MAX is sent as -1
2025-10-14 20:49:11 +01:00
Lennart Poettering
a7c8f92d1f importd: port untarring logic over to libarchive
This way we have can expose identical behaviour everywhere, can make use
of our atomic replacement calls, and openat() logic, and later apply
additional tracks while unpacking, such as putting limits on UID ranges
and similar.
2025-10-14 11:56:45 +02:00
Lennart Poettering
8be204df2b stat-util: add fd_verify_symlink() helper 2025-10-14 11:21:46 +02:00
Lennart Poettering
90fae0b46c stat-util: add helper inode_type_can_hardlink() 2025-10-14 11:21:46 +02:00
Lennart Poettering
b2fa6d0945 xattr: add helper that detect special purpose xattrs 2025-10-14 11:21:46 +02:00
33 changed files with 1154 additions and 80 deletions

View File

@ -3992,6 +3992,34 @@ ServerAddress=192.168.0.1/24</programlisting>
<xi:include href="version-info.xml" xpointer="v226"/></listitem>
</varlistentry>
<varlistentry>
<term><varname>EmitDomain=</varname></term>
<listitem><para>Takes a boolean. Configures whether the DHCP leases handed out
to clients shall contain domain name information (DHCP option 15). Defaults to
<literal>no</literal>.</para>
<xi:include href="version-info.xml" xpointer="v259"/></listitem>
</varlistentry>
<varlistentry>
<term><varname>Domain=</varname></term>
<listitem><para>Takes a domain name (such as <literal>example.com</literal>)
to pass to DHCP clients. This configures the DNS default domain for DHCP clients.
When set, DHCP clients will use this as their DNS search domain.</para>
<para>When <varname>EmitDomain=yes</varname> is set but <varname>Domain=</varname>
is not configured, the domain name will be automatically derived from the system's
fully qualified hostname. For example, if the system's hostname is
<literal>host.example.com</literal>, the domain <literal>example.com</literal>
will be sent to clients. If the system's hostname does not contain a domain part
(e.g., hostname is just <literal>host</literal>), no domain name will be sent to
DHCP clients. When empty or unset, defaults to no domain name.</para>
<xi:include href="version-info.xml" xpointer="v259"/></listitem>
</varlistentry>
<varlistentry>
<term><varname>BootServerAddress=</varname></term>

View File

@ -1438,6 +1438,10 @@ libarchive = dependency('libarchive',
version : '>= 3.0',
required : get_option('libarchive'))
conf.set10('HAVE_LIBARCHIVE', libarchive.found())
conf.set10('HAVE_LIBARCHIVE_UID_IS_SET',
libblkid.found() and cc.has_function('archive_entry_uid_is_set', dependencies : libarchive))
conf.set10('HAVE_LIBARCHIVE_HARDLINK_IS_SET',
libblkid.found() and cc.has_function('archive_entry_hardlink_is_set', dependencies : libarchive))
libxkbcommon = dependency('xkbcommon',
version : '>= 0.3.0',

View File

@ -108,6 +108,10 @@ int stat_verify_symlink(const struct stat *st) {
return 0;
}
int fd_verify_symlink(int fd) {
return verify_stat_at(fd, /* path= */ NULL, /* follow= */ false, stat_verify_symlink, /* verify= */ true);
}
int is_symlink(const char *path) {
assert(!isempty(path));
return verify_stat_at(AT_FDCWD, path, false, stat_verify_symlink, false);

View File

@ -16,6 +16,7 @@ int is_dir_at(int fd, const char *path, bool follow);
int is_dir(const char *path, bool follow);
int stat_verify_symlink(const struct stat *st);
int fd_verify_symlink(int fd);
int is_symlink(const char *path);
int stat_verify_linked(const struct stat *st);
@ -110,3 +111,10 @@ static inline bool stat_is_set(const struct stat *st) {
static inline bool statx_is_set(const struct statx *sx) {
return sx && sx->stx_mask != 0;
}
static inline bool inode_type_can_hardlink(mode_t m) {
/* returns true for all inode types that support hardlinks on linux. Note this is effectively all
* inode types except for directories (and those weird misc fds such as eventfds() that have no inode
* type). */
return IN_SET(m & S_IFMT, S_IFSOCK, S_IFLNK, S_IFREG, S_IFBLK, S_IFCHR, S_IFIFO);
}

View File

@ -477,3 +477,14 @@ int fd_setcrtime(int fd, usec_t usec) {
"user.crtime_usec", (const char*) &le, sizeof(le),
/* xattr_flags = */ 0);
}
bool xattr_is_acl(const char *name) {
return STR_IN_SET(
ASSERT_PTR(name),
"system.posix_acl_access",
"system.posix_acl_default");
}
bool xattr_is_selinux(const char *name) {
return streq(ASSERT_PTR(name), "security.selinux");
}

View File

@ -54,3 +54,6 @@ int getcrtime_at(int fd, const char *path, int at_flags, usec_t *ret);
static inline int fd_getcrtime(int fd, usec_t *ret) {
return getcrtime_at(fd, NULL, 0, ret);
}
bool xattr_is_acl(const char *name);
bool xattr_is_selinux(const char *name);

View File

@ -1,71 +1,60 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <sched.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/prctl.h>
#include "sd-event.h"
#include "alloc-util.h"
#include "capability-util.h"
#include "dirent-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "fs-util.h"
#include "import-common.h"
#include "libarchive-util.h"
#include "log.h"
#include "os-util.h"
#include "pidref.h"
#include "process-util.h"
#include "selinux-util.h"
#include "stat-util.h"
#include "tar-util.h"
#include "tmpfile-util.h"
int import_fork_tar_x(const char *path, PidRef *ret) {
_cleanup_(pidref_done) PidRef pid = PIDREF_NULL;
_cleanup_close_pair_ int pipefd[2] = EBADF_PAIR;
bool use_selinux;
int import_fork_tar_x(int tree_fd, PidRef *ret_pid) {
int r;
assert(path);
assert(ret);
assert(tree_fd >= 0);
assert(ret_pid);
r = dlopen_libarchive();
if (r < 0)
return r;
TarFlags flags = mac_selinux_use() ? TAR_SELINUX : 0;
_cleanup_close_pair_ int pipefd[2] = EBADF_PAIR;
if (pipe2(pipefd, O_CLOEXEC) < 0)
return log_error_errno(errno, "Failed to create pipe for tar: %m");
(void) fcntl(pipefd[0], F_SETPIPE_SZ, IMPORT_BUFFER_SIZE);
use_selinux = mac_selinux_use();
r = pidref_safe_fork_full(
"(tar)",
(int[]) { pipefd[0], -EBADF, STDERR_FILENO },
NULL, 0,
FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_REARRANGE_STDIO|FORK_LOG,
&pid);
"tar-x",
/* stdio_fds= */ NULL,
(int[]) { tree_fd, pipefd[0] }, 2,
FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_REOPEN_LOG,
ret_pid);
if (r < 0)
return r;
if (r == 0) {
const char *cmdline[] = {
"tar",
"--ignore-zeros",
"--numeric-owner",
"-C", path,
"-pxf",
"-",
"--xattrs",
"--xattrs-include=*",
use_selinux ? "--selinux" : "--no-selinux",
NULL
};
uint64_t retain =
static const uint64_t retain =
(1ULL << CAP_CHOWN) |
(1ULL << CAP_FOWNER) |
(1ULL << CAP_FSETID) |
(1ULL << CAP_MKNOD) |
(1ULL << CAP_SETFCAP) |
(1ULL << CAP_DAC_OVERRIDE);
(1ULL << CAP_DAC_OVERRIDE) |
(1ULL << CAP_DAC_READ_SEARCH);
/* Child */
@ -76,21 +65,15 @@ int import_fork_tar_x(const char *path, PidRef *ret) {
if (r < 0)
log_warning_errno(r, "Failed to drop capabilities, ignoring: %m");
/* Try "gtar" before "tar". We only test things upstream with GNU tar. Some distros appear to
* install a different implementation as "tar" (in particular some that do not support the
* same command line switches), but then provide "gtar" as alias for the real thing, hence
* let's prefer that. (Yes, it's a bad idea they do that, given they don't provide equivalent
* command line support, but we are not here to argue, let's just expose the same
* behaviour/implementation everywhere.) */
execvp("gtar", (char* const*) cmdline);
execvp("tar", (char* const*) cmdline);
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0)
log_warning_errno(errno, "Failed to enable PR_SET_NO_NEW_PRIVS, ignoring: %m");
log_error_errno(errno, "Failed to execute tar: %m");
_exit(EXIT_FAILURE);
if (tar_x(pipefd[0], tree_fd, flags) < 0)
_exit(EXIT_FAILURE);
_exit(EXIT_SUCCESS);
}
*ret = TAKE_PIDREF(pid);
return TAKE_FD(pipefd[1]);
}

View File

@ -34,7 +34,7 @@ typedef enum ImportFlags {
} ImportFlags;
int import_fork_tar_c(const char *path, PidRef *ret);
int import_fork_tar_x(const char *path, PidRef *ret);
int import_fork_tar_x(int tree_fd, PidRef *ret_pid);
int import_mangle_os_tree(const char *path);

View File

@ -45,6 +45,7 @@ typedef struct TarImport {
int input_fd;
int tar_fd;
int tree_fd;
ImportCompress compress;
@ -79,6 +80,7 @@ TarImport* tar_import_unref(TarImport *i) {
sd_event_unref(i->event);
safe_close(i->tar_fd);
safe_close(i->tree_fd);
free(i->final_path);
free(i->image_root);
@ -111,6 +113,7 @@ int tar_import_new(
*i = (TarImport) {
.input_fd = -EBADF,
.tar_fd = -EBADF,
.tree_fd = -EBADF,
.on_finished = on_finished,
.userdata = userdata,
.last_percent = UINT_MAX,
@ -172,6 +175,7 @@ static int tar_import_finish(TarImport *i) {
assert(i);
assert(i->tar_fd >= 0);
assert(i->tree_fd >= 0);
i->tar_fd = safe_close(i->tar_fd);
@ -215,6 +219,7 @@ static int tar_import_fork_tar(TarImport *i) {
assert(!i->final_path);
assert(!i->temp_path);
assert(i->tar_fd < 0);
assert(i->tree_fd < 0);
if (i->flags & IMPORT_DIRECT) {
d = i->local;
@ -254,7 +259,11 @@ static int tar_import_fork_tar(TarImport *i) {
(void) import_assign_pool_quota_and_warn(d);
}
i->tar_fd = import_fork_tar_x(d, &i->tar_pid);
i->tree_fd = open(d, O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW);
if (i->tree_fd < 0)
return log_error_errno(errno, "Failed to open '%s': %m", d);
i->tar_fd = import_fork_tar_x(i->tree_fd, &i->tar_pid);
if (i->tar_fd < 0)
return i->tar_fd;

View File

@ -10,6 +10,7 @@
#include "copy.h"
#include "curl-util.h"
#include "errno-util.h"
#include "fd-util.h"
#include "fs-util.h"
#include "import-common.h"
#include "import-util.h"
@ -61,6 +62,8 @@ typedef struct TarPull {
char *settings_temp_path;
char *checksum;
int tree_fd;
} TarPull;
TarPull* tar_pull_unref(TarPull *i) {
@ -86,6 +89,8 @@ TarPull* tar_pull_unref(TarPull *i) {
free(i->local);
free(i->checksum);
safe_close(i->tree_fd);
return mfree(i);
}
@ -132,6 +137,7 @@ int tar_pull_new(
.event = TAKE_PTR(e),
.glue = TAKE_PTR(g),
.tar_pid = PIDREF_NULL,
.tree_fd = -EBADF,
};
i->glue->on_finished = pull_job_curl_on_finished;
@ -512,6 +518,7 @@ static int tar_pull_job_on_open_disk_tar(PullJob *j) {
i = j->userdata;
assert(i->tar_job == j);
assert(!pidref_is_set(&i->tar_pid));
assert(i->tree_fd < 0);
if (i->flags & IMPORT_DIRECT)
where = i->local;
@ -545,7 +552,11 @@ static int tar_pull_job_on_open_disk_tar(PullJob *j) {
(void) import_assign_pool_quota_and_warn(where);
}
j->disk_fd = import_fork_tar_x(where, &i->tar_pid);
i->tree_fd = open(where, O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW);
if (i->tree_fd < 0)
return log_error_errno(errno, "Failed to open '%s': %m", where);
j->disk_fd = import_fork_tar_x(i->tree_fd, &i->tar_pid);
if (j->disk_fd < 0)
return j->disk_fd;

View File

@ -46,6 +46,7 @@ typedef struct sd_dhcp_server {
uint32_t pool_size;
char *timezone;
char *domain_name;
DHCPServerData servers[_SD_DHCP_LEASE_SERVER_TYPE_MAX];
struct in_addr boot_server_address;

View File

@ -128,6 +128,7 @@ static sd_dhcp_server *dhcp_server_free(sd_dhcp_server *server) {
free(server->boot_server_name);
free(server->boot_filename);
free(server->timezone);
free(server->domain_name);
for (sd_dhcp_lease_server_type_t i = 0; i < _SD_DHCP_LEASE_SERVER_TYPE_MAX; i++)
free(server->servers[i].addr);
@ -625,6 +626,15 @@ static int server_send_offer_or_ack(
return r;
}
if (server->domain_name) {
r = dhcp_option_append(
&packet->dhcp, req->max_optlen, &offset, 0,
SD_DHCP_OPTION_DOMAIN_NAME,
strlen(server->domain_name), server->domain_name);
if (r < 0)
return r;
}
/* RFC 8925 section 3.3. DHCPv4 Server Behavior
* The server MUST NOT include the IPv6-Only Preferred option in the DHCPOFFER or DHCPACK message if
* the option was not present in the Parameter Request List sent by the client. */
@ -1415,6 +1425,22 @@ int sd_dhcp_server_set_timezone(sd_dhcp_server *server, const char *tz) {
return 1;
}
int sd_dhcp_server_set_domain_name(sd_dhcp_server *server, const char *domain_name) {
int r;
assert_return(server, -EINVAL);
if (domain_name) {
r = dns_name_is_valid(domain_name);
if (r < 0)
return r;
if (r == 0)
return -EINVAL;
}
return free_and_strdup(&server->domain_name, domain_name);
}
int sd_dhcp_server_set_max_lease_time(sd_dhcp_server *server, uint64_t t) {
assert_return(server, -EINVAL);

View File

@ -316,6 +316,44 @@ static void test_static_lease(void) {
(uint8_t*) &(uint32_t) { 0x01020306 }, sizeof(uint32_t)));
}
static void test_domain_name(void) {
_cleanup_(sd_dhcp_server_unrefp) sd_dhcp_server *server = NULL;
log_debug("/* %s */", __func__);
ASSERT_OK(sd_dhcp_server_new(&server, 1));
/* Test setting domain name */
ASSERT_OK_POSITIVE(sd_dhcp_server_set_domain_name(server, "example.com"));
/* Test setting same domain name (should return 0 - no change) */
ASSERT_OK_ZERO(sd_dhcp_server_set_domain_name(server, "example.com"));
/* Test changing domain name */
ASSERT_OK_POSITIVE(sd_dhcp_server_set_domain_name(server, "test.local"));
/* Test clearing domain name */
ASSERT_OK_POSITIVE(sd_dhcp_server_set_domain_name(server, NULL));
/* Test clearing again (should return 0 - already cleared) */
ASSERT_OK_ZERO(sd_dhcp_server_set_domain_name(server, NULL));
/* Test invalid domain name */
ASSERT_ERROR(sd_dhcp_server_set_domain_name(server, "invalid..domain"), EINVAL);
/* Test empty string (treated differently from NULL) */
ASSERT_OK_POSITIVE(sd_dhcp_server_set_domain_name(server, ""));
/* Test clearing domain name with NULL */
ASSERT_OK_POSITIVE(sd_dhcp_server_set_domain_name(server, NULL));
/* Test valid domain with subdomain */
ASSERT_OK_POSITIVE(sd_dhcp_server_set_domain_name(server, "sub.example.com"));
/* Test single-label domain */
ASSERT_OK_POSITIVE(sd_dhcp_server_set_domain_name(server, "local"));
}
int main(int argc, char *argv[]) {
int r;
@ -323,6 +361,7 @@ int main(int argc, char *argv[]) {
test_client_id_hash();
test_static_lease();
test_domain_name();
r = test_basic(true);
if (r < 0)

View File

@ -108,7 +108,7 @@
<defaults>
<allow_any>auth_admin</allow_any>
<allow_inactive>auth_admin</allow_inactive>
<allow_active>auth_admin_keep</allow_active>
<allow_active>yes</allow_active>
</defaults>
</action>

View File

@ -27,6 +27,7 @@
#include "loop-util.h"
#include "main-func.h"
#include "memory-util.h"
#include "mount-util.h"
#include "namespace-util.h"
#include "nsresource.h"
#include "nulstr-util.h"
@ -41,6 +42,7 @@
#include "time-util.h"
#include "uid-classification.h"
#include "uid-range.h"
#include "user-util.h"
#include "varlink-io.systemd.MountFileSystem.h"
#include "varlink-util.h"
@ -89,6 +91,7 @@ typedef struct MountImageParameters {
int growfs;
char *password;
ImagePolicy *image_policy;
bool verity_sharing;
} MountImageParameters;
static void mount_image_parameters_done(MountImageParameters *p) {
@ -283,12 +286,13 @@ static int vl_method_mount_image(
void *userdata) {
static const sd_json_dispatch_field dispatch_table[] = {
{ "imageFileDescriptor", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uint, offsetof(MountImageParameters, image_fd_idx), SD_JSON_MANDATORY },
{ "userNamespaceFileDescriptor", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uint, offsetof(MountImageParameters, userns_fd_idx), 0 },
{ "readOnly", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, offsetof(MountImageParameters, read_only), 0 },
{ "growFileSystems", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, offsetof(MountImageParameters, growfs), 0 },
{ "password", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(MountImageParameters, password), 0 },
{ "imagePolicy", SD_JSON_VARIANT_STRING, json_dispatch_image_policy, offsetof(MountImageParameters, image_policy), 0 },
{ "imageFileDescriptor", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uint, offsetof(MountImageParameters, image_fd_idx), SD_JSON_MANDATORY },
{ "userNamespaceFileDescriptor", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uint, offsetof(MountImageParameters, userns_fd_idx), 0 },
{ "readOnly", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, offsetof(MountImageParameters, read_only), 0 },
{ "growFileSystems", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, offsetof(MountImageParameters, growfs), 0 },
{ "password", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(MountImageParameters, password), 0 },
{ "imagePolicy", SD_JSON_VARIANT_STRING, json_dispatch_image_policy, offsetof(MountImageParameters, image_policy), 0 },
{ "veritySharing", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(MountImageParameters, verity_sharing), 0 },
VARLINK_DISPATCH_POLKIT_FIELD,
{}
};
@ -403,6 +407,7 @@ static int vl_method_mount_image(
DISSECT_IMAGE_FSCK |
DISSECT_IMAGE_ADD_PARTITION_DEVICES |
DISSECT_IMAGE_PIN_PARTITION_DEVICES |
(p.verity_sharing ? DISSECT_IMAGE_VERITY_SHARE : 0) |
DISSECT_IMAGE_ALLOW_USERSPACE_VERITY;
/* Let's see if we have acquired the privilege to mount untrusted images already */
@ -561,8 +566,8 @@ static int vl_method_mount_image(
SD_JSON_BUILD_PAIR_CONDITION(!sd_id128_is_null(pp->uuid), "partitionUuid", SD_JSON_BUILD_UUID(pp->uuid)),
SD_JSON_BUILD_PAIR("fileSystemType", SD_JSON_BUILD_STRING(dissected_partition_fstype(pp))),
SD_JSON_BUILD_PAIR_CONDITION(!!pp->label, "partitionLabel", SD_JSON_BUILD_STRING(pp->label)),
SD_JSON_BUILD_PAIR("size", SD_JSON_BUILD_INTEGER(pp->size)),
SD_JSON_BUILD_PAIR("offset", SD_JSON_BUILD_INTEGER(pp->offset)),
SD_JSON_BUILD_PAIR("size", SD_JSON_BUILD_UNSIGNED(pp->size)),
SD_JSON_BUILD_PAIR("offset", SD_JSON_BUILD_UNSIGNED(pp->offset)),
SD_JSON_BUILD_PAIR("mountFileDescriptor", SD_JSON_BUILD_INTEGER(fd_idx)),
JSON_BUILD_PAIR_STRV_NON_EMPTY("mountPoint", l));
if (r < 0)
@ -575,8 +580,8 @@ static int vl_method_mount_image(
link,
SD_JSON_BUILD_PAIR("partitions", SD_JSON_BUILD_VARIANT(aj)),
SD_JSON_BUILD_PAIR("imagePolicy", SD_JSON_BUILD_STRING(ps)),
SD_JSON_BUILD_PAIR("imageSize", SD_JSON_BUILD_INTEGER(di->image_size)),
SD_JSON_BUILD_PAIR("sectorSize", SD_JSON_BUILD_INTEGER(di->sector_size)),
SD_JSON_BUILD_PAIR("imageSize", SD_JSON_BUILD_UNSIGNED(di->image_size)),
SD_JSON_BUILD_PAIR("sectorSize", SD_JSON_BUILD_UNSIGNED(di->sector_size)),
SD_JSON_BUILD_PAIR_CONDITION(!sd_id128_is_null(di->image_uuid), "imageUuid", SD_JSON_BUILD_UUID(di->image_uuid)));
}
@ -637,10 +642,16 @@ static MountMapMode default_mount_map_mode(DirectoryOwnership ownership) {
static JSON_DISPATCH_ENUM_DEFINE(dispatch_mount_directory_mode, MountMapMode, mount_map_mode_from_string);
static DirectoryOwnership validate_directory_fd(int fd, uid_t peer_uid) {
static DirectoryOwnership validate_directory_fd(
int fd,
uid_t peer_uid,
uid_t *ret_current_owner_uid) {
int r, fl;
assert(fd >= 0);
assert(uid_is_valid(peer_uid));
assert(ret_current_owner_uid);
/* Checks if the specified directory fd looks sane. Returns a DirectoryOwnership that categorizes the
* ownership situation in comparison to the peer's UID.
@ -665,6 +676,7 @@ static DirectoryOwnership validate_directory_fd(int fd, uid_t peer_uid) {
return log_debug_errno(fl, "Directory file descriptor has unsafe flags set: %m");
if (st.st_uid == 0) {
*ret_current_owner_uid = st.st_uid;
if (peer_uid == 0) {
log_debug("Directory file descriptor points to root owned directory, who is also the peer.");
return DIRECTORY_IS_ROOT_PEER_OWNED;
@ -674,6 +686,7 @@ static DirectoryOwnership validate_directory_fd(int fd, uid_t peer_uid) {
}
if (st.st_uid == peer_uid) {
log_debug("Directory file descriptor points to peer owned directory.");
*ret_current_owner_uid = st.st_uid;
return DIRECTORY_IS_PEER_OWNED;
}
@ -687,12 +700,14 @@ static DirectoryOwnership validate_directory_fd(int fd, uid_t peer_uid) {
/* Stop iteration if we find a directory up the tree that is neither owned by the user, nor is from the foreign UID range */
if (!uid_is_foreign(st.st_uid) || !gid_is_foreign(st.st_gid)) {
log_debug("Directory file descriptor points to directory which itself or its parents is neither owned by foreign UID range nor by the user.");
*ret_current_owner_uid = st.st_uid;
return DIRECTORY_IS_OTHERWISE_OWNED;
}
/* If the peer is root, then it doesn't matter if we find a parent owned by root, let's shortcut things. */
if (peer_uid == 0) {
log_debug("Directory file descriptor is owned by foreign UID range, and peer is root.");
*ret_current_owner_uid = st.st_uid;
return DIRECTORY_IS_FOREIGN_OWNED;
}
@ -708,11 +723,13 @@ static DirectoryOwnership validate_directory_fd(int fd, uid_t peer_uid) {
/* Safety check to see if we hit the root dir */
if (stat_inode_same(&st, &new_st)) {
log_debug("Directory file descriptor is owned by foreign UID range, but didn't find parent directory that is owned by peer among ancestors.");
*ret_current_owner_uid = st.st_uid;
return DIRECTORY_IS_OTHERWISE_OWNED;
}
if (new_st.st_uid == peer_uid) { /* Parent inode is owned by the peer. That's good! Everything's fine. */
log_debug("Directory file descriptor is owned by foreign UID range, and ancestor is owned by peer.");
*ret_current_owner_uid = st.st_uid;
return DIRECTORY_IS_FOREIGN_OWNED;
}
@ -721,6 +738,7 @@ static DirectoryOwnership validate_directory_fd(int fd, uid_t peer_uid) {
}
log_debug("Failed to find peer owned parent directory after %u levels, refusing.", n_level);
*ret_current_owner_uid = st.st_uid;
return DIRECTORY_IS_OTHERWISE_OWNED;
}
@ -771,7 +789,8 @@ static int vl_method_mount_directory(
if (r < 0)
return log_debug_errno(r, "Failed to get client UID: %m");
DirectoryOwnership owned_by = validate_directory_fd(directory_fd, peer_uid);
uid_t current_owner_uid;
DirectoryOwnership owned_by = validate_directory_fd(directory_fd, peer_uid, &current_owner_uid);
if (owned_by == -EREMOTEIO)
return sd_varlink_errorbo(link, "io.systemd.MountFileSystem.BadFileDescriptorFlags", SD_JSON_BUILD_PAIR_STRING("parameter", "directoryFileDescriptor"));
if (owned_by < 0)
@ -836,10 +855,28 @@ static int vl_method_mount_directory(
if (r < 0)
return r;
_cleanup_close_ int mount_fd = open_tree(directory_fd, "", OPEN_TREE_CLONE|OPEN_TREE_CLOEXEC|AT_SYMLINK_NOFOLLOW|AT_EMPTY_PATH);
_cleanup_close_ int mount_fd = open_tree_try_drop_idmap(
directory_fd,
"",
OPEN_TREE_CLONE|OPEN_TREE_CLOEXEC|AT_SYMLINK_NOFOLLOW|AT_EMPTY_PATH);
if (mount_fd < 0)
return log_debug_errno(errno, "Failed to issue open_tree() of provided directory '%s': %m", strna(directory_path));
/* MOUNT_ATTR_IDMAP has possibly been cleared. Let's verify that the underlying data matches our expectations. */
struct stat unmapped_st;
if (fstat(mount_fd, &unmapped_st) < 0)
return log_debug_errno(errno, "Failed to stat unmapped inode: %m");
r = stat_verify_directory(&unmapped_st);
if (r < 0)
return r;
/* For now, let's simply refuse things if dropping the idmapping changed anything. For now that
* should be good enough, because the primary usecase for this (homed) will mount the foreign UID
* range 1:1. */
if (unmapped_st.st_uid != current_owner_uid)
return log_debug_errno(SYNTHETIC_ERRNO(EPERM), "Owner UID of mount after clearing ID mapping not the same anymore, refusing.");
if (p.read_only > 0 && mount_setattr(
mount_fd, "", AT_EMPTY_PATH,
&(struct mount_attr) {

View File

@ -13,6 +13,7 @@
#include "fd-util.h"
#include "fileio.h"
#include "hashmap.h"
#include "hostname-setup.h"
#include "network-common.h"
#include "networkd-address.h"
#include "networkd-dhcp-server.h"
@ -31,6 +32,30 @@
#include "string-util.h"
#include "strv.h"
static int get_hostname_domain(char **ret) {
_cleanup_free_ char *hostname = NULL;
const char *domain;
int r;
assert(ret);
/* Get the full hostname (FQDN if available) */
r = gethostname_full(GET_HOSTNAME_ALLOW_LOCALHOST | GET_HOSTNAME_FALLBACK_DEFAULT, &hostname);
if (r < 0)
return r;
/* Find the first dot to extract the domain part */
domain = strchr(hostname, '.');
if (!domain)
return -ENOENT; /* No domain part in hostname */
domain++; /* Skip the dot */
if (isempty(domain))
return -ENOENT; /* Empty domain after dot */
return strdup_to(ret, domain);
}
static bool link_dhcp4_server_enabled(Link *link) {
assert(link);
@ -678,6 +703,29 @@ static int dhcp4_server_configure(Link *link) {
}
}
if (link->network->dhcp_server_emit_domain) {
_cleanup_free_ char *buffer = NULL;
const char *domain = NULL;
if (link->network->dhcp_server_domain)
domain = link->network->dhcp_server_domain;
else {
r = get_hostname_domain(&buffer);
if (r < 0)
log_link_warning_errno(link, r, "Failed to determine domain name from host's hostname, will not send domain in DHCP leases: %m");
else {
domain = buffer;
log_link_debug(link, "Using autodetected domain name '%s' for DHCP server.", domain);
}
}
if (domain) {
r = sd_dhcp_server_set_domain_name(link->dhcp_server, domain);
if (r < 0)
return log_link_error_errno(link, r, "Failed to set domain name for DHCP server: %m");
}
}
ORDERED_HASHMAP_FOREACH(p, link->network->dhcp_server_send_options) {
r = sd_dhcp_server_add_option(link->dhcp_server, p);
if (r == -EEXIST)

View File

@ -381,6 +381,8 @@ DHCPServer.EmitRouter, config_parse_bool,
DHCPServer.Router, config_parse_in_addr_non_null, AF_INET, offsetof(Network, dhcp_server_router)
DHCPServer.EmitTimezone, config_parse_bool, 0, offsetof(Network, dhcp_server_emit_timezone)
DHCPServer.Timezone, config_parse_timezone, 0, offsetof(Network, dhcp_server_timezone)
DHCPServer.EmitDomain, config_parse_bool, 0, offsetof(Network, dhcp_server_emit_domain)
DHCPServer.Domain, config_parse_dns_name, 0, offsetof(Network, dhcp_server_domain)
DHCPServer.PoolOffset, config_parse_uint32, 0, offsetof(Network, dhcp_server_pool_offset)
DHCPServer.PoolSize, config_parse_uint32, 0, offsetof(Network, dhcp_server_pool_size)
DHCPServer.SendVendorOption, config_parse_dhcp_send_option, 0, offsetof(Network, dhcp_server_send_vendor_options)

View File

@ -755,6 +755,7 @@ static Network *network_free(Network *network) {
free(network->dhcp_server_boot_server_name);
free(network->dhcp_server_boot_filename);
free(network->dhcp_server_timezone);
free(network->dhcp_server_domain);
free(network->dhcp_server_uplink_name);
for (sd_dhcp_lease_server_type_t t = 0; t < _SD_DHCP_LEASE_SERVER_TYPE_MAX; t++)
free(network->dhcp_server_emit[t].addresses);

View File

@ -220,6 +220,8 @@ typedef struct Network {
struct in_addr dhcp_server_router;
bool dhcp_server_emit_timezone;
char *dhcp_server_timezone;
bool dhcp_server_emit_domain;
char *dhcp_server_domain;
usec_t dhcp_server_default_lease_time_usec, dhcp_server_max_lease_time_usec;
uint32_t dhcp_server_pool_offset;
uint32_t dhcp_server_pool_size;

View File

@ -826,17 +826,16 @@ static int mount_bind(const char *dest, CustomMount *m, uid_t uid_shift, uid_t u
* caller's userns *without* any mount idmapping in place. To get that uid, we clone the
* mount source tree and clear any existing idmapping and temporarily mount that tree over
* the mount source before we stat the mount source to figure out the source uid. */
_cleanup_close_ int fd_clone = open_tree_attr_with_fallback(
_cleanup_close_ int fd_clone =
idmapping == REMOUNT_IDMAPPING_NONE ?
RET_NERRNO(open_tree(
AT_FDCWD,
m->source,
open_tree_flags,
&(struct mount_attr) {
.attr_clr = idmapping != REMOUNT_IDMAPPING_NONE ? MOUNT_ATTR_IDMAP : 0,
});
if (ERRNO_IS_NEG_NOT_SUPPORTED(fd_clone))
/* We can only clear idmapped mounts with open_tree_attr(), but there might not be one in
* the first place, so we keep going if we get a not supported error. */
fd_clone = open_tree(AT_FDCWD, m->source, open_tree_flags);
open_tree_flags)) :
open_tree_try_drop_idmap(
AT_FDCWD,
m->source,
open_tree_flags);
if (fd_clone < 0)
return log_error_errno(errno, "Failed to clone %s: %m", m->source);

View File

@ -3830,6 +3830,7 @@ static DissectImageFlags determine_dissect_image_flags(void) {
DISSECT_IMAGE_PIN_PARTITION_DEVICES |
(arg_read_only ? DISSECT_IMAGE_READ_ONLY : DISSECT_IMAGE_FSCK|DISSECT_IMAGE_GROWFS) |
DISSECT_IMAGE_ALLOW_USERSPACE_VERITY |
DISSECT_IMAGE_VERITY_SHARE |
(arg_console_mode == CONSOLE_INTERACTIVE && arg_ask_password ? DISSECT_IMAGE_ALLOW_INTERACTIVE_AUTH : 0) |
((arg_userns_ownership == USER_NAMESPACE_OWNERSHIP_FOREIGN) ? DISSECT_IMAGE_FOREIGN_UID :
(arg_userns_ownership != USER_NAMESPACE_OWNERSHIP_AUTO) ? DISSECT_IMAGE_IDENTITY_UID : 0);

View File

@ -4727,6 +4727,7 @@ int mountfsd_mount_image(
SD_JSON_BUILD_PAIR("readOnly", SD_JSON_BUILD_BOOLEAN(FLAGS_SET(flags, DISSECT_IMAGE_MOUNT_READ_ONLY))),
SD_JSON_BUILD_PAIR("growFileSystems", SD_JSON_BUILD_BOOLEAN(FLAGS_SET(flags, DISSECT_IMAGE_GROWFS))),
SD_JSON_BUILD_PAIR_CONDITION(!!ps, "imagePolicy", SD_JSON_BUILD_STRING(ps)),
SD_JSON_BUILD_PAIR("veritySharing", SD_JSON_BUILD_BOOLEAN(FLAGS_SET(flags, DISSECT_IMAGE_VERITY_SHARE))),
SD_JSON_BUILD_PAIR("allowInteractiveAuthentication", SD_JSON_BUILD_BOOLEAN(FLAGS_SET(flags, DISSECT_IMAGE_ALLOW_INTERACTIVE_AUTH))));
if (r < 0)
return r;

View File

@ -7,8 +7,24 @@
#if HAVE_LIBARCHIVE
static void *libarchive_dl = NULL;
DLSYM_PROTOTYPE(archive_entry_filetype) = NULL;
DLSYM_PROTOTYPE(archive_entry_free) = NULL;
DLSYM_PROTOTYPE(archive_entry_gid) = NULL;
#if HAVE_LIBARCHIVE_UID_IS_SET
DLSYM_PROTOTYPE(archive_entry_gid_is_set) = NULL;
#endif
DLSYM_PROTOTYPE(archive_entry_hardlink) = NULL;
#if HAVE_LIBARCHIVE_HARDLINK_IS_SET
DLSYM_PROTOTYPE(archive_entry_hardlink_is_set) = NULL;
#endif
DLSYM_PROTOTYPE(archive_entry_mode) = NULL;
DLSYM_PROTOTYPE(archive_entry_mtime) = NULL;
DLSYM_PROTOTYPE(archive_entry_mtime_is_set) = NULL;
DLSYM_PROTOTYPE(archive_entry_mtime_nsec) = NULL;
DLSYM_PROTOTYPE(archive_entry_new) = NULL;
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_filetype) = NULL;
DLSYM_PROTOTYPE(archive_entry_set_gid) = NULL;
@ -17,10 +33,24 @@ DLSYM_PROTOTYPE(archive_entry_set_pathname) = NULL;
DLSYM_PROTOTYPE(archive_entry_set_perm) = NULL;
DLSYM_PROTOTYPE(archive_entry_set_rdevmajor) = NULL;
DLSYM_PROTOTYPE(archive_entry_set_rdevminor) = NULL;
DLSYM_PROTOTYPE(archive_entry_set_symlink) = 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_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_next) = NULL;
DLSYM_PROTOTYPE(archive_entry_xattr_reset) = NULL;
DLSYM_PROTOTYPE(archive_error_string) = NULL;
DLSYM_PROTOTYPE(archive_read_data_into_fd) = NULL;
DLSYM_PROTOTYPE(archive_read_free) = NULL;
DLSYM_PROTOTYPE(archive_read_new) = NULL;
DLSYM_PROTOTYPE(archive_read_next_header) = NULL;
DLSYM_PROTOTYPE(archive_read_open_fd) = NULL;
DLSYM_PROTOTYPE(archive_read_support_format_cpio) = NULL;
DLSYM_PROTOTYPE(archive_read_support_format_tar) = NULL;
DLSYM_PROTOTYPE(archive_write_close) = NULL;
DLSYM_PROTOTYPE(archive_write_data) = NULL;
DLSYM_PROTOTYPE(archive_write_free) = NULL;
@ -41,8 +71,24 @@ int dlopen_libarchive(void) {
&libarchive_dl,
"libarchive.so.13",
LOG_DEBUG,
DLSYM_ARG(archive_entry_filetype),
DLSYM_ARG(archive_entry_free),
DLSYM_ARG(archive_entry_gid),
#if HAVE_LIBARCHIVE_UID_IS_SET
DLSYM_ARG(archive_entry_gid_is_set),
#endif
DLSYM_ARG(archive_entry_hardlink),
#if HAVE_LIBARCHIVE_HARDLINK_IS_SET
DLSYM_ARG(archive_entry_hardlink_is_set),
#endif
DLSYM_ARG(archive_entry_mode),
DLSYM_ARG(archive_entry_mtime),
DLSYM_ARG(archive_entry_mtime_is_set),
DLSYM_ARG(archive_entry_mtime_nsec),
DLSYM_ARG(archive_entry_new),
DLSYM_ARG(archive_entry_pathname),
DLSYM_ARG(archive_entry_rdevmajor),
DLSYM_ARG(archive_entry_rdevminor),
DLSYM_ARG(archive_entry_set_ctime),
DLSYM_ARG(archive_entry_set_filetype),
DLSYM_ARG(archive_entry_set_gid),
@ -54,7 +100,21 @@ 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_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_next),
DLSYM_ARG(archive_entry_xattr_reset),
DLSYM_ARG(archive_error_string),
DLSYM_ARG(archive_read_data_into_fd),
DLSYM_ARG(archive_read_free),
DLSYM_ARG(archive_read_new),
DLSYM_ARG(archive_read_next_header),
DLSYM_ARG(archive_read_open_fd),
DLSYM_ARG(archive_read_support_format_cpio),
DLSYM_ARG(archive_read_support_format_tar),
DLSYM_ARG(archive_write_close),
DLSYM_ARG(archive_write_data),
DLSYM_ARG(archive_write_free),
@ -63,7 +123,18 @@ 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_gnutar)
);
}
/* libarchive uses its own file type macros. They happen to be defined the same way as the Linux ones, and
* we'd like to rely on it. Let's verify this first though. */
assert_cc(S_IFDIR == AE_IFDIR);
assert_cc(S_IFREG == AE_IFREG);
assert_cc(S_IFLNK == AE_IFLNK);
assert_cc(S_IFBLK == AE_IFBLK);
assert_cc(S_IFCHR == AE_IFCHR);
assert_cc(S_IFIFO == AE_IFIFO);
assert_cc(S_IFSOCK == AE_IFSOCK);
#endif

View File

@ -9,8 +9,18 @@
#include "dlfcn-util.h"
extern DLSYM_PROTOTYPE(archive_entry_filetype);
extern DLSYM_PROTOTYPE(archive_entry_free);
extern DLSYM_PROTOTYPE(archive_entry_gid);
extern DLSYM_PROTOTYPE(archive_entry_hardlink);
extern DLSYM_PROTOTYPE(archive_entry_mode);
extern DLSYM_PROTOTYPE(archive_entry_mtime);
extern DLSYM_PROTOTYPE(archive_entry_mtime_is_set);
extern DLSYM_PROTOTYPE(archive_entry_mtime_nsec);
extern DLSYM_PROTOTYPE(archive_entry_new);
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_filetype);
extern DLSYM_PROTOTYPE(archive_entry_set_gid);
@ -19,10 +29,21 @@ extern DLSYM_PROTOTYPE(archive_entry_set_pathname);
extern DLSYM_PROTOTYPE(archive_entry_set_perm);
extern DLSYM_PROTOTYPE(archive_entry_set_rdevmajor);
extern DLSYM_PROTOTYPE(archive_entry_set_rdevminor);
extern DLSYM_PROTOTYPE(archive_entry_set_symlink);
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_symlink);
extern DLSYM_PROTOTYPE(archive_entry_uid);
extern DLSYM_PROTOTYPE(archive_entry_xattr_next);
extern DLSYM_PROTOTYPE(archive_entry_xattr_reset);
extern DLSYM_PROTOTYPE(archive_error_string);
extern DLSYM_PROTOTYPE(archive_read_data_into_fd);
extern DLSYM_PROTOTYPE(archive_read_free);
extern DLSYM_PROTOTYPE(archive_read_new);
extern DLSYM_PROTOTYPE(archive_read_next_header);
extern DLSYM_PROTOTYPE(archive_read_open_fd);
extern DLSYM_PROTOTYPE(archive_read_support_format_cpio);
extern DLSYM_PROTOTYPE(archive_read_support_format_tar);
extern DLSYM_PROTOTYPE(archive_write_close);
extern DLSYM_PROTOTYPE(archive_write_data);
extern DLSYM_PROTOTYPE(archive_write_free);
@ -33,10 +54,32 @@ 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);
#if HAVE_LIBARCHIVE_UID_IS_SET
extern DLSYM_PROTOTYPE(archive_entry_gid_is_set);
extern DLSYM_PROTOTYPE(archive_entry_uid_is_set);
#else
#include "user-util.h"
static inline int sym_archive_entry_gid_is_set(struct archive_entry *e) {
return gid_is_valid(sym_archive_entry_gid(e));
}
static inline int sym_archive_entry_uid_is_set(struct archive_entry *e) {
return uid_is_valid(sym_archive_entry_uid(e));
}
#endif
#if HAVE_LIBARCHIVE_HARDLINK_IS_SET
extern DLSYM_PROTOTYPE(archive_entry_hardlink_is_set);
#else
static inline int sym_archive_entry_hardlink_is_set(struct archive_entry *e) {
return !!sym_archive_entry_hardlink(e);
}
#endif
int dlopen_libarchive(void);
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct archive_entry*, sym_archive_entry_free, NULL);
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct archive*, sym_archive_write_free, NULL);
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct archive*, sym_archive_read_free, NULL);
#else

View File

@ -177,6 +177,7 @@ shared_sources = files(
'socket-netlink.c',
'specifier.c',
'switch-root.c',
'tar-util.c',
'tmpfile-util-label.c',
'tomoyo-util.c',
'tpm2-util.c',

View File

@ -1477,7 +1477,7 @@ int make_userns(uid_t uid_shift,
return TAKE_FD(userns_fd);
}
int open_tree_attr_with_fallback(int dir_fd, const char *path, unsigned int flags, struct mount_attr *attr) {
int open_tree_attr_with_fallback(int dir_fd, const char *path, unsigned flags, struct mount_attr *attr) {
_cleanup_close_ int fd = -EBADF;
assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
@ -1507,6 +1507,42 @@ int open_tree_attr_with_fallback(int dir_fd, const char *path, unsigned int flag
return TAKE_FD(fd);
}
int open_tree_try_drop_idmap(int dir_fd, const char *path, unsigned flags) {
/* Tries to drop MOUNT_ATTR_IDMAP while calling open_tree_attr(), but if that doesn't work just uses
* a regular open_tree() */
assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
if (isempty(path)) {
path = "";
flags |= AT_EMPTY_PATH;
}
_cleanup_close_ int fd = open_tree_attr_with_fallback(
dir_fd,
path,
flags,
&(struct mount_attr) {
.attr_clr = MOUNT_ATTR_IDMAP,
});
if (fd < 0) {
if (!ERRNO_IS_NEG_NOT_SUPPORTED(fd))
return log_debug_errno(fd, "Failed to clear idmap of directory with open_tree_attr(): %m");
log_debug_errno(fd, "Failed to clear idmap with open_tree_attr(), retrying open_tree() without clearing idmap: %m");
fd = RET_NERRNO(open_tree(dir_fd, path, flags));
if (fd < 0)
return log_debug_errno(fd, "Both open_tree() and open_tree_attr() failed, giving up: %m");
log_debug("open_tree() without clearing idmap worked.");
return TAKE_FD(fd);
}
log_debug("Successfully acquired mount fd with cleared idmap.");
return TAKE_FD(fd);
}
int remount_idmap_fd(
char **paths,
int userns_fd,

View File

@ -145,7 +145,8 @@ typedef enum RemountIdmapping {
_REMOUNT_IDMAPPING_INVALID = -EINVAL,
} RemountIdmapping;
int open_tree_attr_with_fallback(int dir_fd, const char *path, unsigned int flags, struct mount_attr *attr);
int open_tree_attr_with_fallback(int dir_fd, const char *path, unsigned flags, struct mount_attr *attr);
int open_tree_try_drop_idmap(int dir_fd, const char *path, unsigned flags);
int make_userns(uid_t uid_shift, uid_t uid_range, uid_t host_owner, uid_t dest_owner, RemountIdmapping idmapping);
int remount_idmap_fd(char **p, int userns_fd, uint64_t extra_mount_attr_set);

View File

@ -400,8 +400,8 @@ int nsresource_add_netif_tap(
return log_debug_errno(sd_varlink_error_to_errno(error_id, reply), "Failed to add network to user namespace: %s", error_id);
static const sd_json_dispatch_field dispatch_table[] = {
{ "hostInterfaceName", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(InterfaceParams, host_interface_name), SD_JSON_MANDATORY },
{ "interfaceFileDescriptor", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint, offsetof(InterfaceParams, namespace_interface_name), SD_JSON_MANDATORY },
{ "hostInterfaceName", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(InterfaceParams, host_interface_name), SD_JSON_MANDATORY },
{ "interfaceFileDescriptor", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint, offsetof(InterfaceParams, interface_fd_index), SD_JSON_MANDATORY },
};
_cleanup_(interface_params_done) InterfaceParams p = {};

686
src/shared/tar-util.c Normal file
View File

@ -0,0 +1,686 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "errno-util.h"
#include "log.h"
#include "tar-util.h"
#if HAVE_LIBARCHIVE
#include <sys/sysmacros.h>
#include "alloc-util.h"
#include "chase.h"
#include "fd-util.h"
#include "fs-util.h"
#include "iovec-util.h"
#include "libarchive-util.h"
#include "path-util.h"
#include "stat-util.h"
#include "string-util.h"
#include "tmpfile-util.h"
#include "user-util.h"
#include "xattr-util.h"
#define DEPTH_MAX 128U
typedef struct XAttr {
char *name;
struct iovec data;
} XAttr;
typedef struct OpenInode {
int fd;
char *path;
/* File properties to apply when we are done with the inode, i.e. right before closing it */
mode_t filetype;
mode_t mode;
struct timespec mtime;
uid_t uid;
gid_t gid;
XAttr *xattr;
size_t n_xattr;
} OpenInode;
static void xattr_done(XAttr *xa) {
assert(xa);
free(xa->name);
iovec_done(&xa->data);
}
static void xattr_done_many(XAttr *xa, size_t n) {
assert(xa || n == 0);
FOREACH_ARRAY(i, xa, n)
xattr_done(i);
free(xa);
}
static void open_inode_done(OpenInode *of) {
assert(of);
if (of->path) {
/* Only close the stored fd if the path field is set. We'll set the path to NULL for the root
* inode, and we don't want the fd for that closed, as it's owned by the caller. */
of->fd = safe_close(of->fd);
of->path = mfree(of->path);
}
xattr_done_many(of->xattr, of->n_xattr);
}
static void open_inode_done_many(OpenInode *array, size_t n) {
assert(array || n == 0);
FOREACH_ARRAY(i, array, n)
open_inode_done(i);
free(array);
}
static int open_inode_finalize(OpenInode *of) {
int r = 0;
assert(of);
if (of->fd >= 0) {
int k;
/* We adjust the UID/GID right before the mode, since doing this might affect the mode (drops
* suid/sgid bits).
*
* We adjust the mode only when leaving a dir, because if we are unpriv we might lose the
* ability to enter it once we do this. */
if (uid_is_valid(of->uid) || gid_is_valid(of->gid) || of->mode != MODE_INVALID) {
k = fchmod_and_chown_with_fallback(of->fd, /* path= */ NULL, of->mode, of->uid, of->gid);
if (k < 0)
RET_GATHER(r, log_error_errno(k, "Failed to adjust ownership/mode 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) {
k = futimens_opath(of->fd, (const struct timespec[2]) {
{ .tv_nsec = UTIME_OMIT },
of->mtime,
});
if (k < 0)
RET_GATHER(r, log_error_errno(k, "Failed to adjust mtime of '%s': %m", of->path));
}
/* Setting certain xattrs might cause us to lose access to the inode, hence set this last */
FOREACH_ARRAY(i, of->xattr, of->n_xattr) {
k = xsetxattr_full(
of->fd,
/* path= */ NULL,
AT_EMPTY_PATH,
i->name,
i->data.iov_base,
i->data.iov_len,
/* xattr_flags= */ 0);
if (k < 0)
RET_GATHER(r, log_error_errno(k, "Failed to set xattr '%s' of '%s': %m", i->name, of->path));
}
}
open_inode_done(of); /* free this item even on failure */
return r;
}
static int open_inode_finalize_many(OpenInode **array, size_t *n) {
int r = 0;
assert(array);
assert(n);
assert(*array || *n == 0);
/* Go backwards, so that we adjust innermost first */
for (size_t i = *n; i > 0; i--)
RET_GATHER(r, open_inode_finalize(*array + i - 1));
*array = mfree(*array);
*n = 0;
return r;
}
static int archive_unpack_regular(
struct archive *a,
struct archive_entry *entry,
int parent_fd,
const char *filename,
const char *path) {
int r;
assert(a);
assert(entry);
assert(parent_fd >= 0);
assert(filename);
assert(path);
_cleanup_free_ char *tmp = NULL;
_cleanup_close_ int fd = open_tmpfile_linkable_at(parent_fd, filename, O_CLOEXEC|O_WRONLY, &tmp);
if (fd < 0)
return log_error_errno(fd, "Failed to create regular file '%s': %m", path);
r = sym_archive_read_data_into_fd(a, fd);
if (r != ARCHIVE_OK) {
r = log_error_errno(
SYNTHETIC_ERRNO(ENOTRECOVERABLE),
"Failed to unpack regular file '%s': %s", path, sym_archive_error_string(a));
goto fail;
}
/* If this is a sparse file, then libarchive's archive_read_data_into_fd() won't insert the final
* hole. We need to manually truncate. */
off_t l = lseek(fd, 0, SEEK_CUR);
if (l < 0) {
r = log_error_errno(errno, "Failed to determine current file position in '%s': %m", path);
goto fail;
}
if (ftruncate(fd, l) < 0) {
r = log_error_errno(errno, "Failed to truncate regular file '%s' to %" PRIu64 ": %m", path, (uint64_t) l);
goto fail;
}
r = link_tmpfile_at(fd, parent_fd, tmp, filename, LINK_TMPFILE_REPLACE);
if (r < 0) {
log_error_errno(r, "Failed to install regular file '%s': %m", path);
goto fail;
}
return TAKE_FD(fd);
fail:
if (tmp)
(void) unlinkat(parent_fd, tmp, /* flags= */ 0);
return r;
}
static int archive_unpack_directory(
struct archive *a,
struct archive_entry *entry,
int parent_fd,
const char *filename,
const char *path) {
assert(a);
assert(entry);
assert(parent_fd >= 0);
assert(filename);
assert(path);
/* For the other inode types we operate in an atomic replace fashion, but not for the directories,
* they are more of a "shared" concept, and we try to reuse existing inodes. Note that we create the
* dir inode in mode 0700, so that we can fully access it (but others cannot). We'll adjust the modes
* right before closing the inode. */
_cleanup_close_ int fd = open_mkdir_at(parent_fd, filename, O_CLOEXEC, 0700);
if (fd < 0)
return log_error_errno(fd, "Failed to create directory '%s': %m", path);
return TAKE_FD(fd);
}
static int archive_unpack_symlink(
struct archive *a,
struct archive_entry *entry,
int parent_fd,
const char *filename,
const char *path) {
int r;
assert(a);
assert(entry);
assert(parent_fd >= 0);
assert(filename);
assert(path);
const char *target = sym_archive_entry_symlink(entry);
if (!target)
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to get symlink target for '%s': %m", path);
r = symlinkat_atomic_full(target, parent_fd, filename, /* flags= */ 0);
if (r < 0)
return log_error_errno(r, "Failed to create symlink '%s' → '%s': %m", path, target);
_cleanup_close_ int fd = openat(parent_fd, filename, O_CLOEXEC|O_PATH|O_NOFOLLOW);
if (fd < 0)
return log_error_errno(errno, "Failed to open symlink '%s' we just created: %m", path);
r = fd_verify_symlink(fd);
if (r < 0)
return log_error_errno(r, "Symlink '%s' we just created is not a symlink: %m", path);
return TAKE_FD(fd);
}
static int archive_unpack_special_inode(
struct archive *a,
struct archive_entry *entry,
int parent_fd,
const char *filename,
const char *path,
mode_t filetype) {
int r;
assert(a);
assert(entry);
assert(parent_fd >= 0);
assert(filename);
assert(path);
dev_t major = 0, minor = 0;
if (IN_SET(filetype, S_IFCHR, S_IFBLK)) {
major = sym_archive_entry_rdevmajor(entry);
minor = sym_archive_entry_rdevminor(entry);
}
r = mknodat_atomic(parent_fd, filename, filetype | 0000, makedev(major, minor));
if (r < 0)
return log_error_errno(r, "Failed to create special node '%s': %m", path);
_cleanup_close_ int fd = openat(parent_fd, filename, O_CLOEXEC|O_PATH|O_NOFOLLOW);
if (fd < 0)
return log_error_errno(errno, "Failed to open special node '%s' we just created: %m", path);
struct stat st;
if (fstat(fd, &st) < 0)
return log_error_errno(errno, "Failed to fstat() '%s': %m", path);
if (((st.st_mode ^ filetype) & S_IFMT) != 0)
return log_error_errno(
SYNTHETIC_ERRNO(ENODEV),
"Special node '%s' we just created is of a wrong type: %m", path);
return TAKE_FD(fd);
}
static int archive_entry_pathname_safe(struct archive_entry *entry, const char **ret) {
/* libarchive prefixes all paths with "./", let's chop that off. Note that we'll return a path of
* NULL for the root inode here! */
assert(entry);
assert(ret);
const char *p = sym_archive_entry_pathname(entry);
if (!p)
return -EBADMSG;
const char *e = startswith(p, "./") ?: p;
if (isempty(e))
*ret = NULL;
else if (path_is_safe(e))
*ret = e;
else
return -EBADMSG;
return 0;
}
static int archive_entry_read_stat(
struct archive_entry *entry,
mode_t *filetype,
mode_t *mode,
struct timespec *mtime,
uid_t *uid,
gid_t *gid,
XAttr **xa,
size_t *n_xa,
TarFlags flags) {
assert(entry);
/* Fills in all fields that are present in the archive entry. Doesn't change the fields if the entry
* doesn't contain the relevant data */
if (filetype)
*filetype = sym_archive_entry_filetype(entry);
if (mode)
*mode = sym_archive_entry_mode(entry);
if (mtime && sym_archive_entry_mtime_is_set(entry))
*mtime = (struct timespec) {
sym_archive_entry_mtime(entry),
sym_archive_entry_mtime_nsec(entry),
};
if (uid && sym_archive_entry_uid_is_set(entry))
*uid = sym_archive_entry_uid(entry);
if (gid && sym_archive_entry_gid_is_set(entry))
*gid = sym_archive_entry_gid(entry);
(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)
break;
if (xattr_is_acl(name))
continue;
if (!FLAGS_SET(flags, TAR_SELINUX) && xattr_is_selinux(name))
continue;
_cleanup_free_ char *n = strdup(name);
if (!n)
return log_oom();
_cleanup_(iovec_done) struct iovec iovec_copy = {};
if (!iovec_memdup(&data, &iovec_copy))
return log_oom();
if (!GREEDY_REALLOC(*xa, *n_xa+1))
return log_oom();
(*xa)[(*n_xa)++] = (XAttr) {
.name = TAKE_PTR(n),
.data = TAKE_STRUCT(iovec_copy),
};
}
return 0;
}
int tar_x(int input_fd, int tree_fd, TarFlags flags) {
int ar, r;
assert(input_fd >= 0);
assert(tree_fd >= 0);
_cleanup_(sym_archive_read_freep) struct archive *a = NULL;
a = sym_archive_read_new();
if (!a)
return log_oom();
ar = sym_archive_read_support_format_tar(a);
if (ar != ARCHIVE_OK)
return log_error_errno(
SYNTHETIC_ERRNO(ENOTRECOVERABLE),
"Failed to enable tar unpacking: %s", sym_archive_error_string(a));
ar = sym_archive_read_support_format_cpio(a);
if (ar != ARCHIVE_OK)
return log_error_errno(
SYNTHETIC_ERRNO(ENOTRECOVERABLE),
"Failed to enable cpio unpacking: %s", sym_archive_error_string(a));
ar = sym_archive_read_open_fd(a, input_fd, 64 * 1024);
if (ar != ARCHIVE_OK)
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to initialize archive context: %s", sym_archive_error_string(a));
OpenInode *open_inodes = NULL;
if (!GREEDY_REALLOC(open_inodes, 2)) /* the minimal case is a single file in an archive, which would
* mean two inodes, the root dir inode, and he regular file
* inode, hence start with 2 here */
return log_oom();
size_t n_open_inodes = 0;
CLEANUP_ARRAY(open_inodes, n_open_inodes, open_inode_done_many);
/* Fill in the root inode. (Note: we leave the .path field as NULL to mark it as root inode.) */
open_inodes[0] = (OpenInode) {
.fd = tree_fd,
.filetype = S_IFDIR,
.mode = MODE_INVALID,
.mtime = { .tv_nsec = UTIME_OMIT },
.uid = UID_INVALID,
.gid = GID_INVALID,
};
n_open_inodes = 1;
for (;;) {
struct archive_entry *entry = NULL;
ar = sym_archive_read_next_header(a, &entry);
if (ar == ARCHIVE_EOF)
break;
if (ar != ARCHIVE_OK)
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Failed to parse archive: %s", sym_archive_error_string(a));
const char *p = NULL;
r = archive_entry_pathname_safe(entry, &p);
if (r < 0)
return log_error_errno(r, "Invalid path name in entry, refusing.");
if (!p) {
/* This is the root inode */
r = archive_entry_read_stat(
entry,
&open_inodes[0].filetype,
&open_inodes[0].mode,
&open_inodes[0].mtime,
&open_inodes[0].uid,
&open_inodes[0].gid,
&open_inodes[0].xattr,
&open_inodes[0].n_xattr,
flags);
if (r < 0)
return r;
if (open_inodes[0].filetype != S_IFDIR)
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Archives root inode is not a directory, refusing.");
continue;
}
/* Find common prefix with path elements we were looking at so far. */
const char *rest = p;
size_t i;
for (i = 1; i < n_open_inodes; i++) {
const char *e = path_startswith(p, open_inodes[i].path);
if (isempty(e))
break;
rest = e;
}
/* Finalize all inodes we won't need anymore now (go backwards, i.e. close inner fds first) */
while (n_open_inodes > i) {
r = open_inode_finalize(open_inodes + n_open_inodes - 1);
if (r < 0)
return r;
n_open_inodes--;
}
/* And now create all remaining components */
for (;;) {
const char *element;
r = path_find_first_component(&rest, /* accept_dot_dot= */ false, &element);
if (r < 0)
return log_error_errno(r, "Failed to extract next element from path: %m");
if (r == 0)
break;
/* Safety check, before we add another level to our stack */
if (n_open_inodes >= DEPTH_MAX)
return log_error_errno(
SYNTHETIC_ERRNO(E2BIG),
"Archive's directory tree nested too deeply, refusing to descend more than %u levels.", DEPTH_MAX);
_cleanup_free_ char *e = strndup(element, r);
if (!e)
return log_oom();
const char *parent_path = NULL;
int parent_fd = -EBADF;
assert(n_open_inodes > 0);
parent_fd = open_inodes[n_open_inodes-1].fd;
parent_path = open_inodes[n_open_inodes-1].path;
_cleanup_free_ char *j = parent_path ? path_join(parent_path, e) : strdup(e);
if (!j)
return log_oom();
if (!GREEDY_REALLOC(open_inodes, n_open_inodes+1))
return log_oom();
_cleanup_close_ int fd = -EBADF;
mode_t filetype = MODE_INVALID;
mode_t mode = MODE_INVALID;
uid_t uid = UID_INVALID;
gid_t gid = GID_INVALID;
struct timespec mtime = { .tv_nsec = UTIME_OMIT };
XAttr *xa = NULL;
size_t n_xa = 0;
CLEANUP_ARRAY(xa, n_xa, xattr_done_many);
if (isempty(rest)) {
/* This is the final node in the path, create it */
if (sym_archive_entry_hardlink_is_set(entry)) {
/* If this is a hardlink, act on it */
const char *h = sym_archive_entry_hardlink(entry);
if (!h)
return log_error_errno(
SYNTHETIC_ERRNO(EBADMSG),
"No hardlink target in hardlink entry, refusing.");
/* libarchive prefixes all paths with "./", let's chop that off */
const char *target = startswith(h, "./") ?: h;
if (!path_is_safe(target))
return log_error_errno(
SYNTHETIC_ERRNO(EBADMSG),
"Invalid hardlink path name '%s' in entry, refusing.", target);
_cleanup_close_ int target_fd = -EBADF;
r = chaseat(tree_fd, target, CHASE_PROHIBIT_SYMLINKS|CHASE_AT_RESOLVE_IN_ROOT, /* ret_path= */ NULL, &target_fd);
if (r < 0)
return log_error_errno(
r,
"Failed to find inode '%s' which shall be hardlinked as '%s': %m", target, j);
struct stat verify_st;
if (fstat(target_fd, &verify_st) < 0)
return log_error_errno(errno, "Failed to stat inode '%s': %m", target);
/* Refuse hardlinking directories early. */
if (!inode_type_can_hardlink(verify_st.st_mode))
return log_error_errno(
SYNTHETIC_ERRNO(EBADF),
"Refusing to hardlink inode '%s' of type '%s': %m", target, inode_type_to_string(verify_st.st_mode));
if (linkat(target_fd, "", parent_fd, e, AT_EMPTY_PATH) < 0) {
if (errno != ENOENT)
return log_error_errno(
errno,
"Failed to hardlink inode '%s' as '%s': %m", target, j);
/* To be able to link by inode fd we might have needed
* CAP_DAC_READ_SEARCH which we lacked. Let's retry with the
* parent. Yes, glibc/kernel report this as ENOENT. Kinda
* annoying. */
_cleanup_close_ int target_parent_fd = -EBADF;
_cleanup_free_ char *target_filename = NULL;
r = chaseat(tree_fd, target, CHASE_PROHIBIT_SYMLINKS|CHASE_AT_RESOLVE_IN_ROOT|CHASE_PARENT|CHASE_EXTRACT_FILENAME, &target_filename, &target_parent_fd);
if (r < 0)
return log_error_errno(
r,
"Failed to find inode '%s' which shall be hardlinked as '%s': %m", target, j);
if (linkat(target_parent_fd, target_filename, parent_fd, e, /* flags= */ 0) < 0)
return log_error_errno(
errno,
"Failed to hardlink inode '%s' as '%s': %m", target, j);
}
continue;
}
r = archive_entry_read_stat(
entry,
&filetype,
&mode,
&mtime,
&uid,
&gid,
&xa,
&n_xa,
flags);
if (r < 0)
return r;
switch (filetype) {
case S_IFREG:
fd = archive_unpack_regular(a, entry, parent_fd, e, j);
break;
case S_IFDIR:
fd = archive_unpack_directory(a, entry, parent_fd, e, j);
break;
case S_IFLNK:
fd = archive_unpack_symlink(a, entry, parent_fd, e, j);
break;
case S_IFCHR:
case S_IFBLK:
case S_IFIFO:
case S_IFSOCK:
fd = archive_unpack_special_inode(a, entry, parent_fd, e, j, filetype);
break;
default:
return log_error_errno(
SYNTHETIC_ERRNO(ENOTRECOVERABLE),
"Unexpected file type %i of '%s', refusing.", (int) filetype, j);
}
if (fd < 0)
return fd;
} else {
/* This is some intermediary node in the path that we haven't opened yet. Create it with default attributes */
fd = open_mkdir_at(parent_fd, e, O_CLOEXEC, 0700);
if (fd < 0)
return log_error_errno(fd, "Failed to create directory '%s': %m", j);
filetype = S_IFDIR;
}
/* Now store a reference to the inode we just created in our stack array. Note that
* we have not applied file ownership, access mode, mtime here, we'll do that only
* when we are finished with the inode, since we have to apply them *after* we are
* fully done with the inode (i.e. after creating further inodes inside of dir inodes
* for example), due to permission problems this might create or that the mtime
* changes we do might still be affected by our changes. */
open_inodes[n_open_inodes++] = (OpenInode) {
.fd = TAKE_FD(fd),
.path = TAKE_PTR(j),
.filetype = filetype,
.mode = mode,
.mtime = mtime,
.uid = uid,
.gid = gid,
.xattr = TAKE_PTR(xa),
.n_xattr = n_xa,
};
n_xa = 0;
}
}
r = open_inode_finalize_many(&open_inodes, &n_open_inodes);
if (r < 0)
return r;
return 0;
}
#else
int tar_x(int input_fd, int tree_fd, TarFlags flags) {
assert(input_fd >= 0);
assert(tree_fd >= 0);
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "libarchive support not available.");
}
#endif

8
src/shared/tar-util.h Normal file
View File

@ -0,0 +1,8 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
typedef enum TarFlags {
TAR_SELINUX = 1 << 0,
} TarFlags;
int tar_x(int input_fd, int tree_fd, TarFlags flags);

View File

@ -60,6 +60,8 @@ static SD_VARLINK_DEFINE_METHOD(
SD_VARLINK_DEFINE_INPUT(password, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Takes an image policy string (see systemd.image-policy(7) for details) to apply while mounting the image"),
SD_VARLINK_DEFINE_INPUT(imagePolicy, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Whether to automatically reuse already set up dm-verity devices that share the same roothash."),
SD_VARLINK_DEFINE_INPUT(veritySharing, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE),
VARLINK_DEFINE_POLKIT_INPUT,
SD_VARLINK_FIELD_COMMENT("An array with information about contained partitions that have been prepared for mounting, as well as their mount file descriptors."),
SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(partitions, PartitionInfo, SD_VARLINK_ARRAY),

View File

@ -61,6 +61,7 @@ int sd_dhcp_server_set_boot_server_name(sd_dhcp_server *server, const char *name
int sd_dhcp_server_set_boot_filename(sd_dhcp_server *server, const char *filename);
int sd_dhcp_server_set_bind_to_interface(sd_dhcp_server *server, int enabled);
int sd_dhcp_server_set_timezone(sd_dhcp_server *server, const char *timezone);
int sd_dhcp_server_set_domain_name(sd_dhcp_server *server, const char *domain_name);
int sd_dhcp_server_set_router(sd_dhcp_server *server, const struct in_addr *address);
int sd_dhcp_server_set_servers(

View File

@ -29,9 +29,16 @@ ProtectHostname=yes
RestrictAddressFamilies=AF_UNIX AF_NETLINK AF_INET AF_INET6
RestrictRealtime=yes
RestrictSUIDSGID=yes
SystemCallArchitectures=native
SystemCallErrorNumber=EPERM
SystemCallFilter=@system-service @mount
# FIXME: libseccomp (as of 2.6.0) doesn't know the open_tree_attr() system call
# yet which we need. Thus, applying any system call filter would block this
# system call unconditionally, which is not desirable. This should be added
# back once libseccomp is updated. See
# https://github.com/seccomp/libseccomp/issues/465
#SystemCallArchitectures=native
#SystemCallErrorNumber=EPERM
#SystemCallFilter=@system-service @mount
Type=notify
NotifyAccess=all
FileDescriptorStoreMax=4096