]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
unshare: add whitelist-env command line option
authorChristian Goeschel Ndjomouo <cgoesc2@wgu.edu>
Mon, 20 Apr 2026 04:16:11 +0000 (00:16 -0400)
committerChristian Goeschel Ndjomouo <cgoesc2@wgu.edu>
Mon, 27 Apr 2026 13:37:27 +0000 (09:37 -0400)
This new option allows controlled inheritance of environment
variables from the parent process by the unshared process.

This option by default clears all environment variables except
for those provided in the option argument, specified as a
comma-separated list.

Signed-off-by: Christian Goeschel Ndjomouo <cgoesc2@wgu.edu>
bash-completion/unshare
sys-utils/unshare.1.adoc
sys-utils/unshare.c
tests/expected/unshare/env-clear-env [new file with mode: 0644]
tests/expected/unshare/env-whitelist-env [new file with mode: 0644]
tests/ts/unshare/env [moved from tests/ts/unshare/clear-env with 57% similarity]

index 8fc9c340e0984098d8d750d488e5652cca11df0d..23b10166cbdb7a2a01caac088d2e24af43914f85 100644 (file)
@@ -6,7 +6,7 @@ _unshare_module()
 
        OPTARGOPTS="--mount=|--uts=|--ipc=|--net=|--pid=|--user=|--cgroup=|--time=|--kill-child=|--mount-proc=|--mount-binfmt="
 
-       REQARGOPTS="--owner|--propagation|--setgroups|--root|--wd|--load-interp|--map-group|--map-groups|--map-user|--map-users|--monotonic|--boottime|--setuid|--setgid"
+       REQARGOPTS="--owner|--propagation|--setgroups|--root|--wd|--load-interp|--map-group|--map-groups|--map-user|--map-users|--monotonic|--boottime|--setuid|--setgid|--whitelist-env"
 
        REQARGOPTS_SHORT="-l|-R|-w|-S|-G"
 
index d9806975cb89a323ec05f6bbe9a6874a6367e9c4..f104c33b646bf713dfe9e5001871c9eecfb151e1 100644 (file)
@@ -170,6 +170,9 @@ Set the offset of *CLOCK_BOOTTIME* which will be used in the entered time namesp
 *--clear-env*::
 Do not pass the environment variables of the calling process to the unshared process.
 
+*--whitelist-env* _list_::
+Clear all environment variables except for those specified in the comma-separated _list_.
+
 include::man-common/help-version.adoc[]
 
 == ENVIRONMENT
index 216bfa78583b7e0d799c0591bfbf0402830a7c54..beecc69d41bbed95b6198c6b7525667e3a862ce8 100644 (file)
@@ -48,6 +48,7 @@
 #include "signames.h"
 #include "strutils.h"
 #include "pwdutils.h"
+#include "env.h"
 
 #ifndef HAVE_ENVIRON_DECL
 extern char **environ;
@@ -743,6 +744,15 @@ static void load_interp(const char *binfmt_mnt, const char *interp)
        close(dirfd);
 }
 
+static void set_whitelist_envs(struct ul_env_list **el)
+{
+       if (env_list_setenv(*el, 0) != 0)
+                       err(EXIT_FAILURE, _("failed to set whitelisted environment variables"));
+
+       env_list_free(*el);
+       *el = NULL;
+}
+
 static void __attribute__((__noreturn__)) usage(void)
 {
        FILE *out = stdout;
@@ -797,6 +807,7 @@ static void __attribute__((__noreturn__)) usage(void)
        fputs(_(" --monotonic <offset>      set clock monotonic offset (seconds) in time namespaces\n"), out);
        fputs(_(" --boottime <offset>       set clock boottime offset (seconds) in time namespaces\n"), out);
        fputs(_(" --clear-env               do not inherit environment variables from the calling process\n"), out);
+       fputs(_(" --whitelist-env <list>    clear environment except for specified variables\n"), out);
 
        fputs(USAGE_SEPARATOR, out);
        fprintf(out, USAGE_HELP_OPTIONS(27));
@@ -825,6 +836,7 @@ int main(int argc, char *argv[])
                OPT_OWNER,
                OPT_FORWARD_SIGNALS,
                OPT_CLEAR_ENV,
+               OPT_WHITELIST_ENV,
        };
        static const struct option longopts[] = {
                { "help",          no_argument,       NULL, 'h'             },
@@ -864,6 +876,7 @@ int main(int argc, char *argv[])
                { "boottime",      required_argument, NULL, OPT_BOOTTIME    },
                { "load-interp",   required_argument, NULL, 'l'             },
                { "clear-env",   no_argument, NULL, OPT_CLEAR_ENV                   },
+               { "whitelist-env",   required_argument, NULL, OPT_WHITELIST_ENV },
                { NULL, 0, NULL, 0 }
        };
 
@@ -874,6 +887,7 @@ int main(int argc, char *argv[])
        gid_t mapgroup = -1, ownergroup = -1;
        struct map_range *usermap = NULL;
        struct map_range *groupmap = NULL;
+       struct ul_env_list *env_whitelist = NULL; /* environment whitelist */
        int kill_child_signo = 0; /* 0 means --kill-child was not used */
        const char *procmnt = NULL;
        const char *binfmt_mnt = NULL;
@@ -1080,6 +1094,9 @@ int main(int argc, char *argv[])
                case OPT_CLEAR_ENV:
                        clear_env = 1;
                        break;
+               case OPT_WHITELIST_ENV:
+                       env_whitelist = env_list_add_getenvs(env_whitelist, optarg);
+                       break;
                case 'h':
                        usage();
                case 'V':
@@ -1333,13 +1350,16 @@ int main(int argc, char *argv[])
        if (keepcaps && (unshare_flags & CLONE_NEWUSER))
                cap_permitted_to_ambient();
 
-       if (clear_env)
+       if (clear_env || env_whitelist)
 #ifdef HAVE_CLEARENV
                clearenv();
 #else
                environ = NULL;
 #endif
 
+       if (env_whitelist)
+               set_whitelist_envs(&env_whitelist);
+
        if (optind < argc) {
                execvp(argv[optind], argv + optind);
                errexec(argv[optind]);
diff --git a/tests/expected/unshare/env-clear-env b/tests/expected/unshare/env-clear-env
new file mode 100644 (file)
index 0000000..929843a
--- /dev/null
@@ -0,0 +1 @@
+CLEARED
diff --git a/tests/expected/unshare/env-whitelist-env b/tests/expected/unshare/env-whitelist-env
new file mode 100644 (file)
index 0000000..257cc56
--- /dev/null
@@ -0,0 +1 @@
+foo
similarity index 57%
rename from tests/ts/unshare/clear-env
rename to tests/ts/unshare/env
index 5064a7be285bfd247116f9b11cf9a01b8eccd7b3..f6f2f2d3e6a324ecaaa128c7dd621405a10a3d69 100755 (executable)
@@ -23,16 +23,28 @@ ts_init "$*"
 ts_check_test_command "$TS_CMD_UNSHARE"
 ts_check_test_command "$TS_HELPER_STRERROR"
 
+
+ts_init_subtest "clear-env"
 export UL_TEST_ENV=foo
-res="$("$TS_CMD_UNSHARE" --clear-env /bin/bash -c 'echo $UL_TEST_ENV' 2>&1)"
+"$TS_CMD_UNSHARE" --clear-env /bin/bash -c 'echo "${UL_TEST_ENV:-CLEARED}"' >>"$TS_OUTPUT" 2>>"$TS_ERRLOG"
 unset -v UL_TEST_ENV
 
-if echo "$res" | grep -q "$($TS_HELPER_STRERROR EPERM)"; then
-        ts_skip "missing permissions"
+if grep -q "$($TS_HELPER_STRERROR EPERM)" "$TS_OUTPUT" "$TS_ERRLOG"; then
+        ts_skip_subtest "missing permissions"
+else
+        ts_finalize_subtest
 fi
 
-if [[ -n "$res" ]]; then
-        ts_failed "environment not cleared correctly: $res"
+
+ts_init_subtest "whitelist-env"
+export UL_TEST_ENV1=foo
+export UL_TEST_ENV2=bar
+"$TS_CMD_UNSHARE" --whitelist-env UL_TEST_ENV1 /bin/bash -c 'echo "${UL_TEST_ENV1}${UL_TEST_ENV2}"' >>"$TS_OUTPUT" 2>>"$TS_ERRLOG"
+
+if grep -q "$($TS_HELPER_STRERROR EPERM)" "$TS_OUTPUT" "$TS_ERRLOG"; then
+        ts_skip_subtest "missing permissions"
+else
+        ts_finalize_subtest
 fi
 
 ts_finalize