]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
env-util: ensure NUL termination of the replace_env_argv() output array
authorLuca Boccassi <luca.boccassi@gmail.com>
Mon, 29 Jun 2026 14:05:51 +0000 (15:05 +0100)
committerLuca Boccassi <luca.boccassi@gmail.com>
Tue, 30 Jun 2026 10:49:53 +0000 (11:49 +0100)
The output array is allocated with new() and left uninitialized, but a
bare unset "$VAR" token expands to nothing and writes no terminator.
When such a token leads or is the only word, the returned strv is left
without a trailing NULL.

Follow-up for f331434d13488425ccd8485327085d15f8f92aea

src/basic/env-util.c
src/test/test-env-util.c

index 4f6240e546519413e3f4f0aecd9b59fdac80e612..934428e7f520d3b575be6168290b1a0338a13c80 100644 (file)
@@ -948,6 +948,8 @@ int replace_env_argv(
                         }
 
                         k += q;
+                        /* reallocarray() does not zero-initialize the grown tail, add NUL termination */
+                        n[k] = NULL;
                         continue;
                 }
 
index 281ea7951cb4536c59ce5e551e828008d56b01cd..7c55bcc3d860dbc188b17873d3583752f7a9eae8 100644 (file)
@@ -590,4 +590,28 @@ TEST(strv_env_get_merged) {
         ASSERT_TRUE(strv_equal(m, expected));
 }
 
+TEST(replace_env_argv_unterminated) {
+        /* A bare unset "$VAR" token expands to nothing and advances neither the write index nor writes a
+         * terminator, so the output strv must still be NUL-terminated when such a token leads or is the
+         * only word. */
+
+        _cleanup_strv_free_ char **a = NULL, **b = NULL, **c = NULL, **d = NULL;
+
+        /* Single bare unset variable: result must be a properly terminated empty strv. */
+        ASSERT_OK(replace_env_argv(STRV_MAKE("$THIS_IS_UNSET"), STRV_MAKE("FOO=BAR"), &a, NULL, NULL));
+        ASSERT_TRUE(strv_isempty(a));
+
+        /* Only unset variables. */
+        ASSERT_OK(replace_env_argv(STRV_MAKE("$THIS_IS_UNSET", "$ALSO_UNSET"), STRV_MAKE("FOO=BAR"), &b, NULL, NULL));
+        ASSERT_TRUE(strv_isempty(b));
+
+        /* Trailing bare unset variable after an expanded one. */
+        ASSERT_OK(replace_env_argv(STRV_MAKE("$FOO", "$THIS_IS_UNSET"), STRV_MAKE("FOO=BAR"), &c, NULL, NULL));
+        ASSERT_TRUE(strv_equal(c, STRV_MAKE("BAR")));
+
+        /* Trailing bare unset variable after a literal word. */
+        ASSERT_OK(replace_env_argv(STRV_MAKE("hello", "$THIS_IS_UNSET"), STRV_MAKE("FOO=BAR"), &d, NULL, NULL));
+        ASSERT_TRUE(strv_equal(d, STRV_MAKE("hello")));
+}
+
 DEFINE_TEST_MAIN(LOG_DEBUG);