[libc-commits] [libc] 6a34d61 - [libc] Implement clearenv (#230941)
via libc-commits
libc-commits at lists.llvm.org
Sun Oct 11 10:59:43 PDT 2026
Author: Jeff Bailey
Date: 2026-10-11T18:59:37+01:00
New Revision: 6a34d61ce78c07508081dbc90222859a3b5cfa65
URL: https://github.com/llvm/llvm-project/commit/6a34d61ce78c07508081dbc90222859a3b5cfa65
DIFF: https://github.com/llvm/llvm-project/commit/6a34d61ce78c07508081dbc90222859a3b5cfa65.diff
LOG: [libc] Implement clearenv (#230941)
Added the clearenv entrypoint to clear all environment variables.
* Declared clearenv in stdlib.yaml under GNU standards.
* Added EnvironmentManager::clear() to reset managed storage and set
environ to NULL, leaving string buffers intact to prevent use-after-free
for callers holding getenv() results.
* Added the entrypoint for Linux targets and enabled it for full builds
on aarch64, riscv, and x86_64.
* Added integration test covering clearenv, subsequent environment
modifications, idempotence, and pointer retention.
Assisted-by: Automated tooling, human reviewed.
Added:
libc/src/stdlib/clearenv.h
libc/src/stdlib/linux/clearenv.cpp
libc/test/integration/src/stdlib/clearenv_test.cpp
Modified:
libc/config/linux/aarch64/entrypoints.txt
libc/config/linux/riscv/entrypoints.txt
libc/config/linux/x86_64/entrypoints.txt
libc/include/stdlib.yaml
libc/src/stdlib/CMakeLists.txt
libc/src/stdlib/environ_internal.cpp
libc/src/stdlib/environ_internal.h
libc/src/stdlib/linux/CMakeLists.txt
libc/test/integration/src/stdlib/CMakeLists.txt
Removed:
################################################################################
diff --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt
index b8a9a5cff2040..1e213af91b663 100644
--- a/libc/config/linux/aarch64/entrypoints.txt
+++ b/libc/config/linux/aarch64/entrypoints.txt
@@ -1343,6 +1343,7 @@ if(LLVM_LIBC_FULL_BUILD)
libc.src.stdlib.abort
libc.src.stdlib.at_quick_exit
libc.src.stdlib.atexit
+ libc.src.stdlib.clearenv
libc.src.stdlib.exit
libc.src.stdlib.getenv
libc.src.stdlib.putenv
diff --git a/libc/config/linux/riscv/entrypoints.txt b/libc/config/linux/riscv/entrypoints.txt
index 0728bd41a40f9..b6fa7ce486bb7 100644
--- a/libc/config/linux/riscv/entrypoints.txt
+++ b/libc/config/linux/riscv/entrypoints.txt
@@ -1524,6 +1524,7 @@ if(LLVM_LIBC_FULL_BUILD)
libc.src.stdlib.abort
libc.src.stdlib.at_quick_exit
libc.src.stdlib.atexit
+ libc.src.stdlib.clearenv
libc.src.stdlib.exit
libc.src.stdlib.getenv
libc.src.stdlib.putenv
diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index 2df3fb0dfb363..213016907d321 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -1550,6 +1550,7 @@ if(LLVM_LIBC_FULL_BUILD)
libc.src.stdlib.abort
libc.src.stdlib.at_quick_exit
libc.src.stdlib.atexit
+ libc.src.stdlib.clearenv
libc.src.stdlib.exit
libc.src.stdlib.getenv
libc.src.stdlib.putenv
diff --git a/libc/include/stdlib.yaml b/libc/include/stdlib.yaml
index bd13d6fadb236..d278b8daa67ac 100644
--- a/libc/include/stdlib.yaml
+++ b/libc/include/stdlib.yaml
@@ -97,6 +97,12 @@ functions:
- type: size_t
- type: size_t
- type: __search_compare_t
+ - name: clearenv
+ standards:
+ - gnu
+ return_type: int
+ arguments:
+ - type: void
- name: div
standards:
- stdc
diff --git a/libc/src/stdlib/CMakeLists.txt b/libc/src/stdlib/CMakeLists.txt
index 2619d6c2ba73a..8bdca304e5190 100644
--- a/libc/src/stdlib/CMakeLists.txt
+++ b/libc/src/stdlib/CMakeLists.txt
@@ -742,6 +742,13 @@ add_entrypoint_object(
.${LIBC_TARGET_OS}.unsetenv
)
+add_entrypoint_object(
+ clearenv
+ ALIAS
+ DEPENDS
+ .${LIBC_TARGET_OS}.clearenv
+)
+
if(LIBC_TARGET_OS_IS_BAREMETAL OR LIBC_TARGET_OS_IS_GPU)
add_entrypoint_object(
malloc
diff --git a/libc/src/stdlib/clearenv.h b/libc/src/stdlib/clearenv.h
new file mode 100644
index 0000000000000..f2632e1df39b7
--- /dev/null
+++ b/libc/src/stdlib/clearenv.h
@@ -0,0 +1,25 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Declaration of the clearenv function.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_STDLIB_CLEARENV_H
+#define LLVM_LIBC_SRC_STDLIB_CLEARENV_H
+
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+int clearenv(void);
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_STDLIB_CLEARENV_H
diff --git a/libc/src/stdlib/environ_internal.cpp b/libc/src/stdlib/environ_internal.cpp
index da2e5c8185ca0..7fc2fdafd4867 100644
--- a/libc/src/stdlib/environ_internal.cpp
+++ b/libc/src/stdlib/environ_internal.cpp
@@ -65,7 +65,8 @@ char **EnvironmentManager::get_array() {
EnvironmentManager::iterator EnvironmentManager::begin() { return get_array(); }
EnvironmentManager::iterator EnvironmentManager::end() {
- return get_array() + count;
+ char **env_array = get_array();
+ return env_array ? env_array + count : nullptr;
}
size_t EnvironmentManager::size() const { return count; }
@@ -301,5 +302,26 @@ int EnvironmentManager::put(char *string) {
return 0;
}
+int EnvironmentManager::clear() {
+ init_once();
+
+ // Free the pointer array, but leave the string buffers intact so that
+ // existing getenv pointers remain valid.
+ delete[] storage;
+ delete[] ownership;
+ storage = nullptr;
+ ownership = nullptr;
+ capacity = 0;
+ count = 0;
+ is_ours = false;
+
+ app.env_ptr = nullptr;
+#ifdef LIBC_COPT_SUPPORT_ENVIRON
+ environ = nullptr;
+#endif
+
+ return 0;
+}
+
} // namespace internal
} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdlib/environ_internal.h b/libc/src/stdlib/environ_internal.h
index aa111b8496aec..f39726d5ba9c7 100644
--- a/libc/src/stdlib/environ_internal.h
+++ b/libc/src/stdlib/environ_internal.h
@@ -140,6 +140,11 @@ class EnvironmentManager {
// compacts the array. Returns 0 on success (including if the variable
// was not found), -1 on allocation failure during array transition.
int unset(cpp::string_view name);
+
+ // Clear all environment variables and set environ to NULL.
+ // Frees the pointer array, leaving string buffers intact so that
+ // existing getenv pointers remain valid. Returns 0 on success.
+ int clear();
};
} // namespace internal
diff --git a/libc/src/stdlib/linux/CMakeLists.txt b/libc/src/stdlib/linux/CMakeLists.txt
index 00d108767ac20..c40fc6a5b84ce 100644
--- a/libc/src/stdlib/linux/CMakeLists.txt
+++ b/libc/src/stdlib/linux/CMakeLists.txt
@@ -95,6 +95,18 @@ add_entrypoint_object(
libc.src.stdlib.environ_internal
)
+add_entrypoint_object(
+ clearenv
+ SRCS
+ clearenv.cpp
+ HDRS
+ ../clearenv.h
+ DEPENDS
+ libc.src.__support.common
+ libc.src.__support.macros.config
+ libc.src.stdlib.environ_internal
+)
+
add_entrypoint_object(
system
SRCS
diff --git a/libc/src/stdlib/linux/clearenv.cpp b/libc/src/stdlib/linux/clearenv.cpp
new file mode 100644
index 0000000000000..37998e1abf3b5
--- /dev/null
+++ b/libc/src/stdlib/linux/clearenv.cpp
@@ -0,0 +1,25 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Implementation of the clearenv function.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/stdlib/clearenv.h"
+#include "src/__support/common.h"
+#include "src/__support/macros/config.h"
+#include "src/stdlib/environ_internal.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(int, clearenv, ()) {
+ return internal::EnvironmentManager::get_instance().clear();
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/test/integration/src/stdlib/CMakeLists.txt b/libc/test/integration/src/stdlib/CMakeLists.txt
index 7976e3833e282..11b1e90fbbee8 100644
--- a/libc/test/integration/src/stdlib/CMakeLists.txt
+++ b/libc/test/integration/src/stdlib/CMakeLists.txt
@@ -75,6 +75,21 @@ if(${LIBC_TARGET_OS} STREQUAL "linux")
libc.src.unistd.environ
)
+ add_integration_test(
+ clearenv_test
+ SUITE
+ stdlib-integration-tests
+ SRCS
+ clearenv_test.cpp
+ DEPENDS
+ libc.src.stdlib.clearenv
+ libc.src.stdlib.getenv
+ libc.src.stdlib.putenv
+ libc.src.stdlib.setenv
+ libc.src.stdlib.unsetenv
+ libc.src.unistd.environ
+ )
+
add_integration_test(
abort_test
SUITE
diff --git a/libc/test/integration/src/stdlib/clearenv_test.cpp b/libc/test/integration/src/stdlib/clearenv_test.cpp
new file mode 100644
index 0000000000000..8d80de9a7ceba
--- /dev/null
+++ b/libc/test/integration/src/stdlib/clearenv_test.cpp
@@ -0,0 +1,110 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Integration tests for the clearenv function.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/stdlib/clearenv.h"
+#include "src/stdlib/getenv.h"
+#include "src/stdlib/putenv.h"
+#include "src/stdlib/setenv.h"
+#include "src/stdlib/unsetenv.h"
+#include "src/unistd/environ.h"
+
+#include "test/IntegrationTest/test.h"
+
+TEST_MAIN([[maybe_unused]] int argc, [[maybe_unused]] char **argv,
+ [[maybe_unused]] char **envp) {
+ // Test: Clear environment and verify getenv and environ are null
+ {
+ ASSERT_EQ(LIBC_NAMESPACE::setenv("PRE_CLEAR_VAR", "hello", 1), 0);
+ ASSERT_NE(LIBC_NAMESPACE::getenv("PRE_CLEAR_VAR"), nullptr);
+
+ ASSERT_EQ(LIBC_NAMESPACE::clearenv(), 0);
+ ASSERT_EQ(LIBC_NAMESPACE::getenv("PRE_CLEAR_VAR"), nullptr);
+ ASSERT_EQ(LIBC_NAMESPACE::environ, nullptr);
+ }
+
+ // Test: Adding variables after clearenv using setenv
+ {
+ ASSERT_EQ(LIBC_NAMESPACE::clearenv(), 0);
+ ASSERT_EQ(LIBC_NAMESPACE::environ, nullptr);
+
+ ASSERT_EQ(LIBC_NAMESPACE::setenv("POST_CLEAR_VAR", "world", 1), 0);
+ ASSERT_NE(LIBC_NAMESPACE::environ, nullptr);
+ ASSERT_NE(LIBC_NAMESPACE::getenv("POST_CLEAR_VAR"), nullptr);
+ ASSERT_STREQ(LIBC_NAMESPACE::getenv("POST_CLEAR_VAR"), "world");
+ ASSERT_STREQ(LIBC_NAMESPACE::environ[0], "POST_CLEAR_VAR=world");
+ ASSERT_EQ(LIBC_NAMESPACE::environ[1], nullptr);
+ }
+
+ // Test: Adding variables after clearenv using putenv
+ {
+ ASSERT_EQ(LIBC_NAMESPACE::clearenv(), 0);
+ ASSERT_EQ(LIBC_NAMESPACE::environ, nullptr);
+
+ static char put_buf[] = "PUT_AFTER_CLEAR=test";
+ ASSERT_EQ(LIBC_NAMESPACE::putenv(put_buf), 0);
+ ASSERT_NE(LIBC_NAMESPACE::environ, nullptr);
+ ASSERT_NE(LIBC_NAMESPACE::getenv("PUT_AFTER_CLEAR"), nullptr);
+ ASSERT_STREQ(LIBC_NAMESPACE::getenv("PUT_AFTER_CLEAR"), "test");
+ ASSERT_STREQ(LIBC_NAMESPACE::environ[0], "PUT_AFTER_CLEAR=test");
+ ASSERT_EQ(LIBC_NAMESPACE::environ[1], nullptr);
+ }
+
+ // Test: Calling clearenv multiple times is idempotent
+ {
+ ASSERT_EQ(LIBC_NAMESPACE::clearenv(), 0);
+ ASSERT_EQ(LIBC_NAMESPACE::environ, nullptr);
+ ASSERT_EQ(LIBC_NAMESPACE::clearenv(), 0);
+ ASSERT_EQ(LIBC_NAMESPACE::environ, nullptr);
+ }
+
+ // Test: Unsetenv after clearenv succeeds
+ {
+ ASSERT_EQ(LIBC_NAMESPACE::clearenv(), 0);
+ ASSERT_EQ(LIBC_NAMESPACE::unsetenv("NONEXISTENT"), 0);
+ ASSERT_EQ(LIBC_NAMESPACE::environ, nullptr);
+ }
+
+ // Test: Multiple setenv allocations followed by clear
+ {
+ ASSERT_EQ(LIBC_NAMESPACE::setenv("MULTI_1", "v1", 1), 0);
+ ASSERT_EQ(LIBC_NAMESPACE::setenv("MULTI_2", "v2", 1), 0);
+ ASSERT_EQ(LIBC_NAMESPACE::setenv("MULTI_3", "v3", 1), 0);
+ ASSERT_STREQ(LIBC_NAMESPACE::getenv("MULTI_1"), "v1");
+ ASSERT_STREQ(LIBC_NAMESPACE::getenv("MULTI_2"), "v2");
+ ASSERT_STREQ(LIBC_NAMESPACE::getenv("MULTI_3"), "v3");
+
+ ASSERT_EQ(LIBC_NAMESPACE::clearenv(), 0);
+ ASSERT_EQ(LIBC_NAMESPACE::environ, nullptr);
+ ASSERT_EQ(LIBC_NAMESPACE::getenv("MULTI_1"), nullptr);
+ ASSERT_EQ(LIBC_NAMESPACE::getenv("MULTI_2"), nullptr);
+ ASSERT_EQ(LIBC_NAMESPACE::getenv("MULTI_3"), nullptr);
+ }
+
+ // Test: Pointers obtained from getenv remain valid after clearenv
+ {
+ ASSERT_EQ(LIBC_NAMESPACE::setenv("RETAINED_VAR", "retained_val", 1), 0);
+ char *val = LIBC_NAMESPACE::getenv("RETAINED_VAR");
+ ASSERT_NE(val, nullptr);
+ ASSERT_STREQ(val, "retained_val");
+
+ ASSERT_EQ(LIBC_NAMESPACE::clearenv(), 0);
+ ASSERT_EQ(LIBC_NAMESPACE::getenv("RETAINED_VAR"), nullptr);
+ ASSERT_EQ(LIBC_NAMESPACE::environ, nullptr);
+
+ // The buffer itself was not erased/deallocated, so existing pointers
+ // remain readable without use-after-free.
+ ASSERT_STREQ(val, "retained_val");
+ }
+
+ return 0;
+}
More information about the libc-commits
mailing list