From 360621a3be7dc5ce756c18b8b1fe8607855d3e73 Mon Sep 17 00:00:00 2001 From: Jindrich Novy Date: Tue, 24 Mar 2026 06:34:31 +0100 Subject: [PATCH 1/2] Revert "container: skip sigaction reset in unblock_signals for the run path" This reverts commit 38e17199acd127860726c5c5e2fd3df6d29b81da. Skipping signal handler reset in the non-prefork paths caused containers to inherit SIG_IGN for SIGPIPE from the Go runtime parent, breaking "trap ... SIGPIPE" in shells. Fixes: #2058 Signed-off-by: Jindrich Novy --- src/libcrun/container.c | 45 ++++++++++++++++------------------------- 1 file changed, 17 insertions(+), 28 deletions(-) diff --git a/src/libcrun/container.c b/src/libcrun/container.c index 99ee620b4d..debca0b578 100644 --- a/src/libcrun/container.c +++ b/src/libcrun/container.c @@ -94,8 +94,6 @@ struct container_entrypoint_s int hooks_err_fd; struct custom_handler_instance_s *custom_handler; - - bool reset_signal_handlers; }; struct sync_socket_message_s @@ -642,31 +640,25 @@ block_signals (libcrun_error_t *err) } static int -unblock_signals (bool reset_handlers, libcrun_error_t *err) +unblock_signals (libcrun_error_t *err) { + int i; int ret; sigset_t mask; + struct sigaction act = {}; sigfillset (&mask); ret = sigprocmask (SIG_UNBLOCK, &mask, NULL); if (UNLIKELY (ret < 0)) return crun_make_error (err, errno, "sigprocmask"); - if (! reset_handlers) - return 0; - - { - int i; - struct sigaction act = {}; - - act.sa_handler = SIG_DFL; - for (i = 0; i < NSIG; i++) - { - ret = sigaction (i, &act, NULL); - if (ret < 0 && errno != EINVAL) - return crun_make_error (err, errno, "sigaction"); - } - } + act.sa_handler = SIG_DFL; + for (i = 0; i < NSIG; i++) + { + ret = sigaction (i, &act, NULL); + if (ret < 0 && errno != EINVAL) + return crun_make_error (err, errno, "sigaction"); + } return 0; } @@ -1594,8 +1586,7 @@ container_init (void *args, char *notify_socket, int sync_socket, libcrun_error_ entrypoint_args->sync_socket = -1; - /* Reset signal handlers when used as a library (prefork path). */ - ret = unblock_signals (entrypoint_args->reset_signal_handlers, err); + ret = unblock_signals (err); if (UNLIKELY (ret < 0)) return ret; @@ -2761,8 +2752,7 @@ terminal_setup (runtime_spec_schema_config_schema *def, libcrun_context_t *conte static int libcrun_container_run_internal (libcrun_container_t *container, libcrun_context_t *context, - int *container_ready_fd, bool reset_signal_handlers, - libcrun_error_t *err) + int *container_ready_fd, libcrun_error_t *err) { runtime_spec_schema_config_schema *def = container->container_def; int ret; @@ -2792,7 +2782,6 @@ libcrun_container_run_internal (libcrun_container_t *container, libcrun_context_ .hooks_err_fd = -1, .seccomp_receiver_fd = -1, .custom_handler = NULL, - .reset_signal_handlers = reset_signal_handlers, }; cleanup_close int cgroup_dirfd = -1; struct libcrun_dirfd_s cgroup_dirfd_s; @@ -3152,7 +3141,7 @@ libcrun_container_run (libcrun_context_t *context, libcrun_container_t *containe if (UNLIKELY (ret < 0)) return ret; - ret = libcrun_container_run_internal (container, context, NULL, false, err); + ret = libcrun_container_run_internal (container, context, NULL, err); if (! (options & LIBCRUN_RUN_OPTIONS_KEEP)) force_delete_container_status (context, def); return ret; @@ -3208,7 +3197,7 @@ libcrun_container_run (libcrun_context_t *context, libcrun_container_t *containe if (UNLIKELY (ret < 0)) goto fail; - ret = libcrun_container_run_internal (container, context, NULL, true, &tmp_err); + ret = libcrun_container_run_internal (container, context, NULL, &tmp_err); TEMP_FAILURE_RETRY (write (pipefd1, &ret, sizeof (ret))); if (UNLIKELY (ret < 0)) goto fail; @@ -3271,7 +3260,7 @@ libcrun_container_create (libcrun_context_t *context, libcrun_container_t *conta ret = libcrun_copy_config_file (context->id, context->state_root, container, err); if (UNLIKELY (ret < 0)) return ret; - ret = libcrun_container_run_internal (container, context, NULL, false, err); + ret = libcrun_container_run_internal (container, context, NULL, err); if (UNLIKELY (ret < 0)) force_delete_container_status (context, def); return ret; @@ -3323,7 +3312,7 @@ libcrun_container_create (libcrun_context_t *context, libcrun_container_t *conta libcrun_fail_with_error (errcode, "copy config file"); } - ret = libcrun_container_run_internal (container, context, &pipefd1, true, err); + ret = libcrun_container_run_internal (container, context, &pipefd1, err); if (UNLIKELY (ret < 0)) { force_delete_container_status (context, def); @@ -3682,7 +3671,7 @@ exec_process_entrypoint (libcrun_context_t *context, else crun_error_release (err); - ret = unblock_signals (true, err); + ret = unblock_signals (err); if (UNLIKELY (ret < 0)) return ret; From c4e3fd228696d411fc9cd16e5a80eb43fddb8e1b Mon Sep 17 00:00:00 2001 From: Jindrich Novy Date: Tue, 24 Mar 2026 06:35:19 +0100 Subject: [PATCH 2/2] linux: set MS_PRIVATE on detached mounts from get_bind_mount() Detached mounts from open_tree(OPEN_TREE_CLONE) do not inherit propagation from the parent mount tree, so they keep shared propagation when root_propagation_private skips the MS_PRIVATE call in do_mount(). Set attr.propagation = MS_PRIVATE in get_bind_mount() to prevent mount leaks into the host namespace. Add a regression test that verifies bind mounts inside the container have private propagation. Fixes: #2059 Signed-off-by: Jindrich Novy --- src/libcrun/linux.c | 5 +++++ tests/test_mounts.py | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/src/libcrun/linux.c b/src/libcrun/linux.c index b7049b57a5..57ae901b46 100644 --- a/src/libcrun/linux.c +++ b/src/libcrun/linux.c @@ -311,6 +311,11 @@ get_bind_mount (int dirfd, const char *src, bool recursive, bool rdonly, bool no if (rdonly) attr.attr_set = MS_RDONLY; + /* Detached mounts created by open_tree(OPEN_TREE_CLONE) do not inherit + the propagation type from the parent mount tree. Always set MS_PRIVATE + to prevent mount events from leaking back to the host namespace. */ + attr.propagation = MS_PRIVATE; + errno = 0; open_tree_fd = syscall_open_tree (dirfd, src, AT_NO_AUTOMOUNT | OPEN_TREE_CLOEXEC diff --git a/tests/test_mounts.py b/tests/test_mounts.py index cdf570d67e..16cc7b0be8 100755 --- a/tests/test_mounts.py +++ b/tests/test_mounts.py @@ -952,6 +952,37 @@ def test_annotation_mount_context_type(): return 0 +def test_mount_propagation_private(): + """Verify bind mounts have private propagation and don't leak to the host. + + Regression test for https://github.com/containers/crun/issues/2059. + Detached mounts from open_tree(OPEN_TREE_CLONE) do not inherit + propagation from the parent mount tree, so they must explicitly + set MS_PRIVATE to prevent mount events from leaking back. + """ + conf = base_config() + conf['process']['args'] = ['/init', 'cat', '/proc/self/mountinfo'] + add_all_namespaces(conf) + conf['linux']['rootfsPropagation'] = 'rprivate' + mount_opt = {"destination": "/mnt", "type": "bind", "source": get_tests_root(), + "options": ["bind", "rprivate"]} + conf['mounts'].append(mount_opt) + out, _ = run_and_get_output(conf, hide_stderr=True) + for line in out.splitlines(): + if '/mnt' not in line: + continue + # mountinfo optional fields (between the hyphen separator and the + # mount ID fields) contain "shared:N" for shared propagation. + # With private propagation there should be no "shared:" tag. + if 'shared:' in line: + logger.info("bind mount at /mnt has shared propagation, expected private") + logger.info("mountinfo line: %s", line) + return -1 + return 0 + logger.info("/mnt not found in mountinfo") + logger.info("mountinfo output: %s", out) + return -1 + all_tests = { "mount-ro" : test_mount_ro, "mount-rro" : test_mount_rro, @@ -989,6 +1020,7 @@ def test_annotation_mount_context_type(): "mount-add-remove-mounts": test_add_remove_mounts, "mount-help": test_mount_help, "annotation-mount-context-type": test_annotation_mount_context_type, + "mount-propagation-private": test_mount_propagation_private, } if __name__ == "__main__":