[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