mirror of
https://github.com/systemd/systemd
synced 2025-11-20 09:14:46 +01:00
Compare commits
14 Commits
aad0d11e7c
...
de2276cdcd
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
de2276cdcd | ||
|
|
9dd61cfbe7 | ||
|
|
3eb7b881bd | ||
|
|
4cae0e9a78 | ||
|
|
9f69ff69f7 | ||
|
|
0d87de0b8e | ||
|
|
566a4bbbbf | ||
|
|
ce7a5d6026 | ||
|
|
a9b1e35a32 | ||
|
|
d29a2cd2d4 | ||
|
|
a7c8f92d1f | ||
|
|
8be204df2b | ||
|
|
90fae0b46c | ||
|
|
b2fa6d0945 |
@ -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>
|
||||
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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");
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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]);
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -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, ¤t_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) {
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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
686
src/shared/tar-util.c
Normal 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
8
src/shared/tar-util.h
Normal 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);
|
||||
@ -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),
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user