1
0
mirror of https://github.com/systemd/systemd synced 2026-04-10 17:15:03 +02:00

Compare commits

..

No commits in common. "d62ab43fd0ddb077514f8294ac5345d78e1a7ef3" and "e80c5eb6e63c394fd90c2d8cf454ee649e59ae21" have entirely different histories.

64 changed files with 2335 additions and 3371 deletions

View File

@ -459,73 +459,6 @@
<xi:include href="version-info.xml" xpointer="v256"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--bind-user=</option></term>
<listitem><para>Binds the home directory of the specified user on the host into the virtual
machine. Takes the name of an existing user on the host as argument. May be used multiple times to
bind multiple users into the virtual machine. This does two things:</para>
<orderedlist>
<listitem><para>The user's home directory is made available from the host into
<filename>/run/vmhost/home/</filename> using virtiofs. virtiofsd id translation to map the host
user's UID/GID to its assigned UID/GID in the virtual machine.</para></listitem>
<listitem><para>JSON user and group records are generated in that describes the mapped user which
are passed into the virtual machine using <literal>userdb.transient.*</literal> credentials.
They contain a minimized representation of the host's user record, adjusted to the UID/GID and
home directory path assigned to the user in the virtual machine. The
<citerefentry><refentrytitle>nss-systemd</refentrytitle><manvolnum>8</manvolnum></citerefentry>
glibc NSS module will pick up these records from there and make them available in the virtual
machine's user/group databases.</para></listitem>
</orderedlist>
<para>The combination of the two operations above ensures that it is possible to log into the
virtual machine using the same account information as on the host. The user is only mapped
transiently, while the virtual machine is running, and the mapping itself does not result in
persistent changes to the virtual machine (except maybe for log messages generated at login time,
and similar). Note that in particular the UID/GID assignment in the virtual machine is not made
persistently. If the user is mapped transiently, it is best to not allow the user to make
persistent changes to the virtual machine. If the user leaves files or directories owned by the
user, and those UIDs/GIDs are reused during later virtual machine invocations (possibly with a
different <option>--bind-user=</option> mapping), those files and directories will be accessible to
the "new" user.</para>
<para>The user/group record mapping only works if the virtual machine contains systemd 258 or
newer, with <command>nss-systemd</command> properly configured in
<filename>nsswitch.conf</filename>. See
<citerefentry><refentrytitle>nss-systemd</refentrytitle><manvolnum>8</manvolnum></citerefentry> for
details.</para>
<para>Note that the user record propagated from the host into the virtual machine will contain the
UNIX password hash of the user, so that seamless logins in the virtual machine are possible. If the
virtual machine is less trusted than the host it is hence important to use a strong UNIX password
hash function (e.g. yescrypt or similar, with the <literal>$y$</literal> hash prefix).</para>
<xi:include href="version-info.xml" xpointer="v259"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--bind-user-shell=</option></term>
<listitem><para>When used with <option>--bind-user=</option>, includes the specified shell in the
user records of users bound into the virtual machine. Takes either a boolean or an absolute path.</para>
<itemizedlist>
<listitem><para>If false (the default), no shell is passed in the user records for users bound into
the virtual machine. This causes bound users to the use the virtual machine's default shell.</para></listitem>
<listitem><para>If true, the shells specified by the host user records are included in the user records of all users bound into the virtual machine.</para></listitem>
<listitem><para>If passed an absolute path, sets that path as the shell for user records of all users bound into the virtual machine.</para></listitem>
</itemizedlist>
<para>Note: This will not check whether the specified shells exist in the virtual machine.</para>
<para>This operation is only supported in combination with <option>--bind-user=</option>.</para>
<xi:include href="version-info.xml" xpointer="v259"/></listitem>
</varlistentry>
</variablelist>
</refsect2>

View File

@ -5,5 +5,5 @@ Environment=
GIT_URL=https://salsa.debian.org/systemd-team/systemd.git
GIT_SUBDIR=debian
GIT_BRANCH=debian/master
GIT_COMMIT=5650452e6b0b430f44d3d48b7322c2b3c8b9477f
GIT_COMMIT=e50fce1d4b2a9f1bb990027de8e86603f3b42301
PKG_SUBDIR=debian

View File

@ -4,13 +4,40 @@
#include <stdlib.h>
#include "assert-util.h"
#include "env-util.h"
#include "errno-util.h"
#include "log.h"
static bool assert_return_is_critical = BUILD_MODE_DEVELOPER;
/* Akin to glibc's __abort_msg; which is private and we hence cannot
* use here. */
static char *log_abort_msg = NULL;
void log_set_assert_return_is_critical(bool b) {
assert_return_is_critical = b;
}
void log_set_assert_return_is_critical_from_env(void) {
static int cached = INT_MIN;
int r;
if (cached == INT_MIN) {
r = secure_getenv_bool("SYSTEMD_ASSERT_RETURN_IS_CRITICAL");
if (r < 0 && r != -ENXIO)
log_debug_errno(r, "Failed to parse $SYSTEMD_ASSERT_RETURN_IS_CRITICAL, ignoring: %m");
cached = r;
}
if (cached >= 0)
log_set_assert_return_is_critical(cached);
}
bool log_get_assert_return_is_critical(void) {
return assert_return_is_critical;
}
static void log_assert(
int level,
const char *text,
@ -46,8 +73,8 @@ _noreturn_ void log_assert_failed_unreachable(const char *file, int line, const
}
void log_assert_failed_return(const char *text, const char *file, int line, const char *func) {
/* log_get_assert_return_is_critical is a weak symbol. It may be NULL. */
if (log_get_assert_return_is_critical && log_get_assert_return_is_critical())
if (assert_return_is_critical)
log_assert_failed(text, file, line, func);
PROTECT_ERRNO;

View File

@ -5,7 +5,9 @@
/* Logging for various assertions */
bool log_get_assert_return_is_critical(void) _weak_ _pure_;
void log_set_assert_return_is_critical(bool b);
void log_set_assert_return_is_critical_from_env(void);
bool log_get_assert_return_is_critical(void) _pure_;
void log_assert_failed_return(const char *text, const char *file, int line, const char *func);

View File

@ -22,6 +22,6 @@ static struct mempool pool_name = { \
.at_least = alloc_at_least, \
}
bool mempool_enabled(void) _weak_ _pure_;
__attribute__((weak)) bool mempool_enabled(void);
void mempool_trim(struct mempool *mp);

View File

@ -366,18 +366,6 @@
send_interface="org.freedesktop.systemd1.Manager"
send_member="SetShowStatus"/>
<allow send_destination="org.freedesktop.systemd1"
send_interface="org.freedesktop.systemd1.Manager"
send_member="SetEnvironment"/>
<allow send_destination="org.freedesktop.systemd1"
send_interface="org.freedesktop.systemd1.Manager"
send_member="UnsetEnvironment"/>
<allow send_destination="org.freedesktop.systemd1"
send_interface="org.freedesktop.systemd1.Manager"
send_member="UnsetAndSetEnvironment"/>
<!-- Managed via polkit or other criteria: org.freedesktop.systemd1.Job interface -->
<allow send_destination="org.freedesktop.systemd1"

View File

@ -1,73 +0,0 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "sd-journal.h"
#include "sd-messages.h"
#include "coredump-backtrace.h"
#include "coredump-context.h"
#include "iovec-util.h"
#include "journal-importer.h"
#include "log.h"
#include "string-util.h"
int coredump_backtrace(int argc, char *argv[]) {
_cleanup_(journal_importer_cleanup) JournalImporter importer = JOURNAL_IMPORTER_INIT(STDIN_FILENO);
_cleanup_(iovw_free_freep) struct iovec_wrapper *iovw = NULL;
_cleanup_(context_done) Context context = CONTEXT_NULL;
int r;
assert(argc >= 2);
log_debug("Processing backtrace on stdin...");
iovw = iovw_new();
if (!iovw)
return log_oom();
(void) iovw_put_string_field(iovw, "MESSAGE_ID=", SD_MESSAGE_BACKTRACE_STR);
(void) iovw_put_string_field(iovw, "PRIORITY=", STRINGIFY(LOG_CRIT));
/* Collect all process metadata from argv[] by making sure to skip the '--backtrace' option. */
r = gather_pid_metadata_from_argv(iovw, &context, argc - 2, argv + 2);
if (r < 0)
return r;
/* Collect the rest of the process metadata retrieved from the runtime */
r = gather_pid_metadata_from_procfs(iovw, &context);
if (r < 0)
return r;
for (;;) {
r = journal_importer_process_data(&importer);
if (r < 0)
return log_error_errno(r, "Failed to parse journal entry on stdin: %m");
if (r == 1 || /* complete entry */
journal_importer_eof(&importer)) /* end of data */
break;
}
if (journal_importer_eof(&importer)) {
log_warning("Did not receive a full journal entry on stdin, ignoring message sent by reporter.");
const char *message = strjoina("Process ", context.meta[META_ARGV_PID],
" (", context.meta[META_COMM], ")"
" of user ", context.meta[META_ARGV_UID],
" failed with ", context.meta[META_ARGV_SIGNAL]);
r = iovw_put_string_field(iovw, "MESSAGE=", message);
if (r < 0)
return r;
} else {
/* The imported iovecs are not supposed to be freed by us so let's copy and merge them at the
* end of the array. */
r = iovw_append(iovw, &importer.iovw);
if (r < 0)
return r;
}
r = sd_journal_sendv(iovw->iovec, iovw->count);
if (r < 0)
return log_error_errno(r, "Failed to log backtrace: %m");
return 0;
}

View File

@ -1,4 +0,0 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
int coredump_backtrace(int argc, char *argv[]);

View File

@ -1,83 +0,0 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "conf-parser.h"
#include "coredump-config.h"
#include "format-util.h"
#include "journal-importer.h"
#include "log.h"
#include "string-table.h"
#include "string-util.h"
/* Make sure to not make this larger than the maximum journal entry
* size. See DATA_SIZE_MAX in journal-importer.h. */
assert_cc(JOURNAL_SIZE_MAX <= DATA_SIZE_MAX);
CoredumpStorage arg_storage = COREDUMP_STORAGE_EXTERNAL;
bool arg_compress = true;
uint64_t arg_process_size_max = PROCESS_SIZE_MAX;
uint64_t arg_external_size_max = EXTERNAL_SIZE_MAX;
uint64_t arg_journal_size_max = JOURNAL_SIZE_MAX;
uint64_t arg_keep_free = UINT64_MAX;
uint64_t arg_max_use = UINT64_MAX;
bool arg_enter_namespace = false;
static const char* const coredump_storage_table[_COREDUMP_STORAGE_MAX] = {
[COREDUMP_STORAGE_NONE] = "none",
[COREDUMP_STORAGE_EXTERNAL] = "external",
[COREDUMP_STORAGE_JOURNAL] = "journal",
};
DEFINE_PRIVATE_STRING_TABLE_LOOKUP(coredump_storage, CoredumpStorage);
static DEFINE_CONFIG_PARSE_ENUM(config_parse_coredump_storage, coredump_storage, CoredumpStorage);
int coredump_parse_config(void) {
static const ConfigTableItem items[] = {
{ "Coredump", "Storage", config_parse_coredump_storage, 0, &arg_storage },
{ "Coredump", "Compress", config_parse_bool, 0, &arg_compress },
{ "Coredump", "ProcessSizeMax", config_parse_iec_uint64, 0, &arg_process_size_max },
{ "Coredump", "ExternalSizeMax", config_parse_iec_uint64_infinity, 0, &arg_external_size_max },
{ "Coredump", "JournalSizeMax", config_parse_iec_size, 0, &arg_journal_size_max },
{ "Coredump", "KeepFree", config_parse_iec_uint64, 0, &arg_keep_free },
{ "Coredump", "MaxUse", config_parse_iec_uint64, 0, &arg_max_use },
#if HAVE_DWFL_SET_SYSROOT
{ "Coredump", "EnterNamespace", config_parse_bool, 0, &arg_enter_namespace },
#else
{ "Coredump", "EnterNamespace", config_parse_warn_compat, DISABLED_CONFIGURATION, NULL },
#endif
{}
};
int r;
r = config_parse_standard_file_with_dropins(
"systemd/coredump.conf",
"Coredump\0",
config_item_table_lookup,
items,
CONFIG_PARSE_WARN,
/* userdata= */ NULL);
if (r < 0)
return r;
/* Let's make sure we fix up the maximum size we send to the journal here on the client side, for
* efficiency reasons. journald wouldn't accept anything larger anyway. */
if (arg_journal_size_max > JOURNAL_SIZE_MAX) {
log_warning("JournalSizeMax= set to larger value (%s) than journald would accept (%s), lowering automatically.",
FORMAT_BYTES(arg_journal_size_max), FORMAT_BYTES(JOURNAL_SIZE_MAX));
arg_journal_size_max = JOURNAL_SIZE_MAX;
}
log_debug("Selected storage '%s'.", coredump_storage_to_string(arg_storage));
log_debug("Selected compression %s.", yes_no(arg_compress));
return 0;
}
uint64_t coredump_storage_size_max(void) {
if (arg_storage == COREDUMP_STORAGE_EXTERNAL)
return arg_external_size_max;
if (arg_storage == COREDUMP_STORAGE_JOURNAL)
return arg_journal_size_max;
assert(arg_storage == COREDUMP_STORAGE_NONE);
return 0;
}

View File

@ -1,44 +0,0 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "basic-forward.h"
/* The maximum size up to which we process coredumps. We use 1G on 32-bit systems, and 32G on 64-bit systems */
#if __SIZEOF_POINTER__ == 4
#define PROCESS_SIZE_MAX ((uint64_t) (1LLU*1024LLU*1024LLU*1024LLU))
#elif __SIZEOF_POINTER__ == 8
#define PROCESS_SIZE_MAX ((uint64_t) (32LLU*1024LLU*1024LLU*1024LLU))
#else
#error "Unexpected pointer size"
#endif
/* The maximum size up to which we leave the coredump around on disk */
#define EXTERNAL_SIZE_MAX PROCESS_SIZE_MAX
/* The maximum size up to which we store the coredump in the journal */
#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
#define JOURNAL_SIZE_MAX ((size_t) (767LU*1024LU*1024LU))
#else
/* oss-fuzz limits memory usage. */
#define JOURNAL_SIZE_MAX ((size_t) (10LU*1024LU*1024LU))
#endif
typedef enum CoredumpStorage {
COREDUMP_STORAGE_NONE,
COREDUMP_STORAGE_EXTERNAL,
COREDUMP_STORAGE_JOURNAL,
_COREDUMP_STORAGE_MAX,
_COREDUMP_STORAGE_INVALID = -EINVAL,
} CoredumpStorage;
extern CoredumpStorage arg_storage;
extern bool arg_compress;
extern uint64_t arg_process_size_max;
extern uint64_t arg_external_size_max;
extern uint64_t arg_journal_size_max;
extern uint64_t arg_keep_free;
extern uint64_t arg_max_use;
extern bool arg_enter_namespace;
int coredump_parse_config(void);
uint64_t coredump_storage_size_max(void);

View File

@ -1,454 +0,0 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "sd-login.h"
#include "coredump-config.h"
#include "coredump-context.h"
#include "coredump-util.h"
#include "dirent-util.h"
#include "fd-util.h"
#include "fs-util.h"
#include "iovec-wrapper.h"
#include "log.h"
#include "memstream-util.h"
#include "namespace-util.h"
#include "parse-util.h"
#include "process-util.h"
#include "signal-util.h"
#include "special.h"
#include "string-util.h"
#include "user-util.h"
const char * const meta_field_names[_META_MAX] = {
[META_ARGV_PID] = "COREDUMP_PID=",
[META_ARGV_UID] = "COREDUMP_UID=",
[META_ARGV_GID] = "COREDUMP_GID=",
[META_ARGV_SIGNAL] = "COREDUMP_SIGNAL=",
[META_ARGV_TIMESTAMP] = "COREDUMP_TIMESTAMP=",
[META_ARGV_RLIMIT] = "COREDUMP_RLIMIT=",
[META_ARGV_HOSTNAME] = "COREDUMP_HOSTNAME=",
[META_ARGV_DUMPABLE] = "COREDUMP_DUMPABLE=",
[META_ARGV_PIDFD] = "COREDUMP_BY_PIDFD=",
[META_COMM] = "COREDUMP_COMM=",
[META_EXE] = "COREDUMP_EXE=",
[META_UNIT] = "COREDUMP_UNIT=",
[META_PROC_AUXV] = "COREDUMP_PROC_AUXV=",
};
void context_done(Context *c) {
assert(c);
pidref_done(&c->pidref);
c->mount_tree_fd = safe_close(c->mount_tree_fd);
}
/* Joins /proc/[pid]/fd/ and /proc/[pid]/fdinfo/ into the following lines:
* 0:/dev/pts/23
* pos: 0
* flags: 0100002
*
* 1:/dev/pts/23
* pos: 0
* flags: 0100002
*
* 2:/dev/pts/23
* pos: 0
* flags: 0100002
* EOF
*/
static int compose_open_fds(pid_t pid, char **ret) {
_cleanup_(memstream_done) MemStream m = {};
_cleanup_closedir_ DIR *proc_fd_dir = NULL;
_cleanup_close_ int proc_fdinfo_fd = -EBADF;
const char *fddelim = "", *path;
FILE *stream;
int r;
assert(pid >= 0);
assert(ret);
path = procfs_file_alloca(pid, "fd");
proc_fd_dir = opendir(path);
if (!proc_fd_dir)
return -errno;
proc_fdinfo_fd = openat(dirfd(proc_fd_dir), "../fdinfo", O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC|O_PATH);
if (proc_fdinfo_fd < 0)
return -errno;
stream = memstream_init(&m);
if (!stream)
return -ENOMEM;
FOREACH_DIRENT(de, proc_fd_dir, return -errno) {
_cleanup_fclose_ FILE *fdinfo = NULL;
_cleanup_free_ char *fdname = NULL;
_cleanup_close_ int fd = -EBADF;
r = readlinkat_malloc(dirfd(proc_fd_dir), de->d_name, &fdname);
if (r < 0)
return r;
fprintf(stream, "%s%s:%s\n", fddelim, de->d_name, fdname);
fddelim = "\n";
/* Use the directory entry from /proc/[pid]/fd with /proc/[pid]/fdinfo */
fd = openat(proc_fdinfo_fd, de->d_name, O_NOFOLLOW|O_CLOEXEC|O_RDONLY);
if (fd < 0)
continue;
fdinfo = take_fdopen(&fd, "r");
if (!fdinfo)
continue;
for (;;) {
_cleanup_free_ char *line = NULL;
r = read_line(fdinfo, LONG_LINE_MAX, &line);
if (r < 0)
return r;
if (r == 0)
break;
fputs(line, stream);
fputc('\n', stream);
}
}
return memstream_finalize(&m, ret, NULL);
}
/* Returns 1 if the parent was found.
* Returns 0 if there is not a process we can call the pid's
* container parent (the pid's process isn't 'containerized').
* Returns a negative number on errors.
*/
static int get_process_container_parent_cmdline(PidRef *pid, char** ret_cmdline) {
int r;
assert(pidref_is_set(pid));
assert(!pidref_is_remote(pid));
r = pidref_from_same_root_fs(pid, &PIDREF_MAKE_FROM_PID(1));
if (r < 0)
return r;
if (r > 0) {
/* The process uses system root. */
*ret_cmdline = NULL;
return 0;
}
_cleanup_(pidref_done) PidRef container_pid = PIDREF_NULL;
r = namespace_get_leader(pid, NAMESPACE_MOUNT, &container_pid);
if (r < 0)
return r;
r = pidref_get_cmdline(&container_pid, SIZE_MAX, PROCESS_CMDLINE_QUOTE_POSIX, ret_cmdline);
if (r < 0)
return r;
return 1;
}
int gather_pid_metadata_from_procfs(struct iovec_wrapper *iovw, Context *context) {
char *t;
size_t size;
int r;
assert(iovw);
assert(context);
/* Note that if we fail on oom later on, we do not roll-back changes to the iovec
* structure. (It remains valid, with the first iovec fields initialized.) */
pid_t pid = context->pidref.pid;
/* The following is mandatory */
r = pidref_get_comm(&context->pidref, &t);
if (r < 0)
return log_error_errno(r, "Failed to get COMM: %m");
r = iovw_put_string_field_free(iovw, "COREDUMP_COMM=", t);
if (r < 0)
return r;
/* The following are optional, but we use them if present. */
r = get_process_exe(pid, &t);
if (r >= 0)
r = iovw_put_string_field_free(iovw, "COREDUMP_EXE=", t);
if (r < 0)
log_warning_errno(r, "Failed to get EXE, ignoring: %m");
if (cg_pidref_get_unit(&context->pidref, &t) >= 0)
(void) iovw_put_string_field_free(iovw, "COREDUMP_UNIT=", t);
if (cg_pidref_get_user_unit(&context->pidref, &t) >= 0)
(void) iovw_put_string_field_free(iovw, "COREDUMP_USER_UNIT=", t);
if (cg_pidref_get_session(&context->pidref, &t) >= 0)
(void) iovw_put_string_field_free(iovw, "COREDUMP_SESSION=", t);
uid_t owner_uid;
if (cg_pidref_get_owner_uid(&context->pidref, &owner_uid) >= 0) {
r = asprintf(&t, UID_FMT, owner_uid);
if (r > 0)
(void) iovw_put_string_field_free(iovw, "COREDUMP_OWNER_UID=", t);
}
if (sd_pid_get_slice(pid, &t) >= 0)
(void) iovw_put_string_field_free(iovw, "COREDUMP_SLICE=", t);
if (pidref_get_cmdline(&context->pidref, SIZE_MAX, PROCESS_CMDLINE_QUOTE_POSIX, &t) >= 0)
(void) iovw_put_string_field_free(iovw, "COREDUMP_CMDLINE=", t);
if (cg_pid_get_path_shifted(pid, NULL, &t) >= 0)
(void) iovw_put_string_field_free(iovw, "COREDUMP_CGROUP=", t);
if (compose_open_fds(pid, &t) >= 0)
(void) iovw_put_string_field_free(iovw, "COREDUMP_OPEN_FDS=", t);
if (read_full_file(procfs_file_alloca(pid, "status"), &t, /* ret_size= */ NULL) >= 0)
(void) iovw_put_string_field_free(iovw, "COREDUMP_PROC_STATUS=", t);
if (read_full_file(procfs_file_alloca(pid, "maps"), &t, /* ret_size= */ NULL) >= 0)
(void) iovw_put_string_field_free(iovw, "COREDUMP_PROC_MAPS=", t);
if (read_full_file(procfs_file_alloca(pid, "limits"), &t, /* ret_size= */ NULL) >= 0)
(void) iovw_put_string_field_free(iovw, "COREDUMP_PROC_LIMITS=", t);
if (read_full_file(procfs_file_alloca(pid, "cgroup"), &t, /* ret_size= */ NULL) >= 0)
(void) iovw_put_string_field_free(iovw, "COREDUMP_PROC_CGROUP=", t);
if (read_full_file(procfs_file_alloca(pid, "mountinfo"), &t, /* ret_size= */ NULL) >= 0)
(void) iovw_put_string_field_free(iovw, "COREDUMP_PROC_MOUNTINFO=", t);
/* We attach /proc/auxv here. ELF coredumps also contain a note for this (NT_AUXV), see elf(5). */
if (read_full_file(procfs_file_alloca(pid, "auxv"), &t, &size) >= 0) {
char *buf = malloc(strlen("COREDUMP_PROC_AUXV=") + size + 1);
if (buf) {
/* Add a dummy terminator to make context_parse_iovw() happy. */
*mempcpy_typesafe(stpcpy(buf, "COREDUMP_PROC_AUXV="), t, size) = '\0';
(void) iovw_consume(iovw, buf, size + strlen("COREDUMP_PROC_AUXV="));
}
free(t);
}
if (get_process_cwd(pid, &t) >= 0)
(void) iovw_put_string_field_free(iovw, "COREDUMP_CWD=", t);
if (get_process_root(pid, &t) >= 0) {
bool proc_self_root_is_slash;
proc_self_root_is_slash = strcmp(t, "/") == 0;
(void) iovw_put_string_field_free(iovw, "COREDUMP_ROOT=", t);
/* If the process' root is "/", then there is a chance it has
* mounted own root and hence being containerized. */
if (proc_self_root_is_slash && get_process_container_parent_cmdline(&context->pidref, &t) > 0)
(void) iovw_put_string_field_free(iovw, "COREDUMP_CONTAINER_CMDLINE=", t);
}
if (get_process_environ(pid, &t) >= 0)
(void) iovw_put_string_field_free(iovw, "COREDUMP_ENVIRON=", t);
/* Now that we have parsed info from /proc/ ensure the pidfd is still valid before continuing. */
r = pidref_verify(&context->pidref);
if (r < 0)
return log_error_errno(r, "PIDFD validation failed: %m");
/* We successfully acquired all metadata. */
return context_parse_iovw(context, iovw);
}
int context_parse_iovw(Context *context, struct iovec_wrapper *iovw) {
const char *unit;
int r;
assert(context);
assert(iovw);
/* Converts the data in the iovec array iovw into separate fields. Fills in context->meta[] (for
* which no memory is allocated, it just contains direct pointers into the iovec array memory). */
bool have_signal_name = false;
FOREACH_ARRAY(iovec, iovw->iovec, iovw->count) {
/* Note that these strings are NUL-terminated, because we made sure that a trailing NUL byte
* is in the buffer, though not included in the iov_len count. See coredump_receive() and
* gather_pid_metadata_*(). */
assert(((char*) iovec->iov_base)[iovec->iov_len] == 0);
for (size_t i = 0; i < ELEMENTSOF(meta_field_names); i++) {
const char *p = memory_startswith(iovec->iov_base, iovec->iov_len, meta_field_names[i]);
if (p) {
context->meta[i] = p;
context->meta_size[i] = iovec->iov_len - strlen(meta_field_names[i]);
break;
}
}
have_signal_name = have_signal_name ||
memory_startswith(iovec->iov_base, iovec->iov_len, "COREDUMP_SIGNAL_NAME=");
}
/* The basic fields from argv[] should always be there, refuse early if not. */
for (int i = 0; i < _META_ARGV_REQUIRED; i++)
if (!context->meta[i])
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"A required (%s) has not been sent, aborting.", meta_field_names[i]);
pid_t parsed_pid;
r = parse_pid(context->meta[META_ARGV_PID], &parsed_pid);
if (r < 0)
return log_error_errno(r, "Failed to parse PID \"%s\": %m", context->meta[META_ARGV_PID]);
if (pidref_is_set(&context->pidref)) {
if (context->pidref.pid != parsed_pid)
return log_error_errno(r, "Passed PID " PID_FMT " does not match passed " PID_FMT ": %m",
parsed_pid, context->pidref.pid);
} else {
r = pidref_set_pid(&context->pidref, parsed_pid);
if (r < 0)
return log_error_errno(r, "Failed to initialize pidref from pid " PID_FMT ": %m", parsed_pid);
}
r = parse_uid(context->meta[META_ARGV_UID], &context->uid);
if (r < 0)
return log_error_errno(r, "Failed to parse UID \"%s\": %m", context->meta[META_ARGV_UID]);
r = parse_gid(context->meta[META_ARGV_GID], &context->gid);
if (r < 0)
return log_error_errno(r, "Failed to parse GID \"%s\": %m", context->meta[META_ARGV_GID]);
r = parse_signo(context->meta[META_ARGV_SIGNAL], &context->signo);
if (r < 0)
log_warning_errno(r, "Failed to parse signal number \"%s\", ignoring: %m", context->meta[META_ARGV_SIGNAL]);
r = safe_atou64(context->meta[META_ARGV_RLIMIT], &context->rlimit);
if (r < 0)
log_warning_errno(r, "Failed to parse resource limit \"%s\", ignoring: %m", context->meta[META_ARGV_RLIMIT]);
/* The value is set to contents of /proc/sys/fs/suid_dumpable, which we set to SUID_DUMP_SAFE (2),
* if the process is marked as not dumpable, see PR_SET_DUMPABLE(2const). */
if (context->meta[META_ARGV_DUMPABLE]) {
r = safe_atou(context->meta[META_ARGV_DUMPABLE], &context->dumpable);
if (r < 0)
return log_error_errno(r, "Failed to parse dumpable field \"%s\": %m", context->meta[META_ARGV_DUMPABLE]);
if (context->dumpable > SUID_DUMP_SAFE)
log_notice("Got unexpected %%d/dumpable value %u.", context->dumpable);
}
unit = context->meta[META_UNIT];
context->is_pid1 = streq(context->meta[META_ARGV_PID], "1") || streq_ptr(unit, SPECIAL_INIT_SCOPE);
context->is_journald = streq_ptr(unit, SPECIAL_JOURNALD_SERVICE);
/* After parsing everything, let's also synthesize a new iovw field for the textual signal name if it
* isn't already set. */
if (SIGNAL_VALID(context->signo) && !have_signal_name)
(void) iovw_put_string_field(iovw, "COREDUMP_SIGNAL_NAME=SIG", signal_to_string(context->signo));
return 0;
}
int gather_pid_metadata_from_argv(
struct iovec_wrapper *iovw,
Context *context,
int argc, char **argv) {
_cleanup_(pidref_done) PidRef local_pidref = PIDREF_NULL;
int r, kernel_fd = -EBADF;
assert(iovw);
assert(context);
/* We gather all metadata that were passed via argv[] into an array of iovecs that
* we'll forward to the socket unit.
*
* We require at least _META_ARGV_REQUIRED args, but will accept more.
* We know how to parse _META_ARGV_MAX args. The rest will be ignored. */
if (argc < _META_ARGV_REQUIRED)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Not enough arguments passed by the kernel (%i, expected between %i and %i).",
argc, _META_ARGV_REQUIRED, _META_ARGV_MAX);
for (int i = 0; i < MIN(argc, _META_ARGV_MAX); i++) {
_cleanup_free_ char *buf = NULL;
const char *t = argv[i];
if (i == META_ARGV_TIMESTAMP) {
/* The journal fields contain the timestamp padded with six
* zeroes, so that the kernel-supplied 1s granularity timestamps
* becomes 1μs granularity, i.e. the granularity systemd usually
* operates in. */
buf = strjoin(argv[i], "000000");
if (!buf)
return log_oom();
t = buf;
}
if (i == META_ARGV_PID) {
/* Store this so that we can check whether the core will be forwarded to a container
* even when the kernel doesn't provide a pidfd. Can be dropped once baseline is
* >= v6.16. */
r = pidref_set_pidstr(&local_pidref, t);
if (r < 0)
return log_error_errno(r, "Failed to initialize pidref from pid %s: %m", t);
}
if (i == META_ARGV_PIDFD) {
/* If the current kernel doesn't support the %F specifier (which resolves to a
* pidfd), but we included it in the core_pattern expression, we'll receive an empty
* string here. Deal with that gracefully. */
if (isempty(t))
continue;
assert(!pidref_is_set(&context->pidref));
assert(kernel_fd < 0);
kernel_fd = parse_fd(t);
if (kernel_fd < 0)
return log_error_errno(kernel_fd, "Failed to parse pidfd \"%s\": %m", t);
r = pidref_set_pidfd(&context->pidref, kernel_fd);
if (r < 0)
return log_error_errno(r, "Failed to initialize pidref from pidfd %d: %m", kernel_fd);
context->got_pidfd = 1;
/* If there are containers involved with different versions of the code they might
* not be using pidfds, so it would be wrong to set the metadata, skip it. */
r = pidref_in_same_namespace(/* pid1 = */ NULL, &context->pidref, NAMESPACE_PID);
if (r < 0)
log_debug_errno(r, "Failed to check pidns of crashing process, ignoring: %m");
if (r <= 0)
continue;
/* We don't print the fd number in the journal as it's meaningless, but we still
* record that the parsing was done with a kernel-provided fd as it means it's safe
* from races, which is valuable information to provide in the journal record. */
t = "1";
}
r = iovw_put_string_field(iovw, meta_field_names[i], t);
if (r < 0)
return r;
}
/* Cache some of the process metadata we collected so far and that we'll need to
* access soon. */
r = context_parse_iovw(context, iovw);
if (r < 0)
return r;
/* If the kernel didn't give us a PIDFD, then use the one derived from the
* PID immediately, given we have it. */
if (!pidref_is_set(&context->pidref))
context->pidref = TAKE_PIDREF(local_pidref);
/* Close the kernel-provided FD as the last thing after everything else succeeded. */
kernel_fd = safe_close(kernel_fd);
return 0;
}

View File

@ -1,75 +0,0 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "coredump-forward.h"
#include "pidref.h"
typedef enum {
/* We use these as array indexes for our process metadata cache.
*
* The first indices of the cache stores the same metadata as the ones passed by the kernel via
* argv[], i.e. the strings specified in our pattern defined in /proc/sys/kernel/core_pattern,
* see core(5). */
META_ARGV_PID, /* %P: as seen in the initial pid namespace */
META_ARGV_UID, /* %u: as seen in the initial user namespace */
META_ARGV_GID, /* %g: as seen in the initial user namespace */
META_ARGV_SIGNAL, /* %s: number of signal causing dump */
META_ARGV_TIMESTAMP, /* %t: time of dump, expressed as seconds since the Epoch (we expand this to μs granularity) */
META_ARGV_RLIMIT, /* %c: core file size soft resource limit */
_META_ARGV_REQUIRED,
/* The fields below were added to kernel/core_pattern at later points, so they might be missing. */
META_ARGV_HOSTNAME = _META_ARGV_REQUIRED, /* %h: hostname */
META_ARGV_DUMPABLE, /* %d: as set by the kernel */
META_ARGV_PIDFD, /* %F: pidfd of the process, since v6.16 */
/* If new fields are added, they should be added here, to maintain compatibility
* with callers which don't know about the new fields. */
_META_ARGV_MAX,
/* The following indexes are cached for a couple of special fields we use (and
* thereby need to be retrieved quickly) for naming coredump files, and attaching
* xattrs. Unlike the previous ones they are retrieved from the runtime
* environment. */
META_COMM = _META_ARGV_MAX,
/* The rest are similar to the previous ones except that we won't fail if one of
* them is missing in a message sent over the socket. */
META_EXE,
META_UNIT,
META_PROC_AUXV,
_META_MAX
} meta_argv_t;
extern const char * const meta_field_names[_META_MAX];
struct Context {
PidRef pidref;
uid_t uid;
gid_t gid;
unsigned dumpable;
int signo;
uint64_t rlimit;
bool is_pid1;
bool is_journald;
bool got_pidfd;
int mount_tree_fd;
/* These point into external memory, are not owned by this object */
const char *meta[_META_MAX];
size_t meta_size[_META_MAX];
};
#define CONTEXT_NULL \
(Context) { \
.pidref = PIDREF_NULL, \
.uid = UID_INVALID, \
.gid = GID_INVALID, \
.mount_tree_fd = -EBADF, \
}
void context_done(Context *c);
int context_parse_iovw(Context *context, struct iovec_wrapper *iovw);
int gather_pid_metadata_from_argv(struct iovec_wrapper *iovw, Context *context, int argc, char **argv);
int gather_pid_metadata_from_procfs(struct iovec_wrapper *iovw, Context *context);

View File

@ -1,6 +0,0 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "basic-forward.h"
typedef struct Context Context;

View File

@ -1,87 +0,0 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "sd-messages.h"
#include "coredump-context.h"
#include "coredump-kernel-helper.h"
#include "coredump-send.h"
#include "coredump-submit.h"
#include "coredump-util.h"
#include "fd-util.h"
#include "iovec-wrapper.h"
#include "log.h"
#include "namespace-util.h"
#include "signal-util.h"
int coredump_kernel_helper(int argc, char *argv[]) {
_cleanup_(iovw_free_freep) struct iovec_wrapper *iovw = NULL;
_cleanup_(context_done) Context context = CONTEXT_NULL;
int r;
/* When we're invoked by the kernel, stdout/stderr are closed which is dangerous because the fds
* could get reallocated. To avoid hard to debug issues, let's instead bind stdout/stderr to
* /dev/null. */
r = rearrange_stdio(STDIN_FILENO, -EBADF, -EBADF);
if (r < 0)
return log_error_errno(r, "Failed to connect stdout/stderr to /dev/null: %m");
log_debug("Processing coredump received from the kernel...");
iovw = iovw_new();
if (!iovw)
return log_oom();
/* Collect all process metadata passed by the kernel through argv[] */
r = gather_pid_metadata_from_argv(iovw, &context, argc - 1, argv + 1);
if (r < 0)
return r;
/* Collect the rest of the process metadata retrieved from the runtime */
r = gather_pid_metadata_from_procfs(iovw, &context);
if (r < 0)
return r;
if (!context.is_journald)
/* OK, now we know it's not the journal, hence we can make use of it now. */
log_set_target_and_open(LOG_TARGET_JOURNAL_OR_KMSG);
/* Log minimal metadata now, so it is not lost if the system is about to shut down. */
log_info("Process %s (%s) of user %s terminated abnormally with signal %s/%s, processing...",
context.meta[META_ARGV_PID], context.meta[META_COMM],
context.meta[META_ARGV_UID], context.meta[META_ARGV_SIGNAL],
signal_to_string(context.signo));
r = pidref_in_same_namespace(/* pid1 = */ NULL, &context.pidref, NAMESPACE_PID);
if (r < 0)
log_debug_errno(r, "Failed to check pidns of crashing process, ignoring: %m");
if (r == 0) {
/* If this fails, fallback to the old behavior so that
* there is still some record of the crash. */
r = coredump_send_to_container(&context);
if (r >= 0)
return 0;
r = acquire_pid_mount_tree_fd(&context, &context.mount_tree_fd);
if (r < 0)
log_warning_errno(r, "Failed to access the mount tree of a container, ignoring: %m");
}
/* If this is PID 1, disable coredump collection, we'll unlikely be able to process
* it later on.
*
* FIXME: maybe we should disable coredumps generation from the beginning and
* re-enable it only when we know it's either safe (i.e. we're not running OOM) or
* it's not PID 1 ? */
if (context.is_pid1) {
log_notice("Due to PID 1 having crashed coredump collection will now be turned off.");
disable_coredumps();
}
(void) iovw_put_string_field(iovw, "MESSAGE_ID=", SD_MESSAGE_COREDUMP_STR);
(void) iovw_put_string_field(iovw, "PRIORITY=", STRINGIFY(LOG_CRIT));
if (context.is_journald || context.is_pid1)
return coredump_submit(&context, iovw, STDIN_FILENO);
return coredump_send(iovw, STDIN_FILENO, &context.pidref, context.mount_tree_fd);
}

View File

@ -1,4 +0,0 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
int coredump_kernel_helper(int argc, char *argv[]);

View File

@ -1,162 +0,0 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <stdlib.h>
#include "coredump-context.h"
#include "coredump-receive.h"
#include "coredump-submit.h"
#include "iovec-util.h"
#include "iovec-wrapper.h"
#include "fd-util.h"
#include "log.h"
#include "socket-util.h"
int coredump_receive(int fd) {
_cleanup_(iovw_done_free) struct iovec_wrapper iovw = {};
_cleanup_(context_done) Context context = CONTEXT_NULL;
_cleanup_close_ int input_fd = -EBADF;
enum {
STATE_PAYLOAD,
STATE_INPUT_FD_DONE,
STATE_PID_FD_DONE,
} state = STATE_PAYLOAD;
int r;
assert(fd >= 0);
log_setup();
log_debug("Processing coredump received via socket...");
for (;;) {
CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(int))) control;
struct msghdr mh = {
.msg_control = &control,
.msg_controllen = sizeof(control),
.msg_iovlen = 1,
};
ssize_t n, l;
l = next_datagram_size_fd(fd);
if (l < 0)
return log_error_errno(l, "Failed to determine datagram size to read: %m");
_cleanup_(iovec_done) struct iovec iovec = {
.iov_len = l,
.iov_base = malloc(l + 1),
};
if (!iovec.iov_base)
return log_oom();
mh.msg_iov = &iovec;
n = recvmsg_safe(fd, &mh, MSG_CMSG_CLOEXEC);
if (n < 0)
return log_error_errno(n, "Failed to receive datagram: %m");
/* The final zero-length datagrams ("sentinels") carry file descriptors and tell us that
* we're done. There are three sentinels: one with just the coredump fd, followed by one with
* the pidfd, and finally one with the mount tree fd. The latter two or the last one may be
* omitted (which is supported for compatibility with older systemd version, in particular to
* facilitate cross-container coredumping). */
if (n == 0) {
struct cmsghdr *found;
found = cmsg_find(&mh, SOL_SOCKET, SCM_RIGHTS, CMSG_LEN(sizeof(int)));
if (!found) {
/* This is zero length message but it either doesn't carry a single
* descriptor, or it has more than one. This is a protocol violation so let's
* bail out.
*
* Well, not quite! In practice there's one more complication: EOF on
* SOCK_SEQPACKET is not distinguishable from a zero length datagram. Hence
* if we get a zero length datagram without fds we consider it EOF, and
* that's permissible for the final two fds. Hence let's be strict on the
* first fd, but lenient on the other two. */
if (!cmsg_find(&mh, SOL_SOCKET, SCM_RIGHTS, (socklen_t) -1) && state != STATE_PAYLOAD)
/* No fds, and already got the first fd → we are done. */
break;
cmsg_close_all(&mh);
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG),
"Received zero length message with zero or more than one file descriptor(s), expected one.");
}
switch (state) {
case STATE_PAYLOAD:
assert(input_fd < 0);
input_fd = *CMSG_TYPED_DATA(found, int);
state = STATE_INPUT_FD_DONE;
continue;
case STATE_INPUT_FD_DONE:
assert(!pidref_is_set(&context.pidref));
r = pidref_set_pidfd_consume(&context.pidref, *CMSG_TYPED_DATA(found, int));
if (r < 0)
return log_error_errno(r, "Failed to initialize pidref: %m");
state = STATE_PID_FD_DONE;
continue;
case STATE_PID_FD_DONE:
assert(context.mount_tree_fd < 0);
context.mount_tree_fd = *CMSG_TYPED_DATA(found, int);
/* We have all FDs we need so we are done. */
break;
}
break;
}
cmsg_close_all(&mh);
/* Only zero length messages are allowed after the first message that carried a file descriptor. */
if (state != STATE_PAYLOAD)
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Received unexpected message with non-zero length.");
/* Payload messages should not carry fds */
if (cmsg_find(&mh, SOL_SOCKET, SCM_RIGHTS, (socklen_t) -1))
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG),
"Received payload message with file descriptor(s), expected none.");
/* Add trailing NUL byte, in case these are strings */
((char*) iovec.iov_base)[n] = 0;
iovec.iov_len = (size_t) n;
if (iovw_put(&iovw, iovec.iov_base, iovec.iov_len) < 0)
return log_oom();
TAKE_STRUCT(iovec);
}
/* Make sure we got all data we really need */
assert(input_fd >= 0);
r = context_parse_iovw(&context, &iovw);
if (r < 0)
return r;
/* Make sure we received all the expected fields. We support being called by an *older*
* systemd-coredump from the outside, so we require only the basic set of fields that
* was being sent when the support for sending to containers over a socket was added
* in a108c43e36d3ceb6e34efe37c014fc2cda856000. */
meta_argv_t i;
FOREACH_ARGUMENT(i,
META_ARGV_PID,
META_ARGV_UID,
META_ARGV_GID,
META_ARGV_SIGNAL,
META_ARGV_TIMESTAMP,
META_ARGV_RLIMIT,
META_ARGV_HOSTNAME,
META_COMM)
if (!context.meta[i])
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Mandatory argument %s not received on socket, aborting.",
meta_field_names[i]);
return coredump_submit(&context, &iovw, input_fd);
}

View File

@ -1,4 +0,0 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
int coredump_receive(int fd);

View File

@ -1,334 +0,0 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <unistd.h>
#include "sd-messages.h"
#include "coredump-context.h"
#include "coredump-send.h"
#include "coredump-util.h"
#include "errno-util.h"
#include "fd-util.h"
#include "fs-util.h"
#include "iovec-util.h"
#include "iovec-wrapper.h"
#include "log.h"
#include "namespace-util.h"
#include "path-util.h"
#include "pidref.h"
#include "process-util.h"
#include "socket-util.h"
int coredump_send(const struct iovec_wrapper *iovw, int input_fd, PidRef *pidref, int mount_tree_fd) {
_cleanup_close_ int fd = -EBADF;
int r;
assert(iovw);
assert(input_fd >= 0);
fd = socket(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0);
if (fd < 0)
return log_error_errno(errno, "Failed to create coredump socket: %m");
r = connect_unix_path(fd, AT_FDCWD, "/run/systemd/coredump");
if (r < 0)
return log_error_errno(r, "Failed to connect to coredump service: %m");
for (size_t i = 0; i < iovw->count; i++) {
struct msghdr mh = {
.msg_iov = iovw->iovec + i,
.msg_iovlen = 1,
};
struct iovec copy[2];
for (;;) {
if (sendmsg(fd, &mh, MSG_NOSIGNAL) >= 0)
break;
if (errno == EMSGSIZE && mh.msg_iov[0].iov_len > 0) {
/* This field didn't fit? That's a pity. Given that this is
* just metadata, let's truncate the field at half, and try
* again. We append three dots, in order to show that this is
* truncated. */
if (mh.msg_iov != copy) {
/* We don't want to modify the caller's iovec, hence
* let's create our own array, consisting of two new
* iovecs, where the first is a (truncated) copy of
* what we want to send, and the second one contains
* the trailing dots. */
copy[0] = iovw->iovec[i];
copy[1] = IOVEC_MAKE(((const char[]){'.', '.', '.'}), 3);
mh.msg_iov = copy;
mh.msg_iovlen = 2;
}
copy[0].iov_len /= 2; /* halve it, and try again */
continue;
}
return log_error_errno(errno, "Failed to send coredump datagram: %m");
}
}
/* First sentinel: the coredump fd */
r = send_one_fd(fd, input_fd, 0);
if (r < 0)
return log_error_errno(r, "Failed to send coredump fd: %m");
/* The optional second sentinel: the pidfd */
if (!pidref_is_set(pidref) || pidref->fd < 0) /* If we have no pidfd, stop now */
return 0;
r = send_one_fd(fd, pidref->fd, 0);
if (r < 0)
return log_error_errno(r, "Failed to send pidfd: %m");
/* The optional third sentinel: the mount tree fd */
if (mount_tree_fd < 0) /* If we have no mount tree, stop now */
return 0;
r = send_one_fd(fd, mount_tree_fd, 0);
if (r < 0)
return log_error_errno(r, "Failed to send mount tree fd: %m");
return 0;
}
static int can_forward_coredump(Context *context, const PidRef *pid) {
_cleanup_free_ char *cgroup = NULL, *path = NULL, *unit = NULL;
int r;
assert(context);
assert(pidref_is_set(pid));
assert(!pidref_is_remote(pid));
/* We need to avoid a situation where the attacker crashes a SUID process or a root daemon and
* quickly replaces it with a namespaced process and we forward the coredump to the attacker, into
* the namespace. With %F/pidfd we can reliably check the namespace of the original process, hence we
* can allow forwarding. */
if (!context->got_pidfd && context->dumpable != SUID_DUMP_USER)
return false;
r = cg_pidref_get_path(SYSTEMD_CGROUP_CONTROLLER, pid, &cgroup);
if (r < 0)
return r;
r = path_extract_directory(cgroup, &path);
if (r < 0)
return r;
r = cg_path_get_unit_path(path, &unit);
if (r == -ENOMEM)
return log_oom();
if (r == -ENXIO)
/* No valid units in this path. */
return false;
if (r < 0)
return r;
/* We require that this process belongs to a delegated cgroup
* (i.e. Delegate=yes), with CoredumpReceive=yes also. */
r = cg_is_delegated(unit);
if (r <= 0)
return r;
return cg_has_coredump_receive(unit);
}
static int send_ucred(int transport_fd, const struct ucred *ucred) {
CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(struct ucred))) control = {};
struct msghdr mh = {
.msg_control = &control,
.msg_controllen = sizeof(control),
};
struct cmsghdr *cmsg;
assert(transport_fd >= 0);
assert(ucred);
cmsg = CMSG_FIRSTHDR(&mh);
*cmsg = (struct cmsghdr) {
.cmsg_level = SOL_SOCKET,
.cmsg_type = SCM_CREDENTIALS,
.cmsg_len = CMSG_LEN(sizeof(struct ucred)),
};
memcpy(CMSG_DATA(cmsg), ucred, sizeof(struct ucred));
return RET_NERRNO(sendmsg(transport_fd, &mh, MSG_NOSIGNAL));
}
static int receive_ucred(int transport_fd, struct ucred *ret_ucred) {
CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(struct ucred))) control = {};
struct msghdr mh = {
.msg_control = &control,
.msg_controllen = sizeof(control),
};
struct cmsghdr *cmsg = NULL;
struct ucred *ucred = NULL;
ssize_t n;
assert(transport_fd >= 0);
assert(ret_ucred);
n = recvmsg_safe(transport_fd, &mh, 0);
if (n < 0)
return n;
CMSG_FOREACH(cmsg, &mh)
if (cmsg->cmsg_level == SOL_SOCKET &&
cmsg->cmsg_type == SCM_CREDENTIALS &&
cmsg->cmsg_len == CMSG_LEN(sizeof(struct ucred))) {
assert(!ucred);
ucred = CMSG_TYPED_DATA(cmsg, struct ucred);
}
if (!ucred)
return -EIO;
*ret_ucred = *ucred;
return 0;
}
int coredump_send_to_container(Context *context) {
_cleanup_close_ int pidnsfd = -EBADF, mntnsfd = -EBADF, netnsfd = -EBADF, usernsfd = -EBADF, rootfd = -EBADF;
_cleanup_close_pair_ int pair[2] = EBADF_PAIR;
pid_t child;
struct ucred ucred = {
.pid = context->pidref.pid,
.uid = context->uid,
.gid = context->gid,
};
int r;
assert(context);
_cleanup_(pidref_done) PidRef leader_pid = PIDREF_NULL;
r = namespace_get_leader(&context->pidref, NAMESPACE_PID, &leader_pid);
if (r < 0)
return log_debug_errno(r, "Failed to get namespace leader: %m");
r = can_forward_coredump(context, &leader_pid);
if (r < 0)
return log_debug_errno(r, "Failed to check if coredump can be forwarded: %m");
if (r == 0)
return log_debug_errno(SYNTHETIC_ERRNO(ENOENT),
"Coredump will not be forwarded because no target cgroup was found.");
r = RET_NERRNO(socketpair(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, pair));
if (r < 0)
return log_debug_errno(r, "Failed to create socket pair: %m");
r = setsockopt_int(pair[1], SOL_SOCKET, SO_PASSCRED, true);
if (r < 0)
return log_debug_errno(r, "Failed to set SO_PASSCRED: %m");
r = pidref_namespace_open(&leader_pid, &pidnsfd, &mntnsfd, &netnsfd, &usernsfd, &rootfd);
if (r < 0)
return log_debug_errno(r, "Failed to open namespaces of PID " PID_FMT ": %m", leader_pid.pid);
r = namespace_fork("(sd-coredumpns)", "(sd-coredump)", NULL, 0,
FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM,
pidnsfd, mntnsfd, netnsfd, usernsfd, rootfd, &child);
if (r < 0)
return log_debug_errno(r, "Failed to fork into namespaces of PID " PID_FMT ": %m", leader_pid.pid);
if (r == 0) {
pair[0] = safe_close(pair[0]);
r = access_nofollow("/run/systemd/coredump", W_OK);
if (r < 0) {
log_debug_errno(r, "Cannot find coredump socket, exiting: %m");
_exit(EXIT_FAILURE);
}
r = receive_ucred(pair[1], &ucred);
if (r < 0) {
log_debug_errno(r, "Failed to receive ucred and fd: %m");
_exit(EXIT_FAILURE);
}
_cleanup_(iovw_free_freep) struct iovec_wrapper *iovw = iovw_new();
if (!iovw) {
log_oom();
_exit(EXIT_FAILURE);
}
(void) iovw_put_string_field(iovw, "MESSAGE_ID=", SD_MESSAGE_COREDUMP_STR);
(void) iovw_put_string_field(iovw, "PRIORITY=", STRINGIFY(LOG_CRIT));
(void) iovw_put_string_field(iovw, "COREDUMP_FORWARDED=", "1");
for (int i = 0; i < _META_ARGV_MAX; i++) {
char buf[DECIMAL_STR_MAX(pid_t)];
const char *t = context->meta[i];
/* Patch some of the fields with the translated ucred data */
switch (i) {
case META_ARGV_PID:
xsprintf(buf, PID_FMT, ucred.pid);
t = buf;
break;
case META_ARGV_UID:
xsprintf(buf, UID_FMT, ucred.uid);
t = buf;
break;
case META_ARGV_GID:
xsprintf(buf, GID_FMT, ucred.gid);
t = buf;
break;
default:
;
}
r = iovw_put_string_field(iovw, meta_field_names[i], t);
if (r < 0) {
log_debug_errno(r, "Failed to construct iovec: %m");
_exit(EXIT_FAILURE);
}
}
_cleanup_(context_done) Context child_context = CONTEXT_NULL;
r = context_parse_iovw(&child_context, iovw);
if (r < 0) {
log_debug_errno(r, "Failed to save context: %m");
_exit(EXIT_FAILURE);
}
r = gather_pid_metadata_from_procfs(iovw, &child_context);
if (r < 0) {
log_debug_errno(r, "Failed to gather metadata from procfs: %m");
_exit(EXIT_FAILURE);
}
r = coredump_send(iovw, STDIN_FILENO, &context->pidref, /* mount_tree_fd= */ -EBADF);
if (r < 0) {
log_debug_errno(r, "Failed to send iovec to coredump socket: %m");
_exit(EXIT_FAILURE);
}
_exit(EXIT_SUCCESS);
}
pair[1] = safe_close(pair[1]);
/* We need to translate the PID, UID, and GID of the crashing process
* to the container's namespaces. Do this by sending an SCM_CREDENTIALS
* message on a socket pair, and read the result when we join the
* container. The kernel will perform the translation for us. */
r = send_ucred(pair[0], &ucred);
if (r < 0)
return log_debug_errno(r, "Failed to send metadata to container: %m");
r = wait_for_terminate_and_check("(sd-coredumpns)", child, 0);
if (r < 0)
return log_debug_errno(r, "Failed to wait for child to terminate: %m");
if (r != EXIT_SUCCESS)
return log_debug_errno(SYNTHETIC_ERRNO(EPROTO), "Failed to process coredump in container.");
return 0;
}

View File

@ -1,7 +0,0 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "coredump-forward.h"
int coredump_send(const struct iovec_wrapper *iovw, int input_fd, PidRef *pidref, int mount_tree_fd);
int coredump_send_to_container(Context *context);

View File

@ -1,798 +0,0 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <elf.h>
#include <stdio.h>
#include <sys/mount.h>
#include <sys/statvfs.h>
#include <sys/xattr.h>
#include <unistd.h>
#include "sd-bus.h"
#include "sd-id128.h"
#include "sd-journal.h"
#include "sd-messages.h"
#include "acl-util.h"
#include "bus-error.h"
#include "capability-util.h"
#include "compress.h"
#include "copy.h"
#include "coredump-config.h"
#include "coredump-context.h"
#include "coredump-submit.h"
#include "coredump-util.h"
#include "coredump-vacuum.h"
#include "elf-util.h"
#include "errno-util.h"
#include "escape.h"
#include "fd-util.h"
#include "format-util.h"
#include "fs-util.h"
#include "io-util.h"
#include "iovec-wrapper.h"
#include "journal-send.h"
#include "json-util.h"
#include "log.h"
#include "mkdir-label.h"
#include "namespace-util.h"
#include "path-util.h"
#include "process-util.h"
#include "socket-util.h"
#include "stat-util.h"
#include "string-util.h"
#include "tmpfile-util.h"
#include "uid-classification.h"
#include "user-util.h"
/* When checking for available memory and setting lower limits, don't
* go below 4MB for writing core files to storage. */
#define PROCESS_SIZE_MIN (4U*1024U*1024U)
#define MOUNT_TREE_ROOT "/run/systemd/mount-rootfs"
#define filename_escape(s) xescape((s), "./ ")
static const char* coredump_tmpfile_name(const char *s) {
return s ?: "(unnamed temporary file)";
}
static int make_filename(const Context *context, char **ret) {
_cleanup_free_ char *c = NULL, *u = NULL, *p = NULL, *t = NULL;
sd_id128_t boot = {};
int r;
assert(context);
c = filename_escape(context->meta[META_COMM]);
if (!c)
return -ENOMEM;
u = filename_escape(context->meta[META_ARGV_UID]);
if (!u)
return -ENOMEM;
r = sd_id128_get_boot(&boot);
if (r < 0)
return r;
p = filename_escape(context->meta[META_ARGV_PID]);
if (!p)
return -ENOMEM;
t = filename_escape(context->meta[META_ARGV_TIMESTAMP]);
if (!t)
return -ENOMEM;
if (asprintf(ret,
"/var/lib/systemd/coredump/core.%s.%s." SD_ID128_FORMAT_STR ".%s.%s",
c,
u,
SD_ID128_FORMAT_VAL(boot),
p,
t) < 0)
return -ENOMEM;
return 0;
}
static int grant_user_access(int core_fd, const Context *context) {
int at_secure = -1;
uid_t uid = UID_INVALID, euid = UID_INVALID;
uid_t gid = GID_INVALID, egid = GID_INVALID;
int r;
assert(core_fd >= 0);
assert(context);
if (!context->meta[META_PROC_AUXV])
return log_warning_errno(SYNTHETIC_ERRNO(ENODATA), "No auxv data, not adjusting permissions.");
uint8_t elf[EI_NIDENT];
errno = 0;
if (pread(core_fd, &elf, sizeof(elf), 0) != sizeof(elf))
return log_warning_errno(errno_or_else(EIO),
"Failed to pread from coredump fd: %s", STRERROR_OR_EOF(errno));
if (elf[EI_MAG0] != ELFMAG0 ||
elf[EI_MAG1] != ELFMAG1 ||
elf[EI_MAG2] != ELFMAG2 ||
elf[EI_MAG3] != ELFMAG3 ||
elf[EI_VERSION] != EV_CURRENT)
return log_info_errno(SYNTHETIC_ERRNO(EUCLEAN),
"Core file does not have ELF header, not adjusting permissions.");
if (!IN_SET(elf[EI_CLASS], ELFCLASS32, ELFCLASS64) ||
!IN_SET(elf[EI_DATA], ELFDATA2LSB, ELFDATA2MSB))
return log_info_errno(SYNTHETIC_ERRNO(EUCLEAN),
"Core file has strange ELF class, not adjusting permissions.");
if ((elf[EI_DATA] == ELFDATA2LSB) != (__BYTE_ORDER == __LITTLE_ENDIAN))
return log_info_errno(SYNTHETIC_ERRNO(EUCLEAN),
"Core file has non-native endianness, not adjusting permissions.");
r = parse_auxv(LOG_WARNING,
/* elf_class= */ elf[EI_CLASS],
context->meta[META_PROC_AUXV],
context->meta_size[META_PROC_AUXV],
&at_secure, &uid, &euid, &gid, &egid);
if (r < 0)
return r;
/* We allow access if %d/dumpable on the command line was exactly 1, we got all the data,
* at_secure is not set, and the uid/gid match euid/egid. */
bool ret =
context->dumpable == SUID_DUMP_USER &&
at_secure == 0 &&
uid != UID_INVALID && euid != UID_INVALID && uid == euid &&
gid != GID_INVALID && egid != GID_INVALID && gid == egid;
log_debug("Will %s access (dumpable=%u uid="UID_FMT " euid="UID_FMT " gid="GID_FMT " egid="GID_FMT " at_secure=%s)",
ret ? "permit" : "restrict",
context->dumpable,
uid, euid, gid, egid, yes_no(at_secure));
return ret;
}
static int fix_acl(int fd, uid_t uid, bool allow_user) {
assert(fd >= 0);
assert(uid_is_valid(uid));
#if HAVE_ACL
int r;
/* We don't allow users to read coredumps if the uid or capabilities were changed. */
if (!allow_user)
return 0;
if (uid_is_system(uid) || uid_is_dynamic(uid) || uid_is_greeter(uid) || uid == UID_NOBODY)
return 0;
/* Make sure normal users can read (but not write or delete) their own coredumps */
r = fd_add_uid_acl_permission(fd, uid, ACL_READ);
if (r < 0)
return log_error_errno(r, "Failed to adjust ACL of the coredump: %m");
#endif
return 0;
}
static int fix_xattr(int fd, const Context *context) {
static const char * const xattrs[_META_MAX] = {
[META_ARGV_PID] = "user.coredump.pid",
[META_ARGV_UID] = "user.coredump.uid",
[META_ARGV_GID] = "user.coredump.gid",
[META_ARGV_SIGNAL] = "user.coredump.signal",
[META_ARGV_TIMESTAMP] = "user.coredump.timestamp",
[META_ARGV_RLIMIT] = "user.coredump.rlimit",
[META_ARGV_HOSTNAME] = "user.coredump.hostname",
[META_COMM] = "user.coredump.comm",
[META_EXE] = "user.coredump.exe",
};
int r = 0;
assert(fd >= 0);
/* Attach some metadata to coredumps via extended attributes. Just because we can. */
for (unsigned i = 0; i < _META_MAX; i++) {
int k;
if (isempty(context->meta[i]) || !xattrs[i])
continue;
k = RET_NERRNO(fsetxattr(fd, xattrs[i], context->meta[i], strlen(context->meta[i]), XATTR_CREATE));
RET_GATHER(r, k);
}
return r;
}
static int fix_permissions_and_link(
int fd,
const char *filename,
const char *target,
const Context *context,
bool allow_user) {
int r;
assert(fd >= 0);
assert(target);
assert(context);
/* Ignore errors on these */
(void) fchmod(fd, 0640);
(void) fix_acl(fd, context->uid, allow_user);
(void) fix_xattr(fd, context);
r = link_tmpfile(fd, filename, target, LINK_TMPFILE_SYNC);
if (r < 0)
return log_error_errno(r, "Failed to move coredump %s into place: %m", target);
return 0;
}
static int save_external_coredump(
const Context *context,
int input_fd,
char **ret_filename,
int *ret_node_fd,
int *ret_data_fd,
uint64_t *ret_size,
uint64_t *ret_compressed_size,
bool *ret_truncated) {
_cleanup_(unlink_and_freep) char *tmp = NULL;
_cleanup_free_ char *fn = NULL;
_cleanup_close_ int fd = -EBADF;
uint64_t process_limit, max_size;
bool truncated, storage_on_tmpfs;
struct stat st;
int r;
assert(context);
assert(ret_filename);
assert(ret_node_fd);
assert(ret_data_fd);
assert(ret_size);
assert(ret_compressed_size);
assert(ret_truncated);
if (context->rlimit < page_size())
/* Is coredumping disabled? Then don't bother saving/processing the
* coredump. Anything below PAGE_SIZE cannot give a readable coredump
* (the kernel uses ELF_EXEC_PAGESIZE which is not easily accessible, but
* is usually the same as PAGE_SIZE. */
return log_info_errno(SYNTHETIC_ERRNO(EBADSLT),
"Resource limits disable core dumping for process %s (%s).",
context->meta[META_ARGV_PID], context->meta[META_COMM]);
process_limit = MAX(arg_process_size_max, coredump_storage_size_max());
if (process_limit == 0)
return log_debug_errno(SYNTHETIC_ERRNO(EBADSLT),
"Limits for coredump processing and storage are both 0, not dumping core.");
/* Never store more than the process configured, or than we actually shall keep or process */
max_size = MIN(context->rlimit, process_limit);
r = make_filename(context, &fn);
if (r < 0)
return log_error_errno(r, "Failed to determine coredump file name: %m");
(void) mkdir_parents_label(fn, 0755);
fd = open_tmpfile_linkable(fn, O_RDWR|O_CLOEXEC, &tmp);
if (fd < 0)
return log_error_errno(fd, "Failed to create temporary file for coredump %s: %m", fn);
/* If storage is on tmpfs, the kernel oomd might kill us if there's MemoryMax set on
* the service or the slice it belongs to. This is common on low-resources systems,
* to avoid crashing processes to take away too many system resources.
* Check the cgroup settings, and set max_size to a bit less than half of the
* available memory left to the process.
* Then, attempt to write the core file uncompressed first - if the write gets
* interrupted, we know we won't be able to write it all, so instead compress what
* was written so far, delete the uncompressed truncated core, and then continue
* compressing from STDIN. Given the compressed core cannot be larger than the
* uncompressed one, and 1KB for metadata is accounted for in the calculation, we
* should be able to at least store the full compressed core file. */
storage_on_tmpfs = fd_is_temporary_fs(fd) > 0;
if (storage_on_tmpfs && arg_compress) {
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
uint64_t cgroup_limit = UINT64_MAX;
struct statvfs sv;
/* If we can't get the cgroup limit, just ignore it, but don't fail,
* try anyway with the config settings. */
r = sd_bus_default_system(&bus);
if (r < 0)
log_info_errno(r, "Failed to connect to system bus, skipping MemoryAvailable check: %m");
else {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
r = sd_bus_get_property_trivial(
bus,
"org.freedesktop.systemd1",
"/org/freedesktop/systemd1/unit/self",
"org.freedesktop.systemd1.Service",
"MemoryAvailable",
&error,
't', &cgroup_limit);
if (r < 0)
log_warning_errno(r,
"Failed to query MemoryAvailable for current unit, "
"falling back to static config settings: %s",
bus_error_message(&error, r));
}
/* First, ensure we are not going to go over the cgroup limit */
max_size = MIN(cgroup_limit, max_size);
/* tmpfs might get full quickly, so check the available space too. But don't worry about
* errors here, failing to access the storage location will be better logged when writing to
* it. */
if (fstatvfs(fd, &sv) >= 0)
max_size = MIN((uint64_t)sv.f_frsize * (uint64_t)sv.f_bfree, max_size);
/* Impose a lower minimum, otherwise we will miss the basic headers. */
max_size = MAX(PROCESS_SIZE_MIN, max_size);
/* Ensure we can always switch to compressing on the fly in case we are running out of space
* by keeping half of the space/memory available, plus 1KB metadata overhead from the
* compression algorithm. */
max_size = LESS_BY(max_size, 1024U) / 2;
log_debug("Limiting core file size to %" PRIu64 " bytes due to cgroup and/or filesystem limits.", max_size);
}
r = copy_bytes(input_fd, fd, max_size, 0);
if (r < 0)
return log_error_errno(r, "Cannot store coredump of %s (%s): %m",
context->meta[META_ARGV_PID], context->meta[META_COMM]);
truncated = r == 1;
bool allow_user = grant_user_access(fd, context) > 0;
#if HAVE_COMPRESSION
if (arg_compress) {
_cleanup_(unlink_and_freep) char *tmp_compressed = NULL;
_cleanup_free_ char *fn_compressed = NULL;
_cleanup_close_ int fd_compressed = -EBADF;
uint64_t uncompressed_size = 0;
if (lseek(fd, 0, SEEK_SET) < 0)
return log_error_errno(errno, "Failed to seek on coredump %s: %m", fn);
fn_compressed = strjoin(fn, default_compression_extension());
if (!fn_compressed)
return log_oom();
fd_compressed = open_tmpfile_linkable(fn_compressed, O_RDWR|O_CLOEXEC, &tmp_compressed);
if (fd_compressed < 0)
return log_error_errno(fd_compressed, "Failed to create temporary file for coredump %s: %m", fn_compressed);
r = compress_stream(fd, fd_compressed, max_size, &uncompressed_size);
if (r < 0)
return log_error_errno(r, "Failed to compress %s: %m", coredump_tmpfile_name(tmp_compressed));
if (truncated && storage_on_tmpfs) {
uint64_t partial_uncompressed_size = 0;
/* Uncompressed write was truncated and we are writing to tmpfs: delete
* the uncompressed core, and compress the remaining part from STDIN. */
tmp = unlink_and_free(tmp);
fd = safe_close(fd);
r = compress_stream(input_fd, fd_compressed, max_size, &partial_uncompressed_size);
if (r < 0)
return log_error_errno(r, "Failed to compress %s: %m", coredump_tmpfile_name(tmp_compressed));
uncompressed_size += partial_uncompressed_size;
}
r = fix_permissions_and_link(fd_compressed, tmp_compressed, fn_compressed, context, allow_user);
if (r < 0)
return r;
if (fstat(fd_compressed, &st) < 0)
return log_error_errno(errno,
"Failed to fstat core file %s: %m",
coredump_tmpfile_name(tmp_compressed));
*ret_filename = TAKE_PTR(fn_compressed); /* compressed */
*ret_node_fd = TAKE_FD(fd_compressed); /* compressed */
*ret_data_fd = TAKE_FD(fd);
*ret_size = uncompressed_size;
*ret_compressed_size = (uint64_t) st.st_size; /* compressed */
*ret_truncated = truncated;
return 0;
}
#endif
if (truncated)
log_struct(LOG_INFO,
LOG_MESSAGE("Core file was truncated to %"PRIu64" bytes.", max_size),
LOG_ITEM("SIZE_LIMIT=%"PRIu64, max_size),
LOG_MESSAGE_ID(SD_MESSAGE_TRUNCATED_CORE_STR));
r = fix_permissions_and_link(fd, tmp, fn, context, allow_user);
if (r < 0)
return log_error_errno(r, "Failed to fix permissions and finalize coredump %s into %s: %m", coredump_tmpfile_name(tmp), fn);
if (fstat(fd, &st) < 0)
return log_error_errno(errno, "Failed to fstat core file %s: %m", coredump_tmpfile_name(tmp));
if (lseek(fd, 0, SEEK_SET) < 0)
return log_error_errno(errno, "Failed to seek on coredump %s: %m", fn);
*ret_filename = TAKE_PTR(fn);
*ret_node_fd = -EBADF;
*ret_data_fd = TAKE_FD(fd);
*ret_size = (uint64_t) st.st_size;
*ret_compressed_size = UINT64_MAX;
*ret_truncated = truncated;
return 0;
}
static int maybe_remove_external_coredump(
const Context *c,
const char *filename,
uint64_t size) {
assert(c);
/* Returns true if might remove, false if will not remove, < 0 on error. */
/* Always keep around in case of journald/pid1, since we cannot rely on the journal to accept them. */
if (arg_storage != COREDUMP_STORAGE_NONE && (c->is_pid1 || c->is_journald))
return false;
if (arg_storage == COREDUMP_STORAGE_EXTERNAL &&
size <= arg_external_size_max)
return false;
if (!filename)
return true;
if (unlink(filename) < 0 && errno != ENOENT)
return log_error_errno(errno, "Failed to unlink %s: %m", filename);
return true;
}
int acquire_pid_mount_tree_fd(const Context *context, int *ret_fd) {
/* Don't bother preparing environment if we can't pass it to libdwfl. */
#if !HAVE_DWFL_SET_SYSROOT
*ret_fd = -EOPNOTSUPP;
log_debug("dwfl_set_sysroot() is not supported.");
#else
_cleanup_close_ int mntns_fd = -EBADF, root_fd = -EBADF, fd = -EBADF;
_cleanup_close_pair_ int pair[2] = EBADF_PAIR;
int r;
assert(context);
assert(ret_fd);
if (!arg_enter_namespace) {
*ret_fd = -EHOSTDOWN;
log_debug("EnterNamespace=no so we won't use mount tree of the crashed process for generating backtrace.");
return 0;
}
if (socketpair(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, pair) < 0)
return log_error_errno(errno, "Failed to create socket pair: %m");
r = pidref_namespace_open(
&context->pidref,
/* ret_pidns_fd= */ NULL,
&mntns_fd,
/* ret_netns_fd= */ NULL,
/* ret_userns_fd= */ NULL,
&root_fd);
if (r < 0)
return log_error_errno(r, "Failed to open mount namespace of crashing process: %m");
r = namespace_fork("(sd-mount-tree-ns)",
"(sd-mount-tree)",
/* except_fds= */ NULL,
/* n_except_fds= */ 0,
FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL|FORK_LOG|FORK_WAIT,
/* pidns_fd= */ -EBADF,
mntns_fd,
/* netns_fd= */ -EBADF,
/* userns_fd= */ -EBADF,
root_fd,
NULL);
if (r < 0)
return r;
if (r == 0) {
pair[0] = safe_close(pair[0]);
fd = open_tree(-EBADF, "/", AT_NO_AUTOMOUNT | AT_RECURSIVE | AT_SYMLINK_NOFOLLOW | OPEN_TREE_CLOEXEC | OPEN_TREE_CLONE);
if (fd < 0) {
log_error_errno(errno, "Failed to clone mount tree: %m");
_exit(EXIT_FAILURE);
}
r = send_one_fd(pair[1], fd, 0);
if (r < 0) {
log_error_errno(r, "Failed to send mount tree to parent: %m");
_exit(EXIT_FAILURE);
}
_exit(EXIT_SUCCESS);
}
pair[1] = safe_close(pair[1]);
fd = receive_one_fd(pair[0], MSG_DONTWAIT);
if (fd < 0)
return log_error_errno(fd, "Failed to receive mount tree: %m");
*ret_fd = TAKE_FD(fd);
#endif
return 0;
}
static int attach_mount_tree(int mount_tree_fd) {
int r;
assert(mount_tree_fd >= 0);
r = detach_mount_namespace();
if (r < 0)
return log_warning_errno(r, "Failed to detach mount namespace: %m");
r = mkdir_p_label(MOUNT_TREE_ROOT, 0555);
if (r < 0)
return log_warning_errno(r, "Failed to create directory: %m");
r = mount_setattr(mount_tree_fd, "", AT_EMPTY_PATH,
&(struct mount_attr) {
/* MOUNT_ATTR_NOSYMFOLLOW is left out on purpose to allow libdwfl to resolve symlinks.
* libdwfl will use openat2() with RESOLVE_IN_ROOT so there is no risk of symlink escape.
* https://sourceware.org/git/?p=elfutils.git;a=patch;h=06f0520f9a78b07c11c343181d552791dd630346 */
.attr_set = MOUNT_ATTR_RDONLY|MOUNT_ATTR_NOSUID|MOUNT_ATTR_NODEV|MOUNT_ATTR_NOEXEC,
.propagation = MS_SLAVE,
}, sizeof(struct mount_attr));
if (r < 0)
return log_warning_errno(errno, "Failed to change properties of mount tree: %m");
r = move_mount(mount_tree_fd, "", -EBADF, MOUNT_TREE_ROOT, MOVE_MOUNT_F_EMPTY_PATH);
if (r < 0)
return log_warning_errno(errno, "Failed to attach mount tree: %m");
return 0;
}
static int change_uid_gid(const Context *context) {
int r;
assert(context);
uid_t uid = context->uid;
gid_t gid = context->gid;
if (uid_is_system(uid)) {
const char *user = "systemd-coredump";
r = get_user_creds(&user, &uid, &gid, NULL, NULL, 0);
if (r < 0) {
log_warning_errno(r, "Cannot resolve %s user. Proceeding to dump core as root: %m", user);
uid = gid = 0;
}
}
return drop_privileges(uid, gid, 0);
}
static int allocate_journal_field(int fd, size_t size, char **ret, size_t *ret_size) {
_cleanup_free_ char *field = NULL;
ssize_t n;
assert(fd >= 0);
assert(ret);
assert(ret_size);
if (lseek(fd, 0, SEEK_SET) < 0)
return log_warning_errno(errno, "Failed to seek: %m");
field = malloc(9 + size);
if (!field)
return log_warning_errno(SYNTHETIC_ERRNO(ENOMEM),
"Failed to allocate memory for coredump, coredump will not be stored.");
memcpy(field, "COREDUMP=", 9);
/* NB: simple read() would fail for overly large coredumps, since read() on Linux can only deal with
* 0x7ffff000 bytes max. Hence call things in a loop. */
n = loop_read(fd, field + 9, size, /* do_poll= */ false);
if (n < 0)
return log_error_errno((int) n, "Failed to read core data: %m");
if ((size_t) n < size)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Core data too short.");
*ret = TAKE_PTR(field);
*ret_size = size + 9;
return 0;
}
int coredump_submit(
const Context *context,
struct iovec_wrapper *iovw,
int input_fd) {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *json_metadata = NULL;
_cleanup_close_ int coredump_fd = -EBADF, coredump_node_fd = -EBADF;
_cleanup_free_ char *filename = NULL, *coredump_data = NULL, *stacktrace = NULL;
const char *module_name, *root = NULL;
uint64_t coredump_size = UINT64_MAX, coredump_compressed_size = UINT64_MAX;
bool truncated = false, written = false;
sd_json_variant *module_json;
int r;
assert(context);
assert(iovw);
assert(input_fd >= 0);
/* Vacuum before we write anything again */
(void) coredump_vacuum(-1, arg_keep_free, arg_max_use);
/* Always stream the coredump to disk, if that's possible */
written = save_external_coredump(
context, input_fd,
&filename, &coredump_node_fd, &coredump_fd,
&coredump_size, &coredump_compressed_size, &truncated) >= 0;
if (written) {
/* If we could write it to disk we can now process it. */
/* If we don't want to keep the coredump on disk, remove it now, as later on we
* will lack the privileges for it. However, we keep the fd to it, so that we can
* still process it and log it. */
r = maybe_remove_external_coredump(
context,
filename,
coredump_node_fd >= 0 ? coredump_compressed_size : coredump_size);
if (r < 0)
return r;
if (r == 0)
(void) iovw_put_string_field(iovw, "COREDUMP_FILENAME=", filename);
else if (arg_storage == COREDUMP_STORAGE_EXTERNAL)
log_info("The core will not be stored: size %"PRIu64" is greater than %"PRIu64" (the configured maximum)",
coredump_node_fd >= 0 ? coredump_compressed_size : coredump_size, arg_external_size_max);
/* Vacuum again, but exclude the coredump we just created */
(void) coredump_vacuum(coredump_node_fd >= 0 ? coredump_node_fd : coredump_fd, arg_keep_free, arg_max_use);
}
if (context->mount_tree_fd >= 0 && attach_mount_tree(context->mount_tree_fd) >= 0)
root = MOUNT_TREE_ROOT;
/* Now, let's drop privileges to become the user who owns the segfaulted process and allocate the
* coredump memory under the user's uid. This also ensures that the credentials journald will see are
* the ones of the coredumping user, thus making sure the user gets access to the core dump. Let's
* also get rid of all capabilities, if we run as root, we won't need them anymore. */
r = change_uid_gid(context);
if (r < 0)
return log_error_errno(r, "Failed to drop privileges: %m");
if (written) {
/* Try to get a stack trace if we can */
if (coredump_size > arg_process_size_max)
log_debug("Not generating stack trace: core size %"PRIu64" is greater "
"than %"PRIu64" (the configured maximum)",
coredump_size, arg_process_size_max);
else if (coredump_fd >= 0) {
bool skip = startswith(context->meta[META_COMM], "systemd-coredum"); /* COMM is 16 bytes usually */
(void) parse_elf_object(coredump_fd,
context->meta[META_EXE],
root,
/* fork_disable_dump= */ skip, /* avoid loops */
&stacktrace,
&json_metadata);
}
}
_cleanup_free_ char *core_message = NULL;
core_message = strjoin(
"Process ", context->meta[META_ARGV_PID],
" (", context->meta[META_COMM],
") of user ", context->meta[META_ARGV_UID],
written ? " dumped core." : " terminated abnormally without generating a coredump.");
if (!core_message)
return log_oom();
if (context->is_journald && filename)
if (!strextend(&core_message, "\nCoredump diverted to ", filename))
return log_oom();
if (stacktrace)
if (!strextend(&core_message, "\n\n", stacktrace))
return log_oom();
if (context->is_journald)
/* We might not be able to log to the journal, so let's always print the message to another
* log target. The target was set previously to something safe. */
log_dispatch(LOG_ERR, 0, core_message);
(void) iovw_put_string_field(iovw, "MESSAGE=", core_message);
if (truncated)
(void) iovw_put_string_field(iovw, "COREDUMP_TRUNCATED=", "1");
/* If we managed to parse any ELF metadata (build-id, ELF package meta),
* attach it as journal metadata. */
if (json_metadata) {
_cleanup_free_ char *formatted_json = NULL;
r = sd_json_variant_format(json_metadata, 0, &formatted_json);
if (r < 0)
return log_error_errno(r, "Failed to format JSON package metadata: %m");
(void) iovw_put_string_field(iovw, "COREDUMP_PACKAGE_JSON=", formatted_json);
}
/* In the unlikely scenario that context->meta[META_EXE] is not available,
* let's avoid guessing the module name and skip the loop. */
if (context->meta[META_EXE])
JSON_VARIANT_OBJECT_FOREACH(module_name, module_json, json_metadata) {
sd_json_variant *t;
/* We only add structured fields for the 'main' ELF module, and only if we can identify it. */
if (!path_equal_filename(module_name, context->meta[META_EXE]))
continue;
t = sd_json_variant_by_key(module_json, "name");
if (t)
(void) iovw_put_string_field(iovw, "COREDUMP_PACKAGE_NAME=", sd_json_variant_string(t));
t = sd_json_variant_by_key(module_json, "version");
if (t)
(void) iovw_put_string_field(iovw, "COREDUMP_PACKAGE_VERSION=", sd_json_variant_string(t));
}
/* Optionally store the entire coredump in the journal */
if (arg_storage == COREDUMP_STORAGE_JOURNAL && coredump_fd >= 0) {
if (coredump_size <= arg_journal_size_max) {
size_t sz = 0;
/* Store the coredump itself in the journal */
r = allocate_journal_field(coredump_fd, (size_t) coredump_size, &coredump_data, &sz);
if (r >= 0) {
if (iovw_put(iovw, coredump_data, sz) >= 0)
TAKE_PTR(coredump_data);
} else
log_warning_errno(r, "Failed to attach the core to the journal entry: %m");
} else
log_info("The core will not be stored: size %"PRIu64" is greater than %"PRIu64" (the configured maximum)",
coredump_size, arg_journal_size_max);
}
/* If journald is coredumping, we have to be careful that we don't deadlock when trying to write the
* coredump to the journal, so we put the journal socket in nonblocking mode before trying to write
* the coredump to the socket. */
if (context->is_journald) {
r = journal_fd_nonblock(true);
if (r < 0)
return log_error_errno(r, "Failed to make journal socket non-blocking: %m");
}
r = sd_journal_sendv(iovw->iovec, iovw->count);
if (context->is_journald) {
int k;
k = journal_fd_nonblock(false);
if (k < 0)
return log_error_errno(k, "Failed to make journal socket blocking: %m");
}
if (r == -EAGAIN && context->is_journald)
log_warning_errno(r, "Failed to log journal coredump, ignoring: %m");
else if (r < 0)
return log_error_errno(r, "Failed to log coredump: %m");
return 0;
}

View File

@ -1,10 +0,0 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "coredump-forward.h"
int acquire_pid_mount_tree_fd(const Context *context, int *ret_fd);
int coredump_submit(
const Context *context,
struct iovec_wrapper *iovw,
int input_fd);

File diff suppressed because it is too large Load Diff

View File

@ -6,13 +6,6 @@ endif
systemd_coredump_sources = files(
'coredump.c',
'coredump-backtrace.c',
'coredump-config.c',
'coredump-context.c',
'coredump-kernel-helper.c',
'coredump-receive.c',
'coredump-send.c',
'coredump-submit.c',
)
systemd_coredump_extract_sources = files(
'coredump-vacuum.c',

View File

@ -2302,7 +2302,7 @@ static int run(int argc, char *argv[]) {
/* Don't run things in private userns, if the mount shall be attached to the host */
if (!IN_SET(arg_action, ACTION_MOUNT, ACTION_WITH)) {
userns_fd = nsresource_allocate_userns(/* name= */ NULL, NSRESOURCE_UIDS_64K); /* allocate 64K users by default */
userns_fd = nsresource_allocate_userns(/* name= */ NULL, UINT64_C(0x10000)); /* allocate 64K users by default */
if (userns_fd < 0)
return log_error_errno(userns_fd, "Failed to allocate user namespace with 64K users: %m");
}

View File

@ -7,7 +7,6 @@
#include "fileio.h"
#include "log.h"
#include "log-assert-critical.h"
/* The entry point into the fuzzer */
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);

View File

@ -207,9 +207,7 @@ static int help(int argc, char *argv[], void *userdata) {
" --version Show package version\n"
" --format=FORMAT Select format\n"
" --class=CLASS Select image class (machine, sysext, confext,\n"
" portable)\n"
" --system Operate in per-system mode\n"
" --user Operate in per-user mode\n",
" portable)\n",
program_invocation_short_name,
ansi_underline(),
ansi_normal(),
@ -225,8 +223,6 @@ static int parse_argv(int argc, char *argv[]) {
ARG_VERSION = 0x100,
ARG_FORMAT,
ARG_CLASS,
ARG_SYSTEM,
ARG_USER,
};
static const struct option options[] = {
@ -234,8 +230,6 @@ static int parse_argv(int argc, char *argv[]) {
{ "version", no_argument, NULL, ARG_VERSION },
{ "format", required_argument, NULL, ARG_FORMAT },
{ "class", required_argument, NULL, ARG_CLASS },
{ "system", no_argument, NULL, ARG_SYSTEM },
{ "user", no_argument, NULL, ARG_USER },
{}
};
@ -268,14 +262,6 @@ static int parse_argv(int argc, char *argv[]) {
break;
case ARG_SYSTEM:
arg_runtime_scope = RUNTIME_SCOPE_SYSTEM;
break;
case ARG_USER:
arg_runtime_scope = RUNTIME_SCOPE_USER;
break;
case '?':
return -EINVAL;

View File

@ -6,26 +6,21 @@
#include "sd-event.h"
#include "capability-util.h"
#include "copy.h"
#include "dirent-util.h"
#include "dissect-image.h"
#include "fd-util.h"
#include "fs-util.h"
#include "import-common.h"
#include "libarchive-util.h"
#include "log.h"
#include "namespace-util.h"
#include "nsresource.h"
#include "os-util.h"
#include "pidref.h"
#include "process-util.h"
#include "rm-rf.h"
#include "selinux-util.h"
#include "stat-util.h"
#include "tar-util.h"
#include "tmpfile-util.h"
int import_fork_tar_x(int tree_fd, int userns_fd, PidRef *ret_pid) {
int import_fork_tar_x(int tree_fd, PidRef *ret_pid) {
int r;
assert(tree_fd >= 0);
@ -46,7 +41,7 @@ int import_fork_tar_x(int tree_fd, int userns_fd, PidRef *ret_pid) {
r = pidref_safe_fork_full(
"tar-x",
/* stdio_fds= */ NULL,
(int[]) { tree_fd, pipefd[0], userns_fd }, userns_fd >= 0 ? 3 : 2,
(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)
@ -63,20 +58,12 @@ int import_fork_tar_x(int tree_fd, int userns_fd, PidRef *ret_pid) {
/* Child */
if (userns_fd >= 0) {
r = detach_mount_namespace_userns(userns_fd);
if (r < 0) {
log_error_errno(r, "Failed to join user namespace: %m");
_exit(EXIT_FAILURE);
}
}
if (unshare(CLONE_NEWNET) < 0)
log_debug_errno(errno, "Failed to lock tar into network namespace, ignoring: %m");
log_warning_errno(errno, "Failed to lock tar into network namespace, ignoring: %m");
r = capability_bounding_set_drop(retain, true);
if (r < 0)
log_debug_errno(r, "Failed to drop capabilities, ignoring: %m");
log_warning_errno(r, "Failed to drop capabilities, ignoring: %m");
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");
@ -304,147 +291,3 @@ int import_allocate_event_with_signals(sd_event **ret) {
*ret = TAKE_PTR(event);
return 0;
}
int import_make_foreign_userns(int *userns_fd) {
assert(userns_fd);
if (*userns_fd >= 0)
return 0;
*userns_fd = nsresource_allocate_userns(/* name= */ NULL, NSRESOURCE_UIDS_64K); /* allocate 64K users */
if (*userns_fd < 0)
return log_error_errno(*userns_fd, "Failed to allocate transient user namespace: %m");
return 1;
}
int import_copy_foreign(
int source_fd,
int target_fd,
int *userns_fd) {
int r;
assert(source_fd >= 0);
assert(target_fd >= 0);
assert(userns_fd);
/* Copies dir referenced by source_fd into dir referenced by source_fd, moves to the specified userns
* for that (allocated if needed), which should be foreign UID range */
r = import_make_foreign_userns(userns_fd);
if (r < 0)
return r;
r = safe_fork_full(
"copy-tree",
/* stdio_fds= */ NULL,
(int[]) { *userns_fd, source_fd, target_fd }, 3,
FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_REOPEN_LOG|FORK_WAIT,
/* ret_pid= */ NULL);
if (r < 0)
return r;
if (r == 0) {
r = namespace_enter(
/* pidns_fd= */ -EBADF,
/* mntns_fd= */ -EBADF,
/* netns_fd= */ -EBADF,
*userns_fd,
/* root_fd= */ -EBADF);
if (r < 0) {
log_error_errno(r, "Failed to join user namespace: %m");
_exit(EXIT_FAILURE);
}
r = copy_tree_at(
source_fd, /* from= */ NULL,
target_fd, /* to = */ NULL,
/* override_uid= */ UID_INVALID,
/* override_gid= */ GID_INVALID,
COPY_REFLINK|COPY_HARDLINKS|COPY_MERGE_EMPTY|COPY_MERGE_APPLY_STAT|COPY_SAME_MOUNT|COPY_ALL_XATTRS,
/* denylist= */ NULL,
/* subvolumes= */ NULL);
if (r < 0) {
log_error_errno(r, "Failed to copy tree: %m");
_exit(EXIT_FAILURE);
}
_exit(EXIT_SUCCESS);
}
return 0;
}
int import_remove_tree_foreign(const char *path, int *userns_fd) {
int r;
assert(path);
assert(userns_fd);
r = import_make_foreign_userns(userns_fd);
if (r < 0)
return r;
_cleanup_close_ int tree_fd = -EBADF;
r = mountfsd_mount_directory(
path,
*userns_fd,
DISSECT_IMAGE_FOREIGN_UID,
&tree_fd);
if (r < 0)
return r;
r = safe_fork_full(
"rm-tree",
/* stdio_fds= */ NULL,
(int[]) { *userns_fd, tree_fd }, 2,
FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_REOPEN_LOG|FORK_WAIT,
/* ret_pid= */ NULL);
if (r < 0)
return r;
if (r == 0) {
/* child */
r = namespace_enter(
/* pidns_fd= */ -EBADF,
/* mntns_fd= */ -EBADF,
/* netns_fd= */ -EBADF,
*userns_fd,
/* root_fd= */ -EBADF);
if (r < 0) {
log_error_errno(r, "Failed to join user namespace: %m");
_exit(EXIT_FAILURE);
}
_cleanup_close_ int dfd = fd_reopen(tree_fd, O_DIRECTORY|O_CLOEXEC);
if (dfd < 0) {
log_error_errno(r, "Failed to reopen tree fd: %m");
_exit(EXIT_FAILURE);
}
r = rm_rf_children(dfd, REMOVE_PHYSICAL|REMOVE_SUBVOLUME|REMOVE_CHMOD, /* root_dev= */ NULL);
if (r < 0)
log_warning_errno(r, "Failed to empty '%s' directory in foreign UID mode, ignoring: %m", path);
_exit(EXIT_SUCCESS);
}
return 0;
}
int import_remove_tree(const char *path, int *userns_fd, ImportFlags flags) {
int r;
assert(path);
assert(userns_fd);
/* Try the userns dance first, to remove foreign UID range owned trees */
if (FLAGS_SET(flags, IMPORT_FOREIGN_UID))
(void) import_remove_tree_foreign(path, userns_fd);
r = rm_rf(path, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME|REMOVE_MISSING_OK|REMOVE_CHMOD);
if (r < 0)
return log_error_errno(r, "Failed to remove '%s': %m", path);
return 0;
}

View File

@ -15,16 +15,15 @@ typedef enum ImportFlags {
IMPORT_CONVERT_QCOW2 = 1 << 5, /* raw: if we detect a qcow2 image, unpack it */
IMPORT_DIRECT = 1 << 6, /* import without rename games */
IMPORT_SYNC = 1 << 7, /* fsync() right before we are done */
IMPORT_FOREIGN_UID = 1 << 8, /* tar: go via nsresourced/mountfsd and make owned by foreign UID */
/* When pulling these flags are defined too */
IMPORT_PULL_SETTINGS = 1 << 9, /* download .nspawn settings file */
IMPORT_PULL_ROOTHASH = 1 << 10, /* only for raw: download .roothash file for verity */
IMPORT_PULL_ROOTHASH_SIGNATURE = 1 << 11, /* only for raw: download .roothash.p7s file for verity */
IMPORT_PULL_VERITY = 1 << 12, /* only for raw: download .verity file for verity */
IMPORT_PULL_SETTINGS = 1 << 8, /* download .nspawn settings file */
IMPORT_PULL_ROOTHASH = 1 << 9, /* only for raw: download .roothash file for verity */
IMPORT_PULL_ROOTHASH_SIGNATURE = 1 << 10, /* only for raw: download .roothash.p7s file for verity */
IMPORT_PULL_VERITY = 1 << 11, /* only for raw: download .verity file for verity */
/* The supported flags for the tar and the raw importing */
IMPORT_FLAGS_MASK_TAR = IMPORT_FORCE|IMPORT_READ_ONLY|IMPORT_BTRFS_SUBVOL|IMPORT_BTRFS_QUOTA|IMPORT_DIRECT|IMPORT_SYNC|IMPORT_FOREIGN_UID,
IMPORT_FLAGS_MASK_TAR = IMPORT_FORCE|IMPORT_READ_ONLY|IMPORT_BTRFS_SUBVOL|IMPORT_BTRFS_QUOTA|IMPORT_DIRECT|IMPORT_SYNC,
IMPORT_FLAGS_MASK_RAW = IMPORT_FORCE|IMPORT_READ_ONLY|IMPORT_CONVERT_QCOW2|IMPORT_DIRECT|IMPORT_SYNC,
/* The supported flags for the tar and the raw pulling */
@ -35,7 +34,7 @@ typedef enum ImportFlags {
} ImportFlags;
int import_fork_tar_c(const char *path, PidRef *ret);
int import_fork_tar_x(int tree_fd, int userns_fd, PidRef *ret_pid);
int import_fork_tar_x(int tree_fd, PidRef *ret_pid);
int import_mangle_os_tree(const char *path);
@ -43,11 +42,4 @@ bool import_validate_local(const char *name, ImportFlags flags);
int import_allocate_event_with_signals(sd_event **ret);
int import_make_foreign_userns(int *userns_fd);
int import_copy_foreign(int source_fd, int target_fd, int *userns_fd);
int import_remove_tree_foreign(const char *path, int *userns_fd);
int import_remove_tree(const char *path, int *userns_fd, ImportFlags flags);
#define IMPORT_BUFFER_SIZE (128U*1024U)

View File

@ -35,12 +35,10 @@ static bool arg_btrfs_subvol = true;
static bool arg_btrfs_quota = true;
static bool arg_sync = true;
static bool arg_direct = false;
static char *arg_image_root = NULL;
static const char *arg_image_root = NULL;
static ImageClass arg_class = IMAGE_MACHINE;
static RuntimeScope arg_runtime_scope = _RUNTIME_SCOPE_INVALID;
STATIC_DESTRUCTOR_REGISTER(arg_image_root, freep);
typedef struct ProgressInfo {
RateLimit limit;
char *path;
@ -282,9 +280,7 @@ static int help(int argc, char *argv[], void *userdata) {
" subvolume\n"
" --sync=BOOL Controls whether to sync() before completing\n"
" --class=CLASS Select image class (machine, sysext, confext,\n"
" portable)\n"
" --system Operate in per-system mode\n"
" --user Operate in per-user mode\n",
" portable)\n",
program_invocation_short_name,
ansi_underline(),
ansi_normal(),
@ -306,8 +302,6 @@ static int parse_argv(int argc, char *argv[]) {
ARG_BTRFS_QUOTA,
ARG_SYNC,
ARG_CLASS,
ARG_SYSTEM,
ARG_USER,
};
static const struct option options[] = {
@ -321,8 +315,6 @@ static int parse_argv(int argc, char *argv[]) {
{ "btrfs-quota", required_argument, NULL, ARG_BTRFS_QUOTA },
{ "sync", required_argument, NULL, ARG_SYNC },
{ "class", required_argument, NULL, ARG_CLASS },
{ "system", no_argument, NULL, ARG_SYSTEM },
{ "user", no_argument, NULL, ARG_USER },
{}
};
@ -346,10 +338,7 @@ static int parse_argv(int argc, char *argv[]) {
break;
case ARG_IMAGE_ROOT:
r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image_root);
if (r < 0)
return r;
arg_image_root = optarg;
break;
case ARG_READ_ONLY:
@ -388,14 +377,6 @@ static int parse_argv(int argc, char *argv[]) {
break;
case ARG_SYSTEM:
arg_runtime_scope = RUNTIME_SCOPE_SYSTEM;
break;
case ARG_USER:
arg_runtime_scope = RUNTIME_SCOPE_USER;
break;
case '?':
return -EINVAL;
@ -403,11 +384,8 @@ static int parse_argv(int argc, char *argv[]) {
assert_not_reached();
}
if (!arg_image_root) {
r = image_root_pick(arg_runtime_scope < 0 ? RUNTIME_SCOPE_SYSTEM : arg_runtime_scope, arg_class, /* runtime= */ false, &arg_image_root);
if (r < 0)
return log_error_errno(r, "Failed to pick image root: %m");
}
if (!arg_image_root)
arg_image_root = image_root_to_string(arg_class);
return 1;
}

View File

@ -17,7 +17,6 @@
#include "parse-util.h"
#include "path-util.h"
#include "proc-cmdline.h"
#include "runtime-scope.h"
#include "specifier.h"
#include "string-util.h"
#include "unit-name.h"
@ -201,10 +200,7 @@ static int parse_pull_expression(const char *v) {
if (!GREEDY_REALLOC(arg_transfers, arg_n_transfers + 1))
return log_oom();
_cleanup_free_ char *image_root = NULL;
r = image_root_pick(RUNTIME_SCOPE_SYSTEM, class, runtime, &image_root);
if (r < 0)
return log_error_errno(r, "Failed to pick image root: %m");
const char *image_root = runtime ? image_root_runtime_to_string(class) : image_root_to_string(class);
_cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL;
r = sd_json_buildo(
@ -224,7 +220,7 @@ static int parse_pull_expression(const char *v) {
.type = type,
.local = TAKE_PTR(local),
.remote = TAKE_PTR(remote),
.image_root = TAKE_PTR(image_root),
.image_root = image_root,
.json = TAKE_PTR(j),
.blockdev = blockdev,
};

View File

@ -7,7 +7,6 @@
#include "alloc-util.h"
#include "btrfs-util.h"
#include "dissect-image.h"
#include "errno-util.h"
#include "fd-util.h"
#include "format-util.h"
@ -47,7 +46,6 @@ typedef struct TarImport {
int input_fd;
int tar_fd;
int tree_fd;
int userns_fd;
ImportCompress compress;
@ -75,10 +73,7 @@ TarImport* tar_import_unref(TarImport *i) {
pidref_done_sigkill_wait(&i->tar_pid);
if (i->temp_path) {
import_remove_tree(i->temp_path, &i->userns_fd, i->flags);
free(i->temp_path);
}
rm_rf_subvolume_and_free(i->temp_path);
import_compress_free(&i->compress);
@ -86,7 +81,6 @@ TarImport* tar_import_unref(TarImport *i) {
safe_close(i->tar_fd);
safe_close(i->tree_fd);
safe_close(i->userns_fd);
free(i->final_path);
free(i->image_root);
@ -120,7 +114,6 @@ int tar_import_new(
.input_fd = -EBADF,
.tar_fd = -EBADF,
.tree_fd = -EBADF,
.userns_fd = -EBADF,
.on_finished = on_finished,
.userdata = userdata,
.last_percent = UINT_MAX,
@ -207,8 +200,8 @@ static int tar_import_finish(TarImport *i) {
AT_FDCWD, d,
AT_FDCWD, i->final_path,
(i->flags & IMPORT_FORCE ? INSTALL_REPLACE : 0) |
(i->flags & IMPORT_READ_ONLY ? INSTALL_READ_ONLY|INSTALL_GRACEFUL : 0) |
(i->flags & IMPORT_SYNC ? INSTALL_SYNCFS|INSTALL_GRACEFUL : 0));
(i->flags & IMPORT_READ_ONLY ? INSTALL_READ_ONLY : 0) |
(i->flags & IMPORT_SYNC ? INSTALL_SYNCFS : 0));
if (r < 0)
return log_error_errno(r, "Failed to move '%s' into place: %m", i->final_path ?: i->local);
@ -251,20 +244,6 @@ static int tar_import_fork_tar(TarImport *i) {
if (FLAGS_SET(i->flags, IMPORT_DIRECT|IMPORT_FORCE))
(void) rm_rf(d, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME);
if (FLAGS_SET(i->flags, IMPORT_FOREIGN_UID)) {
r = import_make_foreign_userns(&i->userns_fd);
if (r < 0)
return r;
_cleanup_close_ int directory_fd = -EBADF;
r = mountfsd_make_directory(d, /* flags= */ 0, &directory_fd);
if (r < 0)
return r;
r = mountfsd_mount_directory_fd(directory_fd, i->userns_fd, DISSECT_IMAGE_FOREIGN_UID, &i->tree_fd);
if (r < 0)
return r;
} else {
if (i->flags & IMPORT_BTRFS_SUBVOL)
r = btrfs_subvol_make_fallback(AT_FDCWD, d, 0755);
else
@ -283,9 +262,8 @@ static int tar_import_fork_tar(TarImport *i) {
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->userns_fd, &i->tar_pid);
i->tar_fd = import_fork_tar_x(i->tree_fd, &i->tar_pid);
if (i->tar_fd < 0)
return i->tar_fd;

View File

@ -26,14 +26,12 @@
#include "string-util.h"
#include "verbs.h"
static char *arg_image_root = NULL;
static const char *arg_image_root = NULL;
static ImportFlags arg_import_flags = IMPORT_BTRFS_SUBVOL | IMPORT_BTRFS_QUOTA | IMPORT_CONVERT_QCOW2 | IMPORT_SYNC;
static uint64_t arg_offset = UINT64_MAX, arg_size_max = UINT64_MAX;
static ImageClass arg_class = IMAGE_MACHINE;
static RuntimeScope arg_runtime_scope = _RUNTIME_SCOPE_INVALID;
STATIC_DESTRUCTOR_REGISTER(arg_image_root, freep);
static int normalize_local(const char *local, char **ret) {
_cleanup_free_ char *ll = NULL;
int r;
@ -292,9 +290,7 @@ static int help(int argc, char *argv[], void *userdata) {
" --offset=BYTES Offset to seek to in destination\n"
" --size-max=BYTES Maximum number of bytes to write to destination\n"
" --class=CLASS Select image class (machine, sysext, confext,\n"
" portable)\n"
" --system Operate in per-system mode\n"
" --user Operate in per-user mode\n",
" portable)\n",
program_invocation_short_name,
ansi_underline(),
ansi_normal(),
@ -319,8 +315,6 @@ static int parse_argv(int argc, char *argv[]) {
ARG_OFFSET,
ARG_SIZE_MAX,
ARG_CLASS,
ARG_SYSTEM,
ARG_USER,
};
static const struct option options[] = {
@ -337,8 +331,6 @@ static int parse_argv(int argc, char *argv[]) {
{ "offset", required_argument, NULL, ARG_OFFSET },
{ "size-max", required_argument, NULL, ARG_SIZE_MAX },
{ "class", required_argument, NULL, ARG_CLASS },
{ "system", no_argument, NULL, ARG_SYSTEM },
{ "user", no_argument, NULL, ARG_USER },
{}
};
@ -362,10 +354,7 @@ static int parse_argv(int argc, char *argv[]) {
break;
case ARG_IMAGE_ROOT:
r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image_root);
if (r < 0)
return r;
arg_image_root = optarg;
break;
case ARG_READ_ONLY:
@ -441,14 +430,6 @@ static int parse_argv(int argc, char *argv[]) {
break;
case ARG_SYSTEM:
arg_runtime_scope = RUNTIME_SCOPE_SYSTEM;
break;
case ARG_USER:
arg_runtime_scope = RUNTIME_SCOPE_USER;
break;
case '?':
return -EINVAL;
@ -465,14 +446,8 @@ static int parse_argv(int argc, char *argv[]) {
if (arg_offset != UINT64_MAX && !FLAGS_SET(arg_import_flags, IMPORT_DIRECT))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "File offset only supported in --direct mode.");
if (!arg_image_root) {
r = image_root_pick(arg_runtime_scope < 0 ? RUNTIME_SCOPE_SYSTEM : arg_runtime_scope, arg_class, /* runtime= */ false, &arg_image_root);
if (r < 0)
return log_error_errno(r, "Failed to pick image root: %m");
}
if (arg_runtime_scope == RUNTIME_SCOPE_USER)
arg_import_flags |= IMPORT_FOREIGN_UID;
if (!arg_image_root)
arg_image_root = image_root_to_string(arg_class);
return 1;
}

View File

@ -45,28 +45,19 @@ static ImportVerify arg_verify = IMPORT_VERIFY_SIGNATURE;
static const char* arg_format = NULL;
static sd_json_format_flags_t arg_json_format_flags = SD_JSON_FORMAT_OFF;
static ImageClass arg_image_class = _IMAGE_CLASS_INVALID;
static RuntimeScope arg_runtime_scope = RUNTIME_SCOPE_SYSTEM;
#define PROGRESS_PREFIX "Total:"
static int settle_image_class(void) {
int r;
if (arg_image_class < 0) {
_cleanup_free_ char *j = NULL;
for (ImageClass class = 0; class < _IMAGE_CLASS_MAX; class++) {
_cleanup_free_ char *root = NULL;
r = image_root_pick(arg_runtime_scope, class, /* runtime= */ false, &root);
if (r < 0)
return log_error_errno(r, "Failed to pick image root: %m");
for (ImageClass class = 0; class < _IMAGE_CLASS_MAX; class++)
if (strextendf_with_separator(&j, ", ", "%s (downloads to %s/)",
image_class_to_string(class),
root) < 0)
image_root_to_string(class)) < 0)
return log_oom();
}
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"No image class specified, retry with --class= set to one of: %s.", j);
@ -1023,8 +1014,6 @@ static int help(int argc, char *argv[], void *userdata) {
" --no-ask-password Do not ask for system passwords\n"
" -H --host=[USER@]HOST Operate on remote host\n"
" -M --machine=CONTAINER Operate on local container\n"
" --system Connect to system machine manager\n"
" --user Connect to user machine manager\n"
" --read-only Create read-only image\n"
" -q --quiet Suppress output\n"
" --json=pretty|short|off Generate JSON output\n"
@ -1066,8 +1055,6 @@ static int parse_argv(int argc, char *argv[]) {
ARG_FORMAT,
ARG_CLASS,
ARG_KEEP_DOWNLOAD,
ARG_SYSTEM,
ARG_USER,
};
static const struct option options[] = {
@ -1086,8 +1073,6 @@ static int parse_argv(int argc, char *argv[]) {
{ "format", required_argument, NULL, ARG_FORMAT },
{ "class", required_argument, NULL, ARG_CLASS },
{ "keep-download", required_argument, NULL, ARG_KEEP_DOWNLOAD },
{ "system", no_argument, NULL, ARG_SYSTEM },
{ "user", no_argument, NULL, ARG_USER },
{}
};
@ -1213,14 +1198,6 @@ static int parse_argv(int argc, char *argv[]) {
arg_import_flags_mask |= IMPORT_PULL_KEEP_DOWNLOAD;
break;
case ARG_USER:
arg_runtime_scope = RUNTIME_SCOPE_USER;
break;
case ARG_SYSTEM:
arg_runtime_scope = RUNTIME_SCOPE_SYSTEM;
break;
case '?':
return -EINVAL;
@ -1263,9 +1240,9 @@ static int run(int argc, char *argv[]) {
if (r <= 0)
return r;
r = bus_connect_transport(arg_transport, arg_host, arg_runtime_scope, &bus);
r = bus_connect_transport(arg_transport, arg_host, RUNTIME_SCOPE_SYSTEM, &bus);
if (r < 0)
return bus_log_connect_error(r, arg_transport, arg_runtime_scope);
return bus_log_connect_error(r, arg_transport, RUNTIME_SCOPE_SYSTEM);
(void) sd_bus_set_allow_interactive_authorization(bus, arg_ask_password);

View File

@ -32,7 +32,6 @@
#include "notify-recv.h"
#include "os-util.h"
#include "parse-util.h"
#include "path-lookup.h"
#include "percent-util.h"
#include "pidref.h"
#include "process-util.h"
@ -101,8 +100,7 @@ typedef struct Transfer {
typedef struct Manager {
sd_event *event;
sd_bus *api_bus;
sd_bus *system_bus;
sd_bus *bus;
sd_varlink_server *varlink_server;
uint32_t current_transfer_id;
@ -115,7 +113,7 @@ typedef struct Manager {
bool use_btrfs_subvol;
bool use_btrfs_quota;
RuntimeScope runtime_scope;
RuntimeScope runtime_scope; /* for now: always RUNTIME_SCOPE_SYSTEM */
} Manager;
#define TRANSFERS_MAX 64
@ -225,7 +223,7 @@ static void transfer_send_log_line(Transfer *t, const char *line) {
log_full(priority, "(transfer%" PRIu32 ") %s", t->id, line);
r = sd_bus_emit_signal(
t->manager->api_bus,
t->manager->bus,
t->object_path,
"org.freedesktop.import1.Transfer",
"LogMessage",
@ -256,7 +254,7 @@ static void transfer_send_progress_update(Transfer *t) {
double progress = transfer_percent_as_double(t);
r = sd_bus_emit_signal(
t->manager->api_bus,
t->manager->bus,
t->object_path,
"org.freedesktop.import1.Transfer",
"ProgressUpdate",
@ -337,7 +335,7 @@ static int transfer_finalize(Transfer *t, bool success) {
transfer_send_logs(t, true);
r = sd_bus_emit_signal(
t->manager->api_bus,
t->manager->bus,
"/org/freedesktop/import1",
"org.freedesktop.import1.Manager",
"TransferRemoved",
@ -445,7 +443,6 @@ static int transfer_start(Transfer *t) {
const char *cmd[] = {
NULL, /* systemd-import, systemd-import-fs, systemd-export or systemd-pull */
NULL, /* tar, raw */
NULL, /* --system or --user */
NULL, /* --verify= */
NULL, /* verify argument */
NULL, /* --class= */
@ -527,8 +524,6 @@ static int transfer_start(Transfer *t) {
;
}
cmd[k++] = runtime_scope_cmdline_option_to_string(t->manager->runtime_scope);
if (t->verify != _IMPORT_VERIFY_INVALID) {
cmd[k++] = "--verify";
cmd[k++] = import_verify_to_string(t->verify);
@ -607,7 +602,7 @@ static int transfer_start(Transfer *t) {
return r;
r = sd_bus_emit_signal(
t->manager->api_bus,
t->manager->bus,
"/org/freedesktop/import1",
"org.freedesktop.import1.Manager",
"TransferNew",
@ -635,8 +630,7 @@ static Manager *manager_unref(Manager *m) {
hashmap_free(m->polkit_registry);
m->api_bus = sd_bus_flush_close_unref(m->api_bus);
m->system_bus = sd_bus_flush_close_unref(m->system_bus);
m->bus = sd_bus_flush_close_unref(m->bus);
m->varlink_server = sd_varlink_server_unref(m->varlink_server);
sd_event_unref(m->event);
@ -687,7 +681,7 @@ static int manager_on_notify(sd_event_source *s, int fd, uint32_t revents, void
return 0;
}
static int manager_new(RuntimeScope scope, Manager **ret) {
static int manager_new(Manager **ret) {
_cleanup_(manager_unrefp) Manager *m = NULL;
int r;
@ -700,7 +694,7 @@ static int manager_new(RuntimeScope scope, Manager **ret) {
*m = (Manager) {
.use_btrfs_subvol = true,
.use_btrfs_quota = true,
.runtime_scope = scope,
.runtime_scope = RUNTIME_SCOPE_SYSTEM,
};
r = sd_event_default(&m->event);
@ -763,7 +757,6 @@ static int method_import_tar_or_raw(sd_bus_message *msg, void *userdata, sd_bus_
assert(msg);
if (m->runtime_scope != RUNTIME_SCOPE_USER) {
r = bus_verify_polkit_async(
msg,
"org.freedesktop.import1.import",
@ -774,7 +767,6 @@ static int method_import_tar_or_raw(sd_bus_message *msg, void *userdata, sd_bus_
return r;
if (r == 0)
return 1; /* Will call us back */
}
if (endswith(sd_bus_message_get_member(msg), "Ex")) {
const char *sclass;
@ -866,7 +858,6 @@ static int method_import_fs(sd_bus_message *msg, void *userdata, sd_bus_error *e
assert(msg);
if (m->runtime_scope != RUNTIME_SCOPE_USER) {
r = bus_verify_polkit_async(
msg,
"org.freedesktop.import1.import",
@ -877,7 +868,6 @@ static int method_import_fs(sd_bus_message *msg, void *userdata, sd_bus_error *e
return r;
if (r == 0)
return 1; /* Will call us back */
}
if (endswith(sd_bus_message_get_member(msg), "Ex")) {
const char *sclass;
@ -966,7 +956,6 @@ static int method_export_tar_or_raw(sd_bus_message *msg, void *userdata, sd_bus_
assert(msg);
if (m->runtime_scope != RUNTIME_SCOPE_USER) {
r = bus_verify_polkit_async(
msg,
"org.freedesktop.import1.export",
@ -977,7 +966,6 @@ static int method_export_tar_or_raw(sd_bus_message *msg, void *userdata, sd_bus_
return r;
if (r == 0)
return 1; /* Will call us back */
}
if (endswith(sd_bus_message_get_member(msg), "Ex")) {
const char *sclass;
@ -1066,7 +1054,6 @@ static int method_pull_tar_or_raw(sd_bus_message *msg, void *userdata, sd_bus_er
assert(msg);
if (m->runtime_scope != RUNTIME_SCOPE_USER) {
r = bus_verify_polkit_async(
msg,
"org.freedesktop.import1.pull",
@ -1077,7 +1064,6 @@ static int method_pull_tar_or_raw(sd_bus_message *msg, void *userdata, sd_bus_er
return r;
if (r == 0)
return 1; /* Will call us back */
}
if (endswith(sd_bus_message_get_member(msg), "Ex")) {
const char *sclass;
@ -1716,43 +1702,28 @@ static int manager_connect_bus(Manager *m) {
assert(m);
assert(m->event);
assert(!m->system_bus);
assert(!m->api_bus);
assert(!m->bus);
r = bus_open_system_watch_bind(&m->system_bus);
r = bus_open_system_watch_bind(&m->bus);
if (r < 0)
return log_error_errno(r, "Failed to get system bus connection: %m");
r = sd_bus_attach_event(m->system_bus, m->event, 0);
if (r < 0)
return log_error_errno(r, "Failed to attach system bus to event loop: %m");
if (m->runtime_scope == RUNTIME_SCOPE_SYSTEM)
m->api_bus = sd_bus_ref(m->system_bus);
else {
assert(m->runtime_scope == RUNTIME_SCOPE_USER);
r = sd_bus_default_user(&m->api_bus);
if (r < 0)
return log_error_errno(r, "Failed to get user bus connection: %m");
r = sd_bus_attach_event(m->api_bus, m->event, 0);
if (r < 0)
return log_error_errno(r, "Failed to attach user bus to event loop: %m");
}
r = bus_add_implementation(m->api_bus, &manager_object, m);
r = bus_add_implementation(m->bus, &manager_object, m);
if (r < 0)
return r;
r = bus_log_control_api_register(m->api_bus);
r = bus_log_control_api_register(m->bus);
if (r < 0)
return r;
r = sd_bus_request_name_async(m->api_bus, NULL, "org.freedesktop.import1", 0, NULL, NULL);
r = sd_bus_request_name_async(m->bus, NULL, "org.freedesktop.import1", 0, NULL, NULL);
if (r < 0)
return log_error_errno(r, "Failed to request name: %m");
r = sd_bus_attach_event(m->bus, m->event, 0);
if (r < 0)
return log_error_errno(r, "Failed to attach bus to event loop: %m");
return 0;
}
@ -1896,10 +1867,9 @@ static int vl_method_pull(sd_varlink *link, sd_json_variant *parameters, sd_varl
if (manager_find(m, tt, p.remote))
return sd_varlink_errorbo(link, "io.systemd.Import.AlreadyInProgress", SD_JSON_BUILD_PAIR_STRING("remote", p.remote));
if (m->runtime_scope != RUNTIME_SCOPE_USER) {
r = varlink_verify_polkit_async(
link,
m->system_bus,
m->bus,
"org.freedesktop.import1.pull",
(const char**) STRV_MAKE(
"remote", p.remote,
@ -1910,7 +1880,6 @@ static int vl_method_pull(sd_varlink *link, sd_json_variant *parameters, sd_varl
&m->polkit_registry);
if (r <= 0)
return r;
}
_cleanup_(transfer_unrefp) Transfer *t = NULL;
@ -1969,10 +1938,8 @@ static int manager_connect_varlink(Manager *m) {
assert(m->event);
assert(!m->varlink_server);
r = varlink_server_new(
&m->varlink_server,
(m->runtime_scope != RUNTIME_SCOPE_USER ? SD_VARLINK_SERVER_ACCOUNT_UID : 0)|
SD_VARLINK_SERVER_INHERIT_USERDATA,
r = varlink_server_new(&m->varlink_server,
SD_VARLINK_SERVER_ACCOUNT_UID|SD_VARLINK_SERVER_INHERIT_USERDATA,
m);
if (r < 0)
return log_error_errno(r, "Failed to allocate varlink server object: %m");
@ -2002,12 +1969,7 @@ static int manager_connect_varlink(Manager *m) {
if (r < 0)
return log_error_errno(r, "Failed to bind to passed Varlink sockets: %m");
if (r == 0) {
_cleanup_free_ char *socket_path = NULL;
r = runtime_directory_generic(m->runtime_scope, "systemd/io.systemd.Import", &socket_path);
if (r < 0)
return log_error_errno(r, "Failed to determine socket path: %m");
r = sd_varlink_server_listen_address(m->varlink_server, socket_path, runtime_scope_to_socket_mode(m->runtime_scope) | SD_VARLINK_SERVER_MODE_MKDIR_0755);
r = sd_varlink_server_listen_address(m->varlink_server, "/run/systemd/io.systemd.Import", 0666);
if (r < 0)
return log_error_errno(r, "Failed to bind to Varlink socket: %m");
}
@ -2047,7 +2009,6 @@ static void manager_parse_env(Manager *m) {
static int run(int argc, char *argv[]) {
_cleanup_(manager_unrefp) Manager *m = NULL;
RuntimeScope scope = RUNTIME_SCOPE_SYSTEM;
int r;
log_setup();
@ -2056,7 +2017,7 @@ static int run(int argc, char *argv[]) {
"VM and container image import and export service.",
BUS_IMPLEMENTATIONS(&manager_object,
&log_control_object),
&scope,
/* runtime_scope= */ NULL,
argc, argv);
if (r <= 0)
return r;
@ -2065,7 +2026,7 @@ static int run(int argc, char *argv[]) {
assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD) >= 0);
r = manager_new(scope, &m);
r = manager_new(&m);
if (r < 0)
return log_error_errno(r, "Failed to allocate manager object: %m");
@ -2085,7 +2046,7 @@ static int run(int argc, char *argv[]) {
r = bus_event_loop_with_idle(
m->event,
m->api_bus,
m->bus,
"org.freedesktop.import1",
DEFAULT_EXIT_USEC,
manager_check_idle,

View File

@ -111,9 +111,6 @@ install_data('org.freedesktop.import1.conf',
install_dir : dbuspolicydir)
install_data('org.freedesktop.import1.service',
install_dir : dbussystemservicedir)
install_data('org.freedesktop.import1.service-for-session',
install_dir : dbussessionservicedir,
rename : 'org.freedesktop.import1.service')
install_data('org.freedesktop.import1.policy',
install_dir : polkitpolicydir)

View File

@ -1,13 +0,0 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
[D-BUS Service]
Name=org.freedesktop.import1
Exec=/bin/false
SystemdService=dbus-org.freedesktop.import1.service

View File

@ -9,10 +9,11 @@
#include "btrfs-util.h"
#include "copy.h"
#include "curl-util.h"
#include "dissect-image.h"
#include "errno-util.h"
#include "fd-util.h"
#include "fs-util.h"
#include "import-common.h"
#include "import-util.h"
#include "install-file.h"
#include "log.h"
#include "mkdir-label.h"
@ -25,7 +26,6 @@
#include "rm-rf.h"
#include "string-util.h"
#include "tmpfile-util.h"
#include "uid-classification.h"
#include "web-util.h"
typedef enum TarProgress {
@ -64,7 +64,6 @@ typedef struct TarPull {
char *checksum;
int tree_fd;
int userns_fd;
} TarPull;
TarPull* tar_pull_unref(TarPull *i) {
@ -81,10 +80,7 @@ TarPull* tar_pull_unref(TarPull *i) {
curl_glue_unref(i->glue);
sd_event_unref(i->event);
if (i->temp_path) {
import_remove_tree(i->temp_path, &i->userns_fd, i->flags);
free(i->temp_path);
}
rm_rf_subvolume_and_free(i->temp_path);
unlink_and_free(i->settings_temp_path);
free(i->final_path);
@ -94,7 +90,6 @@ TarPull* tar_pull_unref(TarPull *i) {
free(i->checksum);
safe_close(i->tree_fd);
safe_close(i->userns_fd);
return mfree(i);
}
@ -143,7 +138,6 @@ int tar_pull_new(
.glue = TAKE_PTR(g),
.tar_pid = PIDREF_NULL,
.tree_fd = -EBADF,
.userns_fd = -EBADF,
};
i->glue->on_finished = pull_job_curl_on_finished;
@ -239,9 +233,6 @@ static int tar_pull_make_local_copy(TarPull *i) {
if (!i->local)
return 0;
/* Creates a copy/clone of the original downloaded version (which is supposed to remain untouched)
* under a local image name (which may then be modified) */
assert(i->final_path);
p = path_join(i->image_root, i->local);
@ -253,48 +244,6 @@ static int tar_pull_make_local_copy(TarPull *i) {
if (r < 0)
return log_error_errno(r, "Failed to generate temporary filename for %s: %m", p);
if (FLAGS_SET(i->flags, IMPORT_FOREIGN_UID)) {
/* Copy in userns */
r = import_make_foreign_userns(&i->userns_fd);
if (r < 0)
return r;
/* Usually, tar_pull_job_on_open_disk_tar() would allocate ->tree_fd for us, but if
* already downloaded the image before, and are just making a copy of the original
* download, we need to open ->tree_fd now */
if (i->tree_fd < 0) {
_cleanup_close_ int directory_fd = open(i->final_path, O_DIRECTORY|O_CLOEXEC);
if (directory_fd < 0)
return log_error_errno(errno, "Failed to open '%s': %m", i->final_path);
struct stat st;
if (fstat(directory_fd, &st) < 0)
return log_error_errno(errno, "Failed to stat '%s': %m", i->final_path);
if (uid_is_foreign(st.st_uid)) {
r = mountfsd_mount_directory_fd(directory_fd, i->userns_fd, DISSECT_IMAGE_FOREIGN_UID, &i->tree_fd);
if (r < 0)
return r;
} else
i->tree_fd = TAKE_FD(directory_fd);
}
_cleanup_close_ int directory_fd = -EBADF;
r = mountfsd_make_directory(t, /* flags= */ 0, &directory_fd);
if (r < 0)
return r;
_cleanup_close_ int copy_fd = -EBADF;
r = mountfsd_mount_directory_fd(directory_fd, i->userns_fd, DISSECT_IMAGE_FOREIGN_UID, &copy_fd);
if (r < 0)
return r;
r = import_copy_foreign(i->tree_fd, copy_fd, &i->userns_fd);
if (r < 0)
return r;
} else {
/* Copy locally */
if (i->flags & IMPORT_BTRFS_SUBVOL)
r = btrfs_subvol_snapshot_at(
AT_FDCWD, i->final_path,
@ -306,8 +255,7 @@ static int tar_pull_make_local_copy(TarPull *i) {
else
r = copy_tree(i->final_path, t, UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_HARDLINKS, NULL, NULL);
if (r < 0)
return log_error_errno(r, "Failed to create original download image: %m");
}
return log_error_errno(r, "Failed to create local image: %m");
source = t;
} else
@ -316,8 +264,8 @@ static int tar_pull_make_local_copy(TarPull *i) {
r = install_file(AT_FDCWD, source,
AT_FDCWD, p,
(i->flags & IMPORT_FORCE ? INSTALL_REPLACE : 0) |
(i->flags & IMPORT_READ_ONLY ? INSTALL_READ_ONLY|INSTALL_GRACEFUL : 0) |
(i->flags & IMPORT_SYNC ? INSTALL_SYNCFS|INSTALL_GRACEFUL : 0));
(i->flags & IMPORT_READ_ONLY ? INSTALL_READ_ONLY : 0) |
(i->flags & IMPORT_SYNC ? INSTALL_SYNCFS : 0));
if (r < 0)
return log_error_errno(r, "Failed to install local image '%s': %m", p);
@ -479,8 +427,8 @@ static void tar_pull_job_on_finished(PullJob *j) {
r = install_file(
AT_FDCWD, i->local,
AT_FDCWD, NULL,
(i->flags & IMPORT_READ_ONLY ? INSTALL_READ_ONLY|INSTALL_GRACEFUL : 0) |
(i->flags & IMPORT_SYNC ? INSTALL_SYNCFS|INSTALL_GRACEFUL : 0));
(i->flags & IMPORT_READ_ONLY ? INSTALL_READ_ONLY : 0) |
(i->flags & IMPORT_SYNC ? INSTALL_SYNCFS : 0));
if (r < 0) {
log_error_errno(r, "Failed to finalize '%s': %m", i->local);
goto finish;
@ -505,8 +453,8 @@ static void tar_pull_job_on_finished(PullJob *j) {
r = install_file(
AT_FDCWD, i->temp_path,
AT_FDCWD, i->final_path,
(i->flags & IMPORT_PULL_KEEP_DOWNLOAD ? INSTALL_READ_ONLY|INSTALL_GRACEFUL : 0) |
(i->flags & IMPORT_SYNC ? INSTALL_SYNCFS|INSTALL_GRACEFUL : 0));
(i->flags & IMPORT_PULL_KEEP_DOWNLOAD ? INSTALL_READ_ONLY : 0) |
(i->flags & IMPORT_SYNC ? INSTALL_SYNCFS : 0));
if (r < 0) {
log_error_errno(r, "Failed to rename to final image name to %s: %m", i->final_path);
goto finish;
@ -532,7 +480,7 @@ static void tar_pull_job_on_finished(PullJob *j) {
r = install_file(
AT_FDCWD, i->settings_temp_path,
AT_FDCWD, i->settings_path,
INSTALL_READ_ONLY|INSTALL_GRACEFUL|
INSTALL_READ_ONLY|
(i->flags & IMPORT_SYNC ? INSTALL_FSYNC_FULL : 0));
if (r < 0) {
log_error_errno(r, "Failed to rename settings file to %s: %m", i->settings_path);
@ -589,20 +537,6 @@ static int tar_pull_job_on_open_disk_tar(PullJob *j) {
if (FLAGS_SET(i->flags, IMPORT_DIRECT|IMPORT_FORCE))
(void) rm_rf(where, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME);
if (FLAGS_SET(i->flags, IMPORT_FOREIGN_UID)) {
r = import_make_foreign_userns(&i->userns_fd);
if (r < 0)
return r;
_cleanup_close_ int directory_fd = -EBADF;
r = mountfsd_make_directory(where, /* flags= */ 0, &directory_fd);
if (r < 0)
return r;
r = mountfsd_mount_directory_fd(directory_fd, i->userns_fd, DISSECT_IMAGE_FOREIGN_UID, &i->tree_fd);
if (r < 0)
return r;
} else {
if (i->flags & IMPORT_BTRFS_SUBVOL)
r = btrfs_subvol_make_fallback(AT_FDCWD, where, 0755);
else
@ -612,7 +546,6 @@ static int tar_pull_job_on_open_disk_tar(PullJob *j) {
r = 0;
if (r < 0)
return log_error_errno(r, "Failed to create directory/subvolume %s: %m", where);
if (r > 0 && (i->flags & IMPORT_BTRFS_QUOTA)) { /* actually btrfs subvol */
if (!(i->flags & IMPORT_DIRECT))
(void) import_assign_pool_quota_and_warn(i->image_root);
@ -622,9 +555,8 @@ static int tar_pull_job_on_open_disk_tar(PullJob *j) {
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->userns_fd, &i->tar_pid);
j->disk_fd = import_fork_tar_x(i->tree_fd, &i->tar_pid);
if (j->disk_fd < 0)
return j->disk_fd;

View File

@ -28,7 +28,7 @@
#include "verbs.h"
#include "web-util.h"
static char *arg_image_root = NULL;
static const char *arg_image_root = NULL;
static ImportVerify arg_verify = IMPORT_VERIFY_SIGNATURE;
static ImportFlags arg_import_flags = IMPORT_PULL_SETTINGS | IMPORT_PULL_ROOTHASH | IMPORT_PULL_ROOTHASH_SIGNATURE | IMPORT_PULL_VERITY | IMPORT_BTRFS_SUBVOL | IMPORT_BTRFS_QUOTA | IMPORT_CONVERT_QCOW2 | IMPORT_SYNC;
static uint64_t arg_offset = UINT64_MAX, arg_size_max = UINT64_MAX;
@ -37,7 +37,6 @@ static ImageClass arg_class = IMAGE_MACHINE;
static RuntimeScope arg_runtime_scope = _RUNTIME_SCOPE_INVALID;
STATIC_DESTRUCTOR_REGISTER(arg_checksum, freep);
STATIC_DESTRUCTOR_REGISTER(arg_image_root, freep);
static int normalize_local(const char *local, const char *url, char **ret) {
_cleanup_free_ char *ll = NULL;
@ -276,9 +275,7 @@ static int help(int argc, char *argv[], void *userdata) {
" --class=CLASS Select image class (machine, sysext, confext,\n"
" portable)\n"
" --keep-download=BOOL Keep a copy pristine copy of the downloaded file\n"
" around\n"
" --system Operate in per-system mode\n"
" --user Operate in per-user mode\n",
" around\n",
program_invocation_short_name,
ansi_underline(),
ansi_normal(),
@ -309,8 +306,6 @@ static int parse_argv(int argc, char *argv[]) {
ARG_SIZE_MAX,
ARG_CLASS,
ARG_KEEP_DOWNLOAD,
ARG_SYSTEM,
ARG_USER,
};
static const struct option options[] = {
@ -333,8 +328,6 @@ static int parse_argv(int argc, char *argv[]) {
{ "size-max", required_argument, NULL, ARG_SIZE_MAX },
{ "class", required_argument, NULL, ARG_CLASS },
{ "keep-download", required_argument, NULL, ARG_KEEP_DOWNLOAD },
{ "system", no_argument, NULL, ARG_SYSTEM },
{ "user", no_argument, NULL, ARG_USER },
{}
};
@ -359,10 +352,7 @@ static int parse_argv(int argc, char *argv[]) {
break;
case ARG_IMAGE_ROOT:
r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image_root);
if (r < 0)
return r;
arg_image_root = optarg;
break;
case ARG_VERIFY: {
@ -518,14 +508,6 @@ static int parse_argv(int argc, char *argv[]) {
auto_keep_download = false;
break;
case ARG_SYSTEM:
arg_runtime_scope = RUNTIME_SCOPE_SYSTEM;
break;
case ARG_USER:
arg_runtime_scope = RUNTIME_SCOPE_USER;
break;
case '?':
return -EINVAL;
@ -545,11 +527,8 @@ static int parse_argv(int argc, char *argv[]) {
if (arg_checksum && (arg_import_flags & (IMPORT_PULL_SETTINGS|IMPORT_PULL_ROOTHASH|IMPORT_PULL_ROOTHASH_SIGNATURE|IMPORT_PULL_VERITY)) != 0)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Literal checksum verification only supported if no associated files are downloaded.");
if (!arg_image_root) {
r = image_root_pick(arg_runtime_scope < 0 ? RUNTIME_SCOPE_SYSTEM : arg_runtime_scope, arg_class, /* runtime= */ false, &arg_image_root);
if (r < 0)
return log_error_errno(r, "Failed to pick image root: %m");
}
if (!arg_image_root)
arg_image_root = image_root_to_string(arg_class);
/* .nspawn settings files only really make sense for machine images, not for sysext/confext/portable */
if (auto_settings && arg_class != IMAGE_MACHINE)
@ -560,9 +539,6 @@ static int parse_argv(int argc, char *argv[]) {
if (auto_keep_download)
SET_FLAG(arg_import_flags, IMPORT_PULL_KEEP_DOWNLOAD, arg_class == IMAGE_MACHINE);
if (arg_runtime_scope == RUNTIME_SCOPE_USER)
arg_import_flags |= IMPORT_FOREIGN_UID;
return 1;
}

View File

@ -9,7 +9,6 @@
#include "sd-varlink.h"
#include "alloc-util.h"
#include "env-util.h"
#include "errno-list.h"
#include "errno-util.h"
#include "escape.h"
@ -270,15 +269,18 @@ _public_ int sd_varlink_connect_exec(sd_varlink **ret, const char *_command, cha
uint64_t pidfdid;
if (pidfd_get_inode_id_self_cached(&pidfdid) >= 0) {
r = setenvf("LISTEN_PIDFDID", /* overwrite= */ true, "%" PRIu64, pidfdid);
if (r < 0) {
log_debug_errno(r, "Failed to set environment variable 'LISTEN_PIDFDID': %m");
char spidfdid[DECIMAL_STR_MAX(uint64_t)+1];
xsprintf(spidfdid, "%" PRIu64, pidfdid);
if (setenv("LISTEN_PIDFDID", spidfdid, /* override= */ true) < 0) {
log_debug_errno(errno,
"Failed to set environment variable 'LISTEN_PIDFDID': %m");
_exit(EXIT_FAILURE);
}
}
STRV_FOREACH_PAIR(a, b, setenv_list) {
if (setenv(*a, *b, /* overwrite= */ true) < 0) {
if (setenv(*a, *b, /* override= */ true) < 0) {
log_debug_errno(errno, "Failed to set environment variable '%s': %m", *a);
_exit(EXIT_FAILURE);
}

View File

@ -142,6 +142,7 @@ static int start_one_worker(Manager *m) {
if (r < 0)
return log_error_errno(r, "Failed to fork new worker child: %m");
if (r == 0) {
char pids[DECIMAL_STR_MAX(pid_t)];
/* Child */
if (m->listen_fd == 3) {
@ -159,17 +160,19 @@ static int start_one_worker(Manager *m) {
safe_close(m->listen_fd);
}
r = setenvf("LISTEN_PID", /* overwrite= */ true, PID_FMT, pid);
if (r < 0) {
log_error_errno(r, "Failed to set $LISTEN_PID: %m");
xsprintf(pids, PID_FMT, pid);
if (setenv("LISTEN_PID", pids, 1) < 0) {
log_error_errno(errno, "Failed to set $LISTEN_PID: %m");
_exit(EXIT_FAILURE);
}
uint64_t pidfdid;
if (pidfd_get_inode_id_self_cached(&pidfdid) >= 0) {
r = setenvf("LISTEN_PIDFDID", /* overwrite= */ true, "%" PRIu64, pidfdid);
if (r < 0) {
log_error_errno(r, "Failed to set $LISTEN_PIDFDID: %m");
char pidfdids[DECIMAL_STR_MAX(uint64_t)];
xsprintf(pidfdids, "%" PRIu64, pidfdid);
if (setenv("LISTEN_PIDFDID", pidfdids, 1) < 0) {
log_error_errno(errno, "Failed to set $LISTEN_PIDFDID: %m");
_exit(EXIT_FAILURE);
}
}

View File

@ -427,8 +427,6 @@ static int help(void) {
" --overlay-ro=PATH[:PATH...]:PATH\n"
" Similar, but creates a read-only overlay mount\n"
" --bind-user=NAME Bind user from host to container\n"
" --bind-user-shell=BOOL|PATH\n"
" Configure the shell to use for --bind-user= users\n"
"\n%3$sInput/Output:%4$s\n"
" --console=MODE Select how stdin/stdout/stderr and /dev/console are\n"
" set up for the container.\n"
@ -4019,7 +4017,6 @@ static int outer_child(
arg_bind_user,
arg_bind_user_shell,
arg_bind_user_shell_copy,
"/run/host/home",
&bind_user_context);
if (r < 0)
return r;
@ -6010,7 +6007,7 @@ static int run(int argc, char *argv[]) {
goto finish;
}
userns_fd = nsresource_allocate_userns(userns_name, NSRESOURCE_UIDS_64K); /* allocate 64K UIDs */
userns_fd = nsresource_allocate_userns(userns_name, UINT64_C(0x10000));
if (userns_fd < 0) {
r = log_error_errno(userns_fd, "Failed to allocate user namespace with 64K users: %m");
goto finish;
@ -6022,7 +6019,7 @@ static int run(int argc, char *argv[]) {
goto finish;
}
arg_uid_range = NSRESOURCE_UIDS_64K;
arg_uid_range = UINT32_C(0x10000);
}
if (arg_directory) {

View File

@ -178,6 +178,7 @@ static int start_one_worker(Manager *m) {
if (r < 0)
return log_error_errno(r, "Failed to fork new worker child: %m");
if (r == 0) {
char pids[DECIMAL_STR_MAX(pid_t)];
/* Child */
if (m->listen_fd == 3) {
@ -195,17 +196,19 @@ static int start_one_worker(Manager *m) {
safe_close(m->listen_fd);
}
r = setenvf("LISTEN_PID", /* overwrite= */ true, PID_FMT, pid);
if (r < 0) {
log_error_errno(r, "Failed to set $LISTEN_PID: %m");
xsprintf(pids, PID_FMT, pid);
if (setenv("LISTEN_PID", pids, 1) < 0) {
log_error_errno(errno, "Failed to set $LISTEN_PID: %m");
_exit(EXIT_FAILURE);
}
uint64_t pidfdid;
if (pidfd_get_inode_id_self_cached(&pidfdid) >= 0) {
r = setenvf("LISTEN_PIDFDID", /* overwrite= */ true, "%" PRIu64, pidfdid);
if (r < 0) {
log_error_errno(r, "Failed to set $LISTEN_PIDFDID: %m");
char pidfdids[DECIMAL_STR_MAX(uint64_t)];
xsprintf(pidfdids, "%" PRIu64, pidfdid);
if (setenv("LISTEN_PIDFDID", pidfdids, 1) < 0) {
log_error_errno(errno, "Failed to set $LISTEN_PIDFDID: %m");
_exit(EXIT_FAILURE);
}
}

View File

@ -657,10 +657,7 @@ static int hardlink_context_setup(
return -errno;
}
if (to)
r = tempfn_random_child(to, "hardlink", &c->subdir);
else
r = tempfn_random("hardlink", /* extra= */ NULL, &c->subdir);
if (r < 0)
return r;
@ -1130,6 +1127,7 @@ static int fd_copy_directory(
int r;
assert(st);
assert(to);
if (depth_left == 0)
return -ENAMETOOLONG;
@ -1259,7 +1257,7 @@ static int fd_copy_directory(
}
finish:
if (FLAGS_SET(copy_flags, COPY_MERGE_APPLY_STAT) || !exists) {
if (!exists) {
if (fchown(fdt,
uid_is_valid(override_uid) ? override_uid : st->st_uid,
gid_is_valid(override_gid) ? override_gid : st->st_gid) < 0)
@ -1347,10 +1345,6 @@ static int fd_copy_tree_generic(
override_gid, copy_flags, denylist, subvolumes, hardlink_context,
display_path, progress_path, progress_bytes, userdata);
/* Only if we are copying a directory we are fine if the target dir is referenced by fd only */
if (!to)
return -ENOTDIR;
DenyType t = PTR_TO_INT(hashmap_get(denylist, st));
if (t == DENY_INODE) {
log_debug("%s is in the denylist, ignoring", from ?: "file to copy");
@ -1388,6 +1382,7 @@ int copy_tree_at_full(
struct stat st;
int r;
assert(to);
assert(!FLAGS_SET(copy_flags, COPY_LOCK_BSD));
if (fstatat(fdf, strempty(from), &st, AT_SYMLINK_NOFOLLOW | (isempty(from) ? AT_EMPTY_PATH : 0)) < 0)

View File

@ -33,7 +33,6 @@ typedef enum CopyFlags {
*/
COPY_NOCOW_AFTER = 1 << 20,
COPY_PRESERVE_FS_VERITY = 1 << 21, /* Preserve fs-verity when copying. */
COPY_MERGE_APPLY_STAT = 1 << 22, /* When we reuse an existing directory inode, apply source ownership/mode/xattrs/timestamps */
} CopyFlags;
typedef enum DenyType {

View File

@ -110,7 +110,7 @@ static const char *const image_root_table[_IMAGE_CLASS_MAX] = {
[IMAGE_CONFEXT] = "/var/lib/confexts",
};
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(image_root, ImageClass);
DEFINE_STRING_TABLE_LOOKUP_TO_STRING(image_root, ImageClass);
static const char *const image_root_runtime_table[_IMAGE_CLASS_MAX] = {
[IMAGE_MACHINE] = "/run/machines",
@ -119,7 +119,7 @@ static const char *const image_root_runtime_table[_IMAGE_CLASS_MAX] = {
[IMAGE_CONFEXT] = "/run/confexts",
};
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(image_root_runtime, ImageClass);
DEFINE_STRING_TABLE_LOOKUP_TO_STRING(image_root_runtime, ImageClass);
static const char *const image_dirname_table[_IMAGE_CLASS_MAX] = {
[IMAGE_MACHINE] = "machines",
@ -2022,45 +2022,3 @@ static const char* const image_type_table[_IMAGE_TYPE_MAX] = {
};
DEFINE_STRING_TABLE_LOOKUP(image_type, ImageType);
int image_root_pick(
RuntimeScope scope,
ImageClass c,
bool runtime,
char **ret) {
int r;
assert(scope >= 0);
assert(scope < _RUNTIME_SCOPE_MAX);
assert(c >= 0);
assert(c < _IMAGE_CLASS_MAX);
assert(ret);
/* Picks the primary target directory for downloads, depending on invocation contexts */
_cleanup_free_ char *s = NULL;
switch (scope) {
case RUNTIME_SCOPE_SYSTEM: {
s = strdup(runtime ? image_root_runtime_to_string(c) : image_root_to_string(c));
if (!s)
return -ENOMEM;
break;
}
case RUNTIME_SCOPE_USER:
r = sd_path_lookup(runtime ? SD_PATH_USER_RUNTIME : SD_PATH_USER_STATE_PRIVATE, "machines", &s);
if (r < 0)
return r;
break;
default:
return -EOPNOTSUPP;
}
*ret = TAKE_PTR(s);
return 0;
}

View File

@ -95,7 +95,8 @@ bool image_is_host(const struct Image *i);
int image_to_json(const struct Image *i, sd_json_variant **ret);
int image_root_pick(RuntimeScope scope, ImageClass c, bool runtime, char **ret);
const char* image_root_to_string(ImageClass c) _const_;
const char* image_root_runtime_to_string(ImageClass c) _const_;
extern const struct hash_ops image_hash_ops;

View File

@ -4848,17 +4848,14 @@ int mountfsd_mount_image(
#endif
}
int mountfsd_mount_directory_fd(
int directory_fd,
int mountfsd_mount_directory(
const char *path,
int userns_fd,
DissectImageFlags flags,
int *ret_mount_fd) {
int r;
assert(directory_fd >= 0);
assert(ret_mount_fd);
/* Pick one identity, not both, that makes no sense. */
assert(!FLAGS_SET(flags, DISSECT_IMAGE_FOREIGN_UID|DISSECT_IMAGE_IDENTITY_UID));
@ -4875,6 +4872,10 @@ int mountfsd_mount_directory_fd(
if (r < 0)
return log_error_errno(r, "Failed to enable varlink fd passing for write: %m");
_cleanup_close_ int directory_fd = open(path, O_DIRECTORY|O_RDONLY|O_CLOEXEC|O_PATH);
if (directory_fd < 0)
return log_error_errno(errno, "Failed to open '%s': %m", path);
r = sd_varlink_push_dup_fd(vl, directory_fd);
if (r < 0)
return log_error_errno(r, "Failed to push directory fd into varlink connection: %m");
@ -4918,103 +4919,3 @@ int mountfsd_mount_directory_fd(
*ret_mount_fd = TAKE_FD(fsmount_fd);
return 0;
}
int mountfsd_mount_directory(
const char *path,
int userns_fd,
DissectImageFlags flags,
int *ret_mount_fd) {
assert(path);
assert(ret_mount_fd);
_cleanup_close_ int directory_fd = open(path, O_DIRECTORY|O_RDONLY|O_CLOEXEC|O_PATH);
if (directory_fd < 0)
return log_error_errno(errno, "Failed to open '%s': %m", path);
return mountfsd_mount_directory_fd(directory_fd, userns_fd, flags, ret_mount_fd);
}
int mountfsd_make_directory_fd(
int parent_fd,
const char *name,
DissectImageFlags flags,
int *ret_directory_fd) {
int r;
assert(parent_fd >= 0);
assert(name);
assert(ret_directory_fd);
_cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL;
r = sd_varlink_connect_address(&vl, "/run/systemd/io.systemd.MountFileSystem");
if (r < 0)
return log_error_errno(r, "Failed to connect to mountfsd: %m");
r = sd_varlink_set_allow_fd_passing_input(vl, true);
if (r < 0)
return log_error_errno(r, "Failed to enable varlink fd passing for read: %m");
r = sd_varlink_set_allow_fd_passing_output(vl, true);
if (r < 0)
return log_error_errno(r, "Failed to enable varlink fd passing for write: %m");
r = sd_varlink_push_dup_fd(vl, parent_fd);
if (r < 0)
return log_error_errno(r, "Failed to push parent fd into varlink connection: %m");
sd_json_variant *reply = NULL;
const char *error_id = NULL;
r = varlink_callbo_and_log(
vl,
"io.systemd.MountFileSystem.MakeDirectory",
&reply,
&error_id,
SD_JSON_BUILD_PAIR_UNSIGNED("parentFileDescriptor", 0),
SD_JSON_BUILD_PAIR_STRING("name", name),
SD_JSON_BUILD_PAIR_BOOLEAN("allowInteractiveAuthentication", FLAGS_SET(flags, DISSECT_IMAGE_ALLOW_INTERACTIVE_AUTH)));
if (r < 0)
return r;
static const sd_json_dispatch_field dispatch_table[] = {
{ "directoryFileDescriptor", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint, 0, SD_JSON_MANDATORY },
{}
};
unsigned directory_fd_idx = UINT_MAX;
r = sd_json_dispatch(reply, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &directory_fd_idx);
if (r < 0)
return log_error_errno(r, "Failed to parse MountImage() reply: %m");
_cleanup_close_ int directory_fd = sd_varlink_take_fd(vl, directory_fd_idx);
if (directory_fd < 0)
return log_error_errno(directory_fd, "Failed to take directory fd from Varlink connection: %m");
*ret_directory_fd = TAKE_FD(directory_fd);
return 0;
}
int mountfsd_make_directory(
const char *path,
DissectImageFlags flags,
int *ret_directory_fd) {
int r;
_cleanup_free_ char *parent = NULL;
r = path_extract_directory(path, &parent);
if (r < 0)
return log_error_errno(r, "Failed to extract parent directory from '%s': %m", path);
_cleanup_free_ char *dirname = NULL;
r = path_extract_filename(path, &dirname);
if (r < 0)
return log_error_errno(r, "Failed to extract directory name from '%s': %m", path);
_cleanup_close_ int fd = open(parent, O_DIRECTORY|O_CLOEXEC);
if (fd < 0)
return log_error_errno(r, "Failed to open '%s': %m", parent);
return mountfsd_make_directory_fd(fd, dirname, flags, ret_directory_fd);
}

View File

@ -258,8 +258,4 @@ static inline const char* dissected_partition_fstype(const DissectedPartition *m
int get_common_dissect_directory(char **ret);
int mountfsd_mount_image(const char *path, int userns_fd, const ImagePolicy *image_policy, const VeritySettings *verity, DissectImageFlags flags, DissectedImage **ret);
int mountfsd_mount_directory_fd(int directory_fd, int userns_fd, DissectImageFlags flags, int *ret_mount_fd);
int mountfsd_mount_directory(const char *path, int userns_fd, DissectImageFlags flags, int *ret_mount_fd);
int mountfsd_make_directory_fd(int parent_fd, const char *name, DissectImageFlags flags, int *ret_directory_fd);
int mountfsd_make_directory(const char *path, DissectImageFlags flags, int *ret_directory_fd);

View File

@ -95,11 +95,6 @@ static int unlinkat_maybe_dir(int dirfd, const char *pathname) {
return 0;
}
static bool need_opath(InstallFileFlags flags) {
/* Returns true when we need to pin the source file via an O_PATH fd */
return (flags & (INSTALL_FSYNC|INSTALL_FSYNC_FULL|INSTALL_SYNCFS|INSTALL_READ_ONLY)) != 0;
}
int install_file(int source_atfd, const char *source_name,
int target_atfd, const char *target_name,
InstallFileFlags flags) {
@ -123,7 +118,7 @@ int install_file(int source_atfd, const char *source_name,
* already in place, and only the syncing/read-only marking shall be applied. Note that with
* target_name=NULL and flags=0 this call is a NOP */
if (need_opath(flags)) {
if ((flags & (INSTALL_FSYNC|INSTALL_FSYNC_FULL|INSTALL_SYNCFS|INSTALL_READ_ONLY)) != 0) {
_cleanup_close_ int pfd = -EBADF;
struct stat st;
@ -142,31 +137,23 @@ int install_file(int source_atfd, const char *source_name,
_cleanup_close_ int regfd = -EBADF;
regfd = fd_reopen(pfd, O_RDONLY|O_CLOEXEC);
if (regfd < 0) {
if (!FLAGS_SET(flags, INSTALL_GRACEFUL))
return log_debug_errno(regfd, "Failed to open referenced inode: %m");
if (regfd < 0)
return regfd;
log_debug_errno(regfd, "Failed to open referenced inode, ignoring: %m");
} else {
if ((flags & (INSTALL_FSYNC_FULL|INSTALL_SYNCFS)) != 0)
/* If this is just a regular file (as opposed to a fully populated
* directory) let's downgrade INSTALL_SYNCFS to INSTALL_FSYNC_FULL,
* after all this is going to be a single inode we install */
if ((flags & (INSTALL_FSYNC_FULL|INSTALL_SYNCFS)) != 0) {
/* If this is just a regular file (as oppose to a fully populated directory)
* let's downgrade INSTALL_SYNCFS to INSTALL_FSYNC_FULL, after all this is
* going to be a single inode we install */
r = fsync_full(regfd);
else if (flags & INSTALL_FSYNC)
r = RET_NERRNO(fsync(regfd));
else
r = 0;
if (r < 0) {
if (!FLAGS_SET(flags, INSTALL_GRACEFUL))
return log_debug_errno(r, "Failed to sync source inode: %m");
log_debug_errno(r, "Failed to sync source inode, ignoring: %m");
if (r < 0)
return r;
} else if (flags & INSTALL_FSYNC) {
if (fsync(regfd) < 0)
return -errno;
}
if (flags & INSTALL_READ_ONLY)
rofd = TAKE_FD(regfd);
}
break;
}
@ -175,30 +162,23 @@ int install_file(int source_atfd, const char *source_name,
_cleanup_close_ int dfd = -EBADF;
dfd = fd_reopen(pfd, O_RDONLY|O_DIRECTORY|O_CLOEXEC);
if (dfd < 0) {
if (!FLAGS_SET(flags, INSTALL_GRACEFUL))
return log_debug_errno(dfd, "Failed to open referenced inode: %m");
if (dfd < 0)
return dfd;
log_debug_errno(dfd, "Failed to open referenced inode, ignoring: %m");
} else {
if (flags & INSTALL_SYNCFS)
r = RET_NERRNO(syncfs(dfd));
else if (flags & INSTALL_FSYNC_FULL)
if (flags & INSTALL_SYNCFS) {
if (syncfs(dfd) < 0)
return -errno;
} else if (flags & INSTALL_FSYNC_FULL) {
r = fsync_full(dfd);
else if (flags & INSTALL_FSYNC)
r = RET_NERRNO(fsync(dfd));
else
r = 0;
if (r < 0) {
if (!FLAGS_SET(flags, INSTALL_GRACEFUL))
return log_debug_errno(r, "Failed to sync source inode: %m");
log_debug_errno(r, "Failed to sync source inode, ignoring: %m");
if (r < 0)
return r;
} else if (flags & INSTALL_FSYNC) {
if (fsync(dfd) < 0)
return -errno;
}
if (flags & INSTALL_READ_ONLY)
rofd = TAKE_FD(dfd);
}
break;
}
@ -210,12 +190,8 @@ int install_file(int source_atfd, const char *source_name,
if (target_name && (flags & (INSTALL_FSYNC_FULL|INSTALL_SYNCFS)) != 0) {
r = fsync_directory_of_file(pfd);
if (r < 0) {
if (!FLAGS_SET(flags, INSTALL_GRACEFUL))
return log_debug_errno(r, "Failed to sync source inode: %m");
log_debug_errno(r, "Failed to sync source inode, ignoring: %m");
}
if (r < 0)
return r;
}
break;
@ -278,12 +254,8 @@ int install_file(int source_atfd, const char *source_name,
if (rofd >= 0) {
r = fs_make_very_read_only(rofd);
if (r < 0) {
if (!FLAGS_SET(flags, INSTALL_GRACEFUL))
return log_debug_errno(r, "Failed to make inode read-only: %m");
log_debug_errno(r, "Failed to make inode read-only, ignoring: %m");
}
if (r < 0)
return r;
}
if ((flags & (INSTALL_FSYNC_FULL|INSTALL_SYNCFS)) != 0) {
@ -291,12 +263,8 @@ int install_file(int source_atfd, const char *source_name,
r = fsync_parent_at(target_atfd, target_name);
else
r = fsync_parent_at(source_atfd, source_name);
if (r < 0) {
if (!FLAGS_SET(flags, INSTALL_GRACEFUL))
return log_debug_errno(r, "Failed to sync inode: %m");
log_debug_errno(r, "Failed to sync inode, ignoring: %m");
}
if (r < 0)
return r;
}
return 0;

View File

@ -9,7 +9,6 @@ typedef enum InstallFileFlags {
INSTALL_FSYNC = 1 << 2, /* fsync() file contents before moving file in */
INSTALL_FSYNC_FULL = 1 << 3, /* like INSTALL_FSYNC, but also fsync() parent dir before+after moving file in */
INSTALL_SYNCFS = 1 << 4, /* syncfs() before moving file in, fsync() parent dir after moving file in */
INSTALL_GRACEFUL = 1 << 5, /* don't fail if we cannot sync or mark read-only */
} InstallFileFlags;
int install_file(int source_atfd, const char *source_name, int target_atfd, const char *target_name, InstallFileFlags flags);

View File

@ -1,31 +0,0 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "env-util.h"
#include "log.h"
#include "log-assert-critical.h"
static bool assert_return_is_critical = BUILD_MODE_DEVELOPER;
void log_set_assert_return_is_critical(bool b) {
assert_return_is_critical = b;
}
void log_set_assert_return_is_critical_from_env(void) {
static int cached = INT_MIN;
int r;
if (cached == INT_MIN) {
r = secure_getenv_bool("SYSTEMD_ASSERT_RETURN_IS_CRITICAL");
if (r < 0 && r != -ENXIO)
log_debug_errno(r, "Failed to parse $SYSTEMD_ASSERT_RETURN_IS_CRITICAL, ignoring: %m");
cached = r;
}
if (cached >= 0)
log_set_assert_return_is_critical(cached);
}
bool log_get_assert_return_is_critical(void) {
return assert_return_is_critical;
}

View File

@ -1,7 +0,0 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "basic-forward.h"
void log_set_assert_return_is_critical(bool b);
void log_set_assert_return_is_critical_from_env(void);

View File

@ -33,14 +33,14 @@ static int check_etc_passwd_collisions(
if (r == -ENOENT)
return 0; /* no user database? then no user, hence no collision */
if (r < 0)
return log_error_errno(r, "Failed to open /etc/passwd of machine: %m");
return log_error_errno(r, "Failed to open /etc/passwd of container: %m");
for (;;) {
struct passwd *pw;
r = fgetpwent_sane(f, &pw);
if (r < 0)
return log_error_errno(r, "Failed to iterate through /etc/passwd of machine: %m");
return log_error_errno(r, "Failed to iterate through /etc/passwd of container: %m");
if (r == 0) /* EOF */
return 0; /* no collision */
@ -68,14 +68,14 @@ static int check_etc_group_collisions(
if (r == -ENOENT)
return 0; /* no group database? then no group, hence no collision */
if (r < 0)
return log_error_errno(r, "Failed to open /etc/group of machine: %m");
return log_error_errno(r, "Failed to open /etc/group of container: %m");
for (;;) {
struct group *gr;
r = fgetgrent_sane(f, &gr);
if (r < 0)
return log_error_errno(r, "Failed to iterate through /etc/group of machine: %m");
return log_error_errno(r, "Failed to iterate through /etc/group of container: %m");
if (r == 0)
return 0; /* no collision */
@ -93,7 +93,6 @@ static int convert_user(
uid_t allocate_uid,
const char *shell,
bool shell_copy,
const char *home_mount_directory,
UserRecord **ret_converted_user,
GroupRecord **ret_converted_group) {
@ -115,16 +114,16 @@ static int convert_user(
return r;
if (r > 0)
return log_error_errno(SYNTHETIC_ERRNO(EBUSY),
"Sorry, the user '%s' already exists in the machine.", u->user_name);
"Sorry, the user '%s' already exists in the container.", u->user_name);
r = check_etc_group_collisions(directory, g->group_name, GID_INVALID);
if (r < 0)
return r;
if (r > 0)
return log_error_errno(SYNTHETIC_ERRNO(EBUSY),
"Sorry, the group '%s' already exists in the machine.", g->group_name);
"Sorry, the group '%s' already exists in the container.", g->group_name);
h = path_join(home_mount_directory, u->user_name);
h = path_join("/run/host/home/", u->user_name);
if (!h)
return log_oom();
@ -149,7 +148,7 @@ static int convert_user(
SD_JSON_BUILD_PAIR_CONDITION(!strv_isempty(u->hashed_password), "hashedPassword", SD_JSON_BUILD_VARIANT(hp)),
SD_JSON_BUILD_PAIR_CONDITION(!!ssh, "sshAuthorizedKeys", SD_JSON_BUILD_VARIANT(ssh))))));
if (r < 0)
return log_error_errno(r, "Failed to build machine user record: %m");
return log_error_errno(r, "Failed to build container user record: %m");
r = group_record_build(
&converted_group,
@ -159,7 +158,7 @@ static int convert_user(
SD_JSON_BUILD_PAIR_CONDITION(g->disposition >= 0, "disposition", SD_JSON_BUILD_STRING(user_disposition_to_string(g->disposition))),
SD_JSON_BUILD_PAIR("service", JSON_BUILD_CONST_STRING("io.systemd.NSpawn"))));
if (r < 0)
return log_error_errno(r, "Failed to build machine group record: %m");
return log_error_errno(r, "Failed to build container group record: %m");
*ret_converted_user = TAKE_PTR(converted_user);
*ret_converted_group = TAKE_PTR(converted_group);
@ -176,7 +175,7 @@ static int find_free_uid(const char *directory, uid_t *current_uid) {
if (*current_uid > MAP_UID_MAX)
return log_error_errno(
SYNTHETIC_ERRNO(EBUSY),
"No suitable available UID in range " UID_FMT "" UID_FMT " in machine detected, can't map user.",
"No suitable available UID in range " UID_FMT "" UID_FMT " in container detected, can't map user.",
MAP_UID_MIN, MAP_UID_MAX);
r = check_etc_passwd_collisions(directory, NULL, *current_uid);
@ -211,7 +210,6 @@ int machine_bind_user_prepare(
char **bind_user,
const char *bind_user_shell,
bool bind_user_shell_copy,
const char *bind_user_home_mount_directory,
MachineBindUserContext **ret) {
_cleanup_(machine_bind_user_context_freep) MachineBindUserContext *c = NULL;
@ -221,7 +219,7 @@ int machine_bind_user_prepare(
assert(ret);
/* This resolves the users specified in 'bind_user', generates a minimalized JSON user + group record
* for it to stick in the machine, allocates a UID/GID for it, and updates the custom mount table,
* for it to stick in the container, allocates a UID/GID for it, and updates the custom mount table,
* to include an appropriate bind mount mapping.
*
* This extends the passed custom_mounts/n_custom_mounts with the home directories, and allocates a
@ -266,13 +264,13 @@ int machine_bind_user_prepare(
if (r < 0)
return log_error_errno(r, "Failed to resolve group of user '%s': %m", u->user_name);
/* We want to synthesize exactly one user + group from the host into the machine. This only
/* We want to synthesize exactly one user + group from the host into the container. This only
* makes sense if the user on the host has its own private group. We can't reasonably check
* this, so we just check of the name of user and group match.
*
* One of these days we might want to support users in a shared/common group too, but it's
* not clear to me how this would have to be mapped, precisely given that the common group
* probably already exists in the machine. */
* probably already exists in the container. */
if (!streq(u->user_name, g->group_name))
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"Sorry, mapping users without private groups is currently not supported.");
@ -281,14 +279,7 @@ int machine_bind_user_prepare(
if (r < 0)
return r;
r = convert_user(
directory,
u, g,
current_uid,
bind_user_shell,
bind_user_shell_copy,
bind_user_home_mount_directory,
&cu, &cg);
r = convert_user(directory, u, g, current_uid, bind_user_shell, bind_user_shell_copy, &cu, &cg);
if (r < 0)
return r;

View File

@ -27,5 +27,4 @@ int machine_bind_user_prepare(
char **bind_user,
const char *bind_user_shell,
bool bind_user_shell_copy,
const char *bind_user_home_mount_directory,
MachineBindUserContext **ret);

View File

@ -119,7 +119,6 @@ shared_sources = files(
'libmount-util.c',
'local-addresses.c',
'locale-setup.c',
'log-assert-critical.c',
'logs-show.c',
'loop-util.c',
'loopback-setup.c',

View File

@ -3,10 +3,6 @@
#include "shared-forward.h"
/* Helpful constants for the only numbers of UIDs that can currently be allocated */
#define NSRESOURCE_UIDS_64K 0x10000U
#define NSRESOURCE_UIDS_1 1U
int nsresource_allocate_userns(const char *name, uint64_t size);
int nsresource_register_userns(const char *name, int userns_fd);
int nsresource_add_mount(int userns_fd, int mount_fd);

View File

@ -6,7 +6,6 @@
#include "assert-util.h"
#include "log.h"
#include "log-assert-critical.h"
#include "nss-util.h"
sd_json_dispatch_flags_t nss_json_dispatch_flags = SD_JSON_ALLOW_EXTENSIONS;

View File

@ -7,7 +7,6 @@
#include "errno-util.h"
#include "shared-forward.h"
#include "log.h"
#include "log-assert-critical.h"
#include "static-destruct.h"
#include "signal-util.h"
#include "stdio-util.h"

View File

@ -7,7 +7,7 @@
#include "string-util.h"
#include "vmspawn-mount.h"
void runtime_mount_done(RuntimeMount *mount) {
static void runtime_mount_done(RuntimeMount *mount) {
assert(mount);
mount->source = mfree(mount->source);
@ -24,11 +24,7 @@ void runtime_mount_context_done(RuntimeMountContext *ctx) {
}
int runtime_mount_parse(RuntimeMountContext *ctx, const char *s, bool read_only) {
_cleanup_(runtime_mount_done) RuntimeMount mount = {
.read_only = read_only,
.source_uid = UID_INVALID,
.target_uid = UID_INVALID,
};
_cleanup_(runtime_mount_done) RuntimeMount mount = { .read_only = read_only };
_cleanup_free_ char *source_rel = NULL;
int r;

View File

@ -6,9 +6,7 @@
typedef struct RuntimeMount {
bool read_only;
char *source;
uid_t source_uid;
char *target;
uid_t target_uid;
} RuntimeMount;
typedef struct RuntimeMountContext {
@ -16,6 +14,5 @@ typedef struct RuntimeMountContext {
size_t n_mounts;
} RuntimeMountContext;
void runtime_mount_done(RuntimeMount *mount);
void runtime_mount_context_done(RuntimeMountContext *ctx);
int runtime_mount_parse(RuntimeMountContext *ctx, const char *s, bool read_only);

View File

@ -35,13 +35,11 @@
#include "format-util.h"
#include "fs-util.h"
#include "gpt.h"
#include "group-record.h"
#include "hexdecoct.h"
#include "hostname-setup.h"
#include "hostname-util.h"
#include "id128-util.h"
#include "log.h"
#include "machine-bind-user.h"
#include "machine-credential.h"
#include "main-func.h"
#include "mkdir.h"
@ -70,8 +68,6 @@
#include "terminal-util.h"
#include "tmpfile-util.h"
#include "unit-name.h"
#include "user-record.h"
#include "user-util.h"
#include "utf8.h"
#include "vmspawn-mount.h"
#include "vmspawn-register.h"
@ -140,9 +136,6 @@ static char *arg_tpm_state_path = NULL;
static TpmStateMode arg_tpm_state_mode = TPM_STATE_AUTO;
static bool arg_ask_password = true;
static bool arg_notify_ready = true;
static char **arg_bind_user = NULL;
static char *arg_bind_user_shell = NULL;
static bool arg_bind_user_shell_copy = false;
STATIC_DESTRUCTOR_REGISTER(arg_directory, freep);
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
@ -162,8 +155,6 @@ STATIC_DESTRUCTOR_REGISTER(arg_ssh_key_type, freep);
STATIC_DESTRUCTOR_REGISTER(arg_smbios11, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_tpm_state_path, freep);
STATIC_DESTRUCTOR_REGISTER(arg_property, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_bind_user, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_bind_user_shell, freep);
static int help(void) {
_cleanup_free_ char *link = NULL;
@ -224,9 +215,6 @@ static int help(void) {
" --bind-ro=SOURCE[:TARGET]\n"
" Mount a file or directory, but read-only\n"
" --extra-drive=PATH Adds an additional disk to the virtual machine\n"
" --bind-user=NAME Bind user from host to virtual machine\n"
" --bind-user-shell=BOOL|PATH\n"
" Configure the shell to use for --bind-user= users\n"
"\n%3$sIntegration:%4$s\n"
" --forward-journal=FILE|DIR\n"
" Forward the VM's journal to the host\n"
@ -301,8 +289,6 @@ static int parse_argv(int argc, char *argv[]) {
ARG_NO_ASK_PASSWORD,
ARG_PROPERTY,
ARG_NOTIFY_READY,
ARG_BIND_USER,
ARG_BIND_USER_SHELL,
};
static const struct option options[] = {
@ -352,8 +338,6 @@ static int parse_argv(int argc, char *argv[]) {
{ "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
{ "property", required_argument, NULL, ARG_PROPERTY },
{ "notify-ready", required_argument, NULL, ARG_NOTIFY_READY },
{ "bind-user", required_argument, NULL, ARG_BIND_USER },
{ "bind-user-shell", required_argument, NULL, ARG_BIND_USER_SHELL },
{}
};
@ -691,30 +675,6 @@ static int parse_argv(int argc, char *argv[]) {
break;
case ARG_BIND_USER:
if (!valid_user_group_name(optarg, /* flags= */ 0))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid user name to bind: %s", optarg);
if (strv_extend(&arg_bind_user, optarg) < 0)
return log_oom();
break;
case ARG_BIND_USER_SHELL: {
bool copy = false;
char *sh = NULL;
r = parse_user_shell(optarg, &sh, &copy);
if (r == -ENOMEM)
return log_oom();
if (r < 0)
return log_error_errno(r, "Invalid user shell to bind: %s", optarg);
free_and_replace(arg_bind_user_shell, sh);
arg_bind_user_shell_copy = copy;
break;
}
case '?':
return -EINVAL;
@ -722,12 +682,6 @@ static int parse_argv(int argc, char *argv[]) {
assert_not_reached();
}
/* Drop duplicate --bind-user= entries */
strv_uniq(arg_bind_user);
if (arg_bind_user_shell && strv_isempty(arg_bind_user))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot use --bind-user-shell= without --bind-user=");
if (argc > optind) {
arg_kernel_cmdline_extra = strv_copy(argv + optind);
if (!arg_kernel_cmdline_extra)
@ -1405,9 +1359,7 @@ static int find_virtiofsd(char **ret) {
static int start_virtiofsd(
const char *scope,
const char *directory,
uid_t source_uid,
uid_t target_uid,
uid_t uid_range,
bool uidmap,
const char *runtime_dir,
const char *sd_socket_activate,
char **ret_listen_address,
@ -1445,20 +1397,20 @@ static int start_virtiofsd(
if (!argv)
return log_oom();
if (source_uid != UID_INVALID && target_uid != UID_INVALID && uid_range != UID_INVALID) {
r = strv_extend(&argv, "--translate-uid");
if (uidmap && arg_uid_shift != UID_INVALID) {
r = strv_extend(&argv, "--uid-map");
if (r < 0)
return log_oom();
r = strv_extendf(&argv, "map:" UID_FMT ":" UID_FMT ":" UID_FMT, target_uid, source_uid, uid_range);
r = strv_extendf(&argv, ":0:" UID_FMT ":" UID_FMT ":", arg_uid_shift, arg_uid_range);
if (r < 0)
return log_oom();
r = strv_extend(&argv, "--translate-gid");
r = strv_extend(&argv, "--gid-map");
if (r < 0)
return log_oom();
r = strv_extendf(&argv, "map:" GID_FMT ":" GID_FMT ":" GID_FMT, target_uid, source_uid, uid_range);
r = strv_extendf(&argv, ":0:" GID_FMT ":" GID_FMT ":", arg_uid_shift, arg_uid_range);
if (r < 0)
return log_oom();
}
@ -1473,65 +1425,6 @@ static int start_virtiofsd(
return 0;
}
static int bind_user_setup(
const MachineBindUserContext *context,
MachineCredentialContext *credentials,
RuntimeMountContext *mounts) {
int r;
assert(credentials);
assert(mounts);
if (!context)
return 0;
FOREACH_ARRAY(bind_user, context->data, context->n_data) {
_cleanup_free_ char *formatted = NULL;
r = sd_json_variant_format(bind_user->payload_user->json, SD_JSON_FORMAT_NEWLINE, &formatted);
if (r < 0)
return log_error_errno(r, "Failed to format JSON user record: %m");
_cleanup_free_ char *cred = strjoin("userdb.transient.user.", bind_user->payload_user->user_name);
if (!cred)
return log_oom();
r = machine_credential_add(credentials, cred, formatted, SIZE_MAX);
if (r < 0)
return r;
formatted = mfree(formatted);
r = sd_json_variant_format(bind_user->payload_group->json, SD_JSON_FORMAT_NEWLINE, &formatted);
if (r < 0)
return log_error_errno(r, "Failed to format JSON group record: %m");
free(cred);
cred = strjoin("userdb.transient.group.", bind_user->payload_group->group_name);
if (!cred)
return log_oom();
r = machine_credential_add(credentials, cred, formatted, SIZE_MAX);
if (r < 0)
return r;
_cleanup_(runtime_mount_done) RuntimeMount mount = {
.source = strdup(user_record_home_directory(bind_user->host_user)),
.source_uid = bind_user->host_user->uid,
.target = strdup(user_record_home_directory(bind_user->payload_user)),
.target_uid = bind_user->payload_user->uid,
};
if (!mount.source || !mount.target)
return log_oom();
if (!GREEDY_REALLOC(mounts->mounts, mounts->n_mounts + 1))
return log_oom();
mounts->mounts[mounts->n_mounts++] = TAKE_STRUCT(mount);
}
return 0;
}
static int kernel_cmdline_maybe_append_root(void) {
int r;
bool cmdline_contains_root = strv_find_startswith(arg_kernel_cmdline_extra, "root=")
@ -1833,21 +1726,6 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
if (r < 0)
return log_error_errno(r, "Failed to find OVMF config: %m");
_cleanup_(machine_bind_user_context_freep) MachineBindUserContext *bind_user_context = NULL;
r = machine_bind_user_prepare(
/* directory= */ NULL,
arg_bind_user,
arg_bind_user_shell,
arg_bind_user_shell_copy,
"/run/vmhost/home",
&bind_user_context);
if (r < 0)
return r;
r = bind_user_setup(bind_user_context, &arg_credentials, &arg_runtime_mounts);
if (r < 0)
return r;
/* only warn if the user hasn't disabled secureboot */
if (!ovmf_config->supports_sb && arg_secure_boot)
log_warning("Couldn't find OVMF firmware blob with Secure Boot support, "
@ -2299,9 +2177,7 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
r = start_virtiofsd(
unit,
arg_directory,
/* source_uid= */ arg_uid_shift,
/* target_uid= */ 0,
/* uid_range= */ arg_uid_range,
/* uidmap= */ true,
runtime_dir,
sd_socket_activate,
&listen_address,
@ -2391,9 +2267,7 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
r = start_virtiofsd(
unit,
mount->source,
/* source_uid= */ mount->source_uid,
/* target_uid= */ mount->target_uid,
/* uid_range= */ 1U,
/* uidmap= */ false,
runtime_dir,
sd_socket_activate,
&listen_address,
@ -2567,7 +2441,7 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
}
if (arg_forward_journal) {
_cleanup_free_ char *listen_address = NULL;
_cleanup_free_ char *listen_address = NULL, *cred = NULL;
if (!GREEDY_REALLOC(children, n_children + 1))
return log_oom();
@ -2585,7 +2459,11 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
pidref_done(&child);
children[n_children++] = TAKE_PTR(source);
r = machine_credential_add(&arg_credentials, "journal.forward_to_socket", listen_address, SIZE_MAX);
cred = strjoin("journal.forward_to_socket:", listen_address);
if (!cred)
return log_oom();
r = machine_credential_set(&arg_credentials, cred);
if (r < 0)
return r;
}
@ -2631,14 +2509,13 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
/* on distros that provide their own sshd@.service file we need to provide a dropin which
* picks up our public key credential */
r = machine_credential_add(
r = machine_credential_set(
&arg_credentials,
"systemd.unit-dropin.sshd-vsock@.service",
"systemd.unit-dropin.sshd-vsock@.service:"
"[Service]\n"
"ExecStart=\n"
"ExecStart=-sshd -i -o 'AuthorizedKeysFile=%d/ssh.ephemeral-authorized_keys-all .ssh/authorized_keys'\n"
"ImportCredential=ssh.ephemeral-authorized_keys-all\n",
SIZE_MAX);
"ImportCredential=ssh.ephemeral-authorized_keys-all\n");
if (r < 0)
return log_error_errno(r, "Failed to set credential systemd.unit-dropin.sshd-vsock@.service: %m");
}

View File

@ -28,16 +28,6 @@ units = [
'conditions' : ['ENABLE_MACHINED'],
'symlinks' : ['sockets.target.wants/'],
},
{
'file' : 'systemd-importd.service.in',
'conditions' : ['ENABLE_IMPORTD'],
'symlinks' : ['dbus-org.freedesktop.import1.service'],
},
{
'file' : 'systemd-importd.socket',
'conditions' : ['ENABLE_IMPORTD'],
'symlinks' : ['sockets.target.wants/'],
},
{ 'file' : 'paths.target' },
{ 'file' : 'printer.target' },
{ 'file' : 'session.slice' },

View File

@ -1,28 +0,0 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
[Unit]
Description=Disk Image Download Service
Documentation=man:systemd-importd.service(8)
Documentation=man:org.freedesktop.import1(5)
[Service]
Type=notify
ExecStart={{LIBEXECDIR}}/systemd-importd --user
BusName=org.freedesktop.import1
KillMode=mixed
NoNewPrivileges=yes
MemoryDenyWriteExecute=yes
RestrictRealtime=yes
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
SystemCallFilter=@system-service @mount
SystemCallErrorNumber=EPERM
SystemCallArchitectures=native
LockPersonality=yes
{{SERVICE_WATCHDOG}}

View File

@ -1,18 +0,0 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
[Unit]
Description=Disk Image Download Service Socket
Documentation=man:systemd-importd.service(8)
Documentation=man:org.freedesktop.import1(5)
[Socket]
ListenStream=%t/systemd/io.systemd.Import
FileDescriptorName=varlink
SocketMode=0600