[libc-commits] [libc] [libc] Implement clearenv (PR #230941)

Jeff Bailey via libc-commits libc-commits at lists.llvm.org
Sun Oct 11 03:25:30 PDT 2026


https://github.com/kaladron created https://github.com/llvm/llvm-project/pull/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.

>From c38ca269b12b5e88e9c3547ab21e2256a48d3d06 Mon Sep 17 00:00:00 2001
From: Jeff Bailey <jbailey at raspberryginger.com>
Date: Fri, 2 Oct 2026 20:02:54 +0100
Subject: [PATCH] [libc] Implement clearenv

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.
---
 libc/config/linux/aarch64/entrypoints.txt     |   1 +
 libc/config/linux/riscv/entrypoints.txt       |   1 +
 libc/config/linux/x86_64/entrypoints.txt      |   1 +
 libc/include/stdlib.yaml                      |   6 +
 libc/src/stdlib/CMakeLists.txt                |   7 ++
 libc/src/stdlib/clearenv.h                    |  25 ++++
 libc/src/stdlib/environ_internal.cpp          |  24 +++-
 libc/src/stdlib/environ_internal.h            |   5 +
 libc/src/stdlib/linux/CMakeLists.txt          |  12 ++
 libc/src/stdlib/linux/clearenv.cpp            |  25 ++++
 .../integration/src/stdlib/CMakeLists.txt     |  15 +++
 .../integration/src/stdlib/clearenv_test.cpp  | 110 ++++++++++++++++++
 12 files changed, 231 insertions(+), 1 deletion(-)
 create mode 100644 libc/src/stdlib/clearenv.h
 create mode 100644 libc/src/stdlib/linux/clearenv.cpp
 create mode 100644 libc/test/integration/src/stdlib/clearenv_test.cpp

diff --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt
index c7ded2fade05d..6a9a17b11819c 100644
--- a/libc/config/linux/aarch64/entrypoints.txt
+++ b/libc/config/linux/aarch64/entrypoints.txt
@@ -1330,6 +1330,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 296985eeff937..4ff569e027145 100644
--- a/libc/config/linux/riscv/entrypoints.txt
+++ b/libc/config/linux/riscv/entrypoints.txt
@@ -1511,6 +1511,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 7928a35a6284a..f7bf51b0bee83 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -1537,6 +1537,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 4c2f3337cd45c..2498080e044d7 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 faa31c62d1d8e..33f4c9d7e282f 100644
--- a/libc/src/stdlib/CMakeLists.txt
+++ b/libc/src/stdlib/CMakeLists.txt
@@ -728,6 +728,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 e8711b7398586..50e9a31a609a8 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