From d914b7d897ad102cbadb6970972078acf0a2f64e Mon Sep 17 00:00:00 2001 From: Kir Kolyshkin Date: Thu, 30 Apr 2026 15:20:57 -0700 Subject: [PATCH] exec: use default env with --env When crun exec --env is used, the environment from the container spec is not set (including PATH): crun exec env PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin TERM=xterm HOME=/root crun exec --env FOO=bar env executable file `env` not found in $PATH: No such file or directory crun exec --env FOO=bar /bin/env FOO=bar HOME=/root Fix this, and add a test. The modified test fails before the fix: $ RUN_TESTS=exec-add-environment_variable python3 tests/test_exec.py # crun command failed: /home/kir/git/crun/crun --root /home/kir/git/crun/.testsuite-run-325177/root exec --env FOO=BAR test-tmp7g12rkzv /init printenv PATH # Return code: 139 # Test 'exec-add-environment_variable' failed with exception after 0.132s: # Exception type: CalledProcessError .... Signed-off-by: Kir Kolyshkin --- src/exec.c | 1 + src/libcrun/container.c | 17 +++++++++-------- src/libcrun/container.h | 1 + tests/test_exec.py | 8 +++++++- 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/exec.c b/src/exec.c index c943b54131..e51acbd6a4 100644 --- a/src/exec.c +++ b/src/exec.c @@ -305,6 +305,7 @@ crun_command_exec (struct crun_global_arguments *global_args, int argc, char **a process->terminal = exec_options.tty; process->env = exec_options.env; process->env_len = exec_options.env_size; + exec_opts.merge_env = true; process->user = make_oci_process_user (exec_options.user); if (exec_options.process_label != NULL) diff --git a/src/libcrun/container.c b/src/libcrun/container.c index 15bd0c3fc2..c2a382862d 100644 --- a/src/libcrun/container.c +++ b/src/libcrun/container.c @@ -3659,6 +3659,7 @@ exec_process_entrypoint (libcrun_context_t *context, int seccomp_fd, int seccomp_receiver_fd, struct custom_handler_instance_s *custom_handler, + bool merge_env, libcrun_error_t *err) { runtime_spec_schema_config_schema_process_capabilities *capabilities = NULL; @@ -3692,13 +3693,7 @@ exec_process_entrypoint (libcrun_context_t *context, if (UNLIKELY (ret < 0)) return crun_make_error (err, errno, "clearenv"); - if (process->env_len) - { - for (i = 0; i < process->env_len; i++) - if (putenv (process->env[i]) < 0) - return crun_make_error (err, errno, "putenv `%s`", process->env[i]); - } - else if (container->container_def->process->env_len) + if ((merge_env || ! process->env_len) && container->container_def->process && container->container_def->process->env_len) { char *e; @@ -3709,6 +3704,12 @@ exec_process_entrypoint (libcrun_context_t *context, return crun_make_error (err, errno, "putenv `%s`", e); } } + if (process->env_len) + { + for (i = 0; i < process->env_len; i++) + if (putenv (process->env[i]) < 0) + return crun_make_error (err, errno, "putenv `%s`", process->env[i]); + } if (getenv ("HOME") == NULL) { @@ -4055,7 +4056,7 @@ libcrun_container_exec_with_options (libcrun_context_t *context, const char *id, TEMP_FAILURE_RETRY (close (pipefd0)); pipefd0 = -1; - exec_process_entrypoint (context, container, process, &pipefd1, seccomp_fd, seccomp_receiver_fd, custom_handler, err); + exec_process_entrypoint (context, container, process, &pipefd1, seccomp_fd, seccomp_receiver_fd, custom_handler, opts->merge_env, err); /* It gets here only on errors. */ if (*err) { diff --git a/src/libcrun/container.h b/src/libcrun/container.h index 2875c22a6d..f0d9a463db 100644 --- a/src/libcrun/container.h +++ b/src/libcrun/container.h @@ -254,6 +254,7 @@ struct libcrun_container_exec_options_s runtime_spec_schema_config_schema_process *process; const char *path; const char *cgroup; + bool merge_env; }; LIBCRUN_PUBLIC int libcrun_container_exec_with_options (libcrun_context_t *context, const char *id, diff --git a/tests/test_exec.py b/tests/test_exec.py index 1416fbc4a9..6247f3a952 100755 --- a/tests/test_exec.py +++ b/tests/test_exec.py @@ -297,7 +297,7 @@ def test_exec_add_env(): conf['process']['capabilities'] = {} cid = None env_args_list = [] - env_dict_orig = {"HOME":"/", "PATH":"/bin"} + env_dict_orig = {"HOME":"/", "PATH":"/bin", "TERM":"xterm"} env_dict_new = {"HOME":"/tmp", "PATH":"/usr/bin","FOO":"BAR"} try: _, cid = run_and_get_output(conf, hide_stderr=True, command='run', detach=True) @@ -321,6 +321,12 @@ def test_exec_add_env(): cid, "/init", "printenv", "PATH"]) if env_dict_new["PATH"] not in out: return -1 + + # --env must merge with, not replace, the container's default env: + # PATH from the spec must still be set when only an unrelated var is added. + out = run_crun_command(["exec", "--env", "FOO=BAR", cid, "/init", "printenv", "PATH"]) + if env_dict_orig["PATH"] not in out: + return -1 finally: if cid is not None: run_crun_command(["delete", "-f", cid])