From 64db44f7fb556dcf72f418ab4c62ce85271bf4d2 Mon Sep 17 00:00:00 2001 From: Ivan Kruglov Date: Tue, 7 Jan 2025 12:58:46 +0100 Subject: [PATCH 1/6] process-util: read_errno() --- src/basic/process-util.c | 23 +++++++++++++++++++++++ src/basic/process-util.h | 1 + 2 files changed, 24 insertions(+) diff --git a/src/basic/process-util.c b/src/basic/process-util.c index e3c696a3a6c..7a4d0b30e49 100644 --- a/src/basic/process-util.c +++ b/src/basic/process-util.c @@ -2215,3 +2215,26 @@ _noreturn_ void report_errno_and_exit(int errno_fd, int error) { _exit(EXIT_FAILURE); } + +int read_errno(int errno_fd) { + int r; + + assert(errno_fd >= 0); + + ssize_t n = loop_read(errno_fd, &r, sizeof(r), /* do_pool = */ 0); + if (n < 0) { + log_debug_errno(n, "Failed to read errno: %m"); + return -EIO; + } + if (n == sizeof(r)) { + /* child processes reported a error, return it */ + if (r < 0) + return log_debug_errno(r, "Child process failed with errno: %m"); + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Received a errno, but it's a positive value"); + } + if (n != 0) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Received unexpected amount of bytes while reading errno"); + + /* the process exited without reporting a error, assuming success */ + return 0; +} diff --git a/src/basic/process-util.h b/src/basic/process-util.h index b436a7b1b31..8864472af64 100644 --- a/src/basic/process-util.h +++ b/src/basic/process-util.h @@ -269,3 +269,4 @@ int proc_dir_read(DIR *d, pid_t *ret); int proc_dir_read_pidref(DIR *d, PidRef *ret); _noreturn_ void report_errno_and_exit(int errno_fd, int error); +int read_errno(int errno_fd); From 65275cfbe58109cf8970993329a829f053fa9212 Mon Sep 17 00:00:00 2001 From: Ivan Kruglov Date: Mon, 6 Jan 2025 16:15:36 +0100 Subject: [PATCH 2/6] machine: machine_open_root_directory() --- src/machine/machine.c | 93 +++++++++++++++++++++++++++++++++++++++++++ src/machine/machine.h | 2 + 2 files changed, 95 insertions(+) diff --git a/src/machine/machine.c b/src/machine/machine.c index 54a5431c19e..3fc2b58fdb4 100644 --- a/src/machine/machine.c +++ b/src/machine/machine.c @@ -1347,6 +1347,99 @@ int machine_translate_gid(Machine *machine, gid_t gid, gid_t *ret_host_gid) { return machine_translate_uid_internal(machine, "gid_map", (uid_t) gid, (uid_t*) ret_host_gid); } +int machine_open_root_directory(Machine *machine) { + int r; + + assert(machine); + + switch (machine->class) { + case MACHINE_HOST: { + int fd = open("/", O_RDONLY|O_CLOEXEC|O_DIRECTORY); + if (fd < 0) + return log_debug_errno(errno, "Failed to open host root directory: %m"); + + return fd; + } + + case MACHINE_CONTAINER: { + _cleanup_close_ int mntns_fd = -EBADF, root_fd = -EBADF; + _cleanup_close_pair_ int errno_pipe_fd[2] = EBADF_PAIR, fd_pass_socket[2] = EBADF_PAIR; + pid_t child; + + r = pidref_namespace_open(&machine->leader, + /* ret_pidns_fd = */ NULL, + &mntns_fd, + /* ret_netns_fd = */ NULL, + /* ret_userns_fd = */ NULL, + &root_fd); + if (r < 0) + return log_debug_errno(r, "Failed to open mount namespace of machine '%s': %m", machine->name); + + if (pipe2(errno_pipe_fd, O_CLOEXEC|O_NONBLOCK) < 0) + return log_debug_errno(errno, "Failed to open pipe: %m"); + + if (socketpair(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, fd_pass_socket) < 0) + return log_debug_errno(errno, "Failed to create socket pair: %m"); + + r = namespace_fork( + "(sd-openrootns)", + "(sd-openroot)", + /* except_fds = */ NULL, + /* n_except_fds = */ 0, + FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL, + /* pidns_fd = */ -EBADF, + mntns_fd, + /* netns_fd = */ -EBADF, + /* userns_fd = */ -EBADF, + root_fd, + &child); + if (r < 0) + return log_debug_errno(r, "Failed to fork into mount namespace of machine '%s': %m", machine->name); + if (r == 0) { + _cleanup_close_ int dfd = -EBADF; + + errno_pipe_fd[0] = safe_close(errno_pipe_fd[0]); + fd_pass_socket[0] = safe_close(fd_pass_socket[0]); + + dfd = open("/", O_RDONLY|O_CLOEXEC|O_DIRECTORY); + if (dfd < 0) { + log_debug_errno(dfd, "Failed to open root directory of machine '%s': %m", machine->name); + report_errno_and_exit(errno_pipe_fd[1], dfd); + } + + r = send_one_fd(fd_pass_socket[1], dfd, /* flags = */ 0); + dfd = safe_close(dfd); + if (r < 0) { + log_debug_errno(r, "Failed to send FD over socket: %m"); + report_errno_and_exit(errno_pipe_fd[1], r); + } + + _exit(EXIT_SUCCESS); + } + + errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]); + fd_pass_socket[1] = safe_close(fd_pass_socket[1]); + + r = wait_for_terminate_and_check("(sd-openrootns)", child, /* flags = */ 0); + if (r < 0) + return log_debug_errno(r, "Failed to wait for child: %m"); + + r = read_errno(errno_pipe_fd[0]); /* the function does debug reporting */ + if (r < 0) + return r; + + int fd = receive_one_fd(fd_pass_socket[0], MSG_DONTWAIT); + if (fd < 0) + return log_debug_errno(fd, "Failed to receive FD from child: %m"); + + return fd; + } + + default: + return -EOPNOTSUPP; + } +} + static const char* const machine_class_table[_MACHINE_CLASS_MAX] = { [MACHINE_CONTAINER] = "container", [MACHINE_VM] = "vm", diff --git a/src/machine/machine.h b/src/machine/machine.h index c538c671fff..614d295bcab 100644 --- a/src/machine/machine.h +++ b/src/machine/machine.h @@ -125,6 +125,8 @@ int machine_owns_gid(Machine *m, gid_t host_gid, gid_t *ret_internal_gid); int machine_translate_uid(Machine *m, uid_t internal_uid, uid_t *ret_host_uid); int machine_translate_gid(Machine *m, gid_t internal_gid, gid_t *ret_host_gid); +int machine_open_root_directory(Machine *machine); + typedef enum AcquireMetadata { ACQUIRE_METADATA_NO, ACQUIRE_METADATA_YES, From 307458a6f48ed2de26a505ecd454b66f06ba5f8f Mon Sep 17 00:00:00 2001 From: Ivan Kruglov Date: Mon, 6 Jan 2025 16:15:57 +0100 Subject: [PATCH 3/6] machine: use machine_open_root_directory() in dbus --- src/machine/machine-dbus.c | 68 +++----------------------------------- 1 file changed, 4 insertions(+), 64 deletions(-) diff --git a/src/machine/machine-dbus.c b/src/machine/machine-dbus.c index 2d05c52608c..560f9f2a804 100644 --- a/src/machine/machine-dbus.c +++ b/src/machine/machine-dbus.c @@ -595,71 +595,11 @@ int bus_machine_method_open_root_directory(sd_bus_message *message, void *userda if (r == 0) return 1; /* Will call us back */ - switch (m->class) { - - case MACHINE_HOST: - fd = open("/", O_RDONLY|O_CLOEXEC|O_DIRECTORY); - if (fd < 0) - return -errno; - - break; - - case MACHINE_CONTAINER: { - _cleanup_close_ int mntns_fd = -EBADF, root_fd = -EBADF; - _cleanup_close_pair_ int pair[2] = EBADF_PAIR; - pid_t child; - - r = pidref_namespace_open(&m->leader, - /* ret_pidns_fd = */ NULL, - &mntns_fd, - /* ret_netns_fd = */ NULL, - /* ret_userns_fd = */ NULL, - &root_fd); - if (r < 0) - return r; - - if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) < 0) - return -errno; - - r = namespace_fork("(sd-openrootns)", "(sd-openroot)", NULL, 0, FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL, - -1, mntns_fd, -1, -1, root_fd, &child); - if (r < 0) - return sd_bus_error_set_errnof(error, r, "Failed to fork(): %m"); - if (r == 0) { - _cleanup_close_ int dfd = -EBADF; - - pair[0] = safe_close(pair[0]); - - dfd = open("/", O_RDONLY|O_CLOEXEC|O_DIRECTORY); - if (dfd < 0) - _exit(EXIT_FAILURE); - - r = send_one_fd(pair[1], dfd, 0); - dfd = safe_close(dfd); - if (r < 0) - _exit(EXIT_FAILURE); - - _exit(EXIT_SUCCESS); - } - - pair[1] = safe_close(pair[1]); - - r = wait_for_terminate_and_check("(sd-openrootns)", child, 0); - if (r < 0) - return sd_bus_error_set_errnof(error, r, "Failed to wait for child: %m"); - if (r != EXIT_SUCCESS) - return sd_bus_error_set(error, SD_BUS_ERROR_FAILED, "Child died abnormally."); - - fd = receive_one_fd(pair[0], MSG_DONTWAIT); - if (fd < 0) - return fd; - - break; - } - - default: + fd = machine_open_root_directory(m); + if (ERRNO_IS_NEG_NOT_SUPPORTED(fd)) return sd_bus_error_set(error, SD_BUS_ERROR_NOT_SUPPORTED, "Opening the root directory is only supported on container machines."); - } + if (fd < 0) + return sd_bus_error_set_errnof(error, fd, "Failed to open root directory of machine '%s': %m", m->name); return sd_bus_reply_method_return(message, "h", fd); } From 8633bf583692175ccef0a47450d4a1b0491dde6e Mon Sep 17 00:00:00 2001 From: Ivan Kruglov Date: Mon, 6 Jan 2025 16:31:02 +0100 Subject: [PATCH 4/6] machine: introduce io.systemd.Machine.OpenRootDirectory --- src/machine/machine-varlink.c | 37 +++++++++++++++++++++++++ src/machine/machine-varlink.h | 1 + src/machine/machined-varlink.c | 35 +++++++++++++---------- src/shared/varlink-io.systemd.Machine.c | 8 ++++++ 4 files changed, 66 insertions(+), 15 deletions(-) diff --git a/src/machine/machine-varlink.c b/src/machine/machine-varlink.c index a889b94d804..89d047f954f 100644 --- a/src/machine/machine-varlink.c +++ b/src/machine/machine-varlink.c @@ -919,3 +919,40 @@ int vl_method_copy_internal(sd_varlink *link, sd_json_variant *parameters, sd_va op->done = copy_done; return 1; } + +int vl_method_open_root_directory_internal(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + _cleanup_close_ int fd = -EBADF; + Machine *machine = ASSERT_PTR(userdata); + Manager *manager = ASSERT_PTR(machine->manager); + int r; + + r = varlink_verify_polkit_async( + link, + manager->bus, + "org.freedesktop.machine1.manage-machines", + (const char**) STRV_MAKE("name", machine->name, + "verb", "open_root_directory"), + &manager->polkit_registry); + if (r <= 0) + return r; + + fd = machine_open_root_directory(machine); + if (ERRNO_IS_NEG_NOT_SUPPORTED(fd)) + return sd_varlink_error(link, "io.systemd.Machine.NotSupported", NULL); + if (fd < 0) + return log_debug_errno(fd, "Failed to open root directory of machine '%s': %m", machine->name); + + int fd_idx = sd_varlink_push_fd(link, fd); + /* no need to handle -EPERM because server has SD_VARLINK_SERVER_ALLOW_FD_PASSING_OUTPUT */ + if (fd_idx < 0) + return log_debug_errno(fd_idx, "Failed to push file descriptor over varlink: %m"); + + TAKE_FD(fd); + + r = sd_json_buildo(&v, SD_JSON_BUILD_PAIR_INTEGER("fileDescriptor", fd_idx)); + if (r < 0) + return r; + + return sd_varlink_reply(link, v); +} diff --git a/src/machine/machine-varlink.h b/src/machine/machine-varlink.h index bd80cb2653c..3d56e2150e9 100644 --- a/src/machine/machine-varlink.h +++ b/src/machine/machine-varlink.h @@ -29,3 +29,4 @@ int vl_method_map_from(sd_varlink *link, sd_json_variant *parameters, sd_varlink int vl_method_map_to(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); int vl_method_bind_mount(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); int vl_method_copy_internal(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata, bool copy_from); +int vl_method_open_root_directory_internal(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); diff --git a/src/machine/machined-varlink.c b/src/machine/machined-varlink.c index e0de3aca012..b94cce296d8 100644 --- a/src/machine/machined-varlink.c +++ b/src/machine/machined-varlink.c @@ -599,6 +599,10 @@ static int vl_method_copy_to(sd_varlink *link, sd_json_variant *parameters, sd_v return vl_method_copy_internal(link, parameters, flags, userdata, /* copy_from = */ false); } +static int vl_method_open_root_directory(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + return lookup_machine_and_call_method(link, parameters, flags, userdata, vl_method_open_root_directory_internal); +} + static int list_image_one_and_maybe_read_metadata(sd_varlink *link, Image *image, bool more, AcquireMetadata am) { int r; @@ -774,21 +778,22 @@ static int manager_varlink_init_machine(Manager *m) { r = sd_varlink_server_bind_method_many( s, - "io.systemd.Machine.Register", vl_method_register, - "io.systemd.Machine.List", vl_method_list, - "io.systemd.Machine.Unregister", vl_method_unregister, - "io.systemd.Machine.Terminate", vl_method_terminate, - "io.systemd.Machine.Kill", vl_method_kill, - "io.systemd.Machine.Open", vl_method_open, - "io.systemd.Machine.MapFrom", vl_method_map_from, - "io.systemd.Machine.MapTo", vl_method_map_to, - "io.systemd.Machine.BindMount", vl_method_bind_mount, - "io.systemd.Machine.CopyFrom", vl_method_copy_from, - "io.systemd.Machine.CopyTo", vl_method_copy_to, - "io.systemd.MachineImage.List", vl_method_list_images, - "io.systemd.MachineImage.Update", vl_method_update_image, - "io.systemd.MachineImage.Clone", vl_method_clone_image, - "io.systemd.MachineImage.Remove", vl_method_remove_image); + "io.systemd.Machine.Register", vl_method_register, + "io.systemd.Machine.List", vl_method_list, + "io.systemd.Machine.Unregister", vl_method_unregister, + "io.systemd.Machine.Terminate", vl_method_terminate, + "io.systemd.Machine.Kill", vl_method_kill, + "io.systemd.Machine.Open", vl_method_open, + "io.systemd.Machine.OpenRootDirectory", vl_method_open_root_directory, + "io.systemd.Machine.MapFrom", vl_method_map_from, + "io.systemd.Machine.MapTo", vl_method_map_to, + "io.systemd.Machine.BindMount", vl_method_bind_mount, + "io.systemd.Machine.CopyFrom", vl_method_copy_from, + "io.systemd.Machine.CopyTo", vl_method_copy_to, + "io.systemd.MachineImage.List", vl_method_list_images, + "io.systemd.MachineImage.Update", vl_method_update_image, + "io.systemd.MachineImage.Clone", vl_method_clone_image, + "io.systemd.MachineImage.Remove", vl_method_remove_image); if (r < 0) return log_error_errno(r, "Failed to register varlink methods: %m"); diff --git a/src/shared/varlink-io.systemd.Machine.c b/src/shared/varlink-io.systemd.Machine.c index 1953f2fc612..1d6cab8da77 100644 --- a/src/shared/varlink-io.systemd.Machine.c +++ b/src/shared/varlink-io.systemd.Machine.c @@ -179,6 +179,12 @@ static SD_VARLINK_DEFINE_METHOD( SD_VARLINK_FIELD_COMMENT("If true the destination will be replaced"), SD_VARLINK_DEFINE_INPUT(replace, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE)); +static SD_VARLINK_DEFINE_METHOD( + OpenRootDirectory, + VARLINK_DEFINE_MACHINE_LOOKUP_AND_POLKIT_INPUT_FIELDS, + SD_VARLINK_FIELD_COMMENT("File descriptor of opened root directory"), + SD_VARLINK_DEFINE_OUTPUT(fileDescriptor, SD_VARLINK_INT, 0)); + static SD_VARLINK_DEFINE_ERROR(NoSuchMachine); static SD_VARLINK_DEFINE_ERROR(MachineExists); static SD_VARLINK_DEFINE_ERROR(NoPrivateNetworking); @@ -225,6 +231,8 @@ SD_VARLINK_DEFINE_INTERFACE( &vl_method_CopyFrom, SD_VARLINK_SYMBOL_COMMENT("Copy files or directories from the host into a container"), &vl_method_CopyTo, + SD_VARLINK_SYMBOL_COMMENT("Opens machine's root directory"), + &vl_method_OpenRootDirectory, SD_VARLINK_SYMBOL_COMMENT("No matching machine currently running"), &vl_error_NoSuchMachine, &vl_error_MachineExists, From 3ddd3adfcda17d8f7761d01bb796ceefb96a55f5 Mon Sep 17 00:00:00 2001 From: Ivan Kruglov Date: Mon, 6 Jan 2025 16:43:28 +0100 Subject: [PATCH 5/6] machine: tests for io.systemd.Machine.OpenRootDirectory --- test/units/TEST-13-NSPAWN.machined.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/units/TEST-13-NSPAWN.machined.sh b/test/units/TEST-13-NSPAWN.machined.sh index f3d59722ea8..4bd663117f4 100755 --- a/test/units/TEST-13-NSPAWN.machined.sh +++ b/test/units/TEST-13-NSPAWN.machined.sh @@ -422,6 +422,10 @@ varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.CopyF diff /var/lib/machines/long-running/foo /foo rm -f /var/lib/machines/long-running/root/foo /foo +# test io.systemd.Machine.OpenRootDirectory +varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.OpenRootDirectory '{"name": ".host"}' +varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.OpenRootDirectory '{"name": "long-running"}' + # Terminating machine, otherwise acquiring image metadata by io.systemd.MachineImage.List may fail in the below. machinectl terminate long-running # wait for the container being stopped, otherwise acquiring image metadata by io.systemd.MachineImage.List may fail in the below. From 04bf637e25a2e0134c7a3f82739e6245b257dd61 Mon Sep 17 00:00:00 2001 From: Ivan Kruglov Date: Tue, 7 Jan 2025 11:10:44 +0100 Subject: [PATCH 6/6] machine: set SD_VARLINK_SERVER_ALLOW_FD_PASSING_OUTPUT for varlink server --- src/machine/machine-varlink.c | 10 ++++------ src/machine/machined-varlink.c | 6 +++++- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/machine/machine-varlink.c b/src/machine/machine-varlink.c index 89d047f954f..f680b5c3da6 100644 --- a/src/machine/machine-varlink.c +++ b/src/machine/machine-varlink.c @@ -479,10 +479,6 @@ int vl_method_open(sd_varlink *link, sd_json_variant *parameters, sd_varlink_met assert(link); assert(parameters); - r = sd_varlink_set_allow_fd_passing_output(link, true); - if (r < 0) - return log_error_errno(r, "Failed to enable varlink fd passing for write: %m"); - r = sd_varlink_dispatch(link, parameters, dispatch_table, &p); if (r != 0) return r; @@ -559,7 +555,8 @@ int vl_method_open(sd_varlink *link, sd_json_variant *parameters, sd_varlink_met } ptmx_fd_idx = sd_varlink_push_fd(link, ptmx_fd); - /* no need to handle -EPERM because we do sd_varlink_set_allow_fd_passing_output() above */ + if (ERRNO_IS_PRIVILEGE(ptmx_fd_idx)) + return sd_varlink_error(link, SD_VARLINK_ERROR_PERMISSION_DENIED, NULL); if (ptmx_fd_idx < 0) return log_debug_errno(ptmx_fd_idx, "Failed to push file descriptor over varlink: %m"); @@ -944,7 +941,8 @@ int vl_method_open_root_directory_internal(sd_varlink *link, sd_json_variant *pa return log_debug_errno(fd, "Failed to open root directory of machine '%s': %m", machine->name); int fd_idx = sd_varlink_push_fd(link, fd); - /* no need to handle -EPERM because server has SD_VARLINK_SERVER_ALLOW_FD_PASSING_OUTPUT */ + if (ERRNO_IS_PRIVILEGE(fd_idx)) + return sd_varlink_error(link, SD_VARLINK_ERROR_PERMISSION_DENIED, NULL); if (fd_idx < 0) return log_debug_errno(fd_idx, "Failed to push file descriptor over varlink: %m"); diff --git a/src/machine/machined-varlink.c b/src/machine/machined-varlink.c index b94cce296d8..da8d5615385 100644 --- a/src/machine/machined-varlink.c +++ b/src/machine/machined-varlink.c @@ -765,7 +765,11 @@ static int manager_varlink_init_machine(Manager *m) { if (m->varlink_machine_server) return 0; - r = varlink_server_new(&s, SD_VARLINK_SERVER_ACCOUNT_UID|SD_VARLINK_SERVER_INHERIT_USERDATA, m); + r = varlink_server_new( + &s, + SD_VARLINK_SERVER_ACCOUNT_UID|SD_VARLINK_SERVER_INHERIT_USERDATA| + SD_VARLINK_SERVER_ALLOW_FD_PASSING_OUTPUT, + m); if (r < 0) return log_error_errno(r, "Failed to allocate varlink server object: %m");