Files
AnyPS5/core/libs/tests/GuestEnvironment.cpp
T
Claude dbce646e7b fix(libc): keep guest environment strings alive across setenv and unsetenv
setenv replaced a variable's string with a new allocation and freed the
old one, and unsetenv freed it as well, so a pointer that getenv had
returned for that variable dangled. A guest that reads such a pointer
after setenv(name, value, 1) reads freed host memory: AddressSanitizer
reports a heap-use-after-free on that read.

FreeBSD 11.4's getenv.c, which holds the setenv, unsetenv and putenv the
console's libc exports next to getenv, never frees a variable's string
("No previous variables are ever freed here to avoid causing a
segmentation fault in a user's code", lib/libc/stdlib/getenv.c lines
449-520 at 8c639945a4). setenv and unsetenv mark the old string
inactive; setenv writes the new value over the latest string of that
name when the value fits and allocates a new string otherwise; putenv
strings stay the caller's and are only forgotten.

The guest environment now keeps every string it allocates with its value
capacity and an active flag and follows that code: getenv and the time
zone lookup read the name's latest active string, setenv reuses the
latest string of the name when it fits, an unset one included, and
otherwise appends a new one and deactivates the old, putenv replaces an
earlier putenv string or deactivates the setenv one, and unsetenv
deactivates them all. Built on the host next to this library, FreeBSD's
getenv.c gave the same results, errno, pointer reuse and contents of
every earlier pointer over 160,000 random calls once the putenv flag it
leaves uninitialized in new setenv strings was cleared; the debt entry
records that the console may allocate where this reuses.

guest_environment checks the reuse, that earlier values stay readable
after a longer setenv and after unsetenv, and that putenv and setenv
leave each other's strings alone.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01DXPnhogDW4zpeNCBro4Ctt
2026-10-27 17:39:23 +00:00

80 lines
4.0 KiB
C++

#include "prx/libc/include/Api.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include <cstdlib>
#include <cstring>
#include <string>
static void Require(bool value) { if (!value) std::abort(); }
int main() {
const char* key = "ANYPS5_GUEST_ENV_TEST_4C27";
#ifdef _WIN32
Require(_putenv_s(key, "host") == 0);
#else
Require(::setenv(key, "host", 1) == 0);
#endif
Require(getenv_nid_postfix(key) == nullptr);
#ifdef _WIN32
Require(_putenv_s("HOME", "C:\\Users\\host") == 0);
#else
Require(::setenv("HOME", "/home/host", 1) == 0);
#endif
for (const char* host : {"HOME", "PATH"}) Require(std::getenv(host) != nullptr && getenv_nid_postfix(host) == nullptr);
Require(setenv_nid_postfix(key, "inherited", 0) == 0);
Require(setenv_nid_postfix(key, "kept out", 0) == 0);
Require(std::strcmp(getenv_nid_postfix(key), "inherited") == 0);
char value[] = "copied";
Require(setenv_nid_postfix(key, value, 1) == 0);
value[0] = 'X';
Require(std::strcmp(getenv_nid_postfix(key), "copied") == 0);
Require(std::strcmp(std::getenv(key), "host") == 0);
Require(setenv_nid_postfix(key, "", 1) == 0);
Require(getenv_nid_postfix(key) && *getenv_nid_postfix(key) == 0);
char borrowed[] = "ANYPS5_GUEST_ENV_TEST_4C27=one";
Require(putenv_nid_postfix(borrowed) == 0);
char* position = std::strchr(borrowed, '=') + 1;
Require(getenv_nid_postfix(key) == position);
std::memcpy(position, "two", 3);
Require(std::strcmp(getenv_nid_postfix(key), "two") == 0);
Require(setenv_nid_postfix("anyps5_guest_env_test_4c27", "lower", 1) == 0);
Require(std::strcmp(getenv_nid_postfix(key), "two") == 0);
Require(setenv_nid_postfix("bad=name", "x", 1) == -1);
Require(*__error_nid_postfix() == 22);
const char* const invalidNames[] = {nullptr, "", "bad=name"};
for (const char* name : invalidNames) {
*__error_nid_postfix() = 0;
Require(getenv_nid_postfix(name) == nullptr && *__error_nid_postfix() == 22);
}
*__error_nid_postfix() = 34;
Require(getenv_nid_postfix("ANYPS5_GUEST_ENV_ABSENT_4C27") == nullptr && *__error_nid_postfix() == 34);
Require(unsetenv_nid_postfix("") == -1);
char invalid[] = "no-separator";
Require(putenv_nid_postfix(invalid) == -1);
Require(unsetenv_nid_postfix(key) == 0);
Require(getenv_nid_postfix(key) == nullptr);
Require(unsetenv_nid_postfix(key) == 0);
Require(unsetenv_nid_postfix("anyps5_guest_env_test_4c27") == 0);
Require(setenv_nid_postfix("Q7A", "a", 1) == 0);
Require(setenv_nid_postfix("Q7B", "b", 1) == 0);
const char* kept = getenv_nid_postfix("Q7B");
Require(unsetenv_nid_postfix("Q7A") == 0);
for (int index = 0; index < 64; ++index)
Require(setenv_nid_postfix(("ANYPS5_GUEST_ENV_GROW_4C27_" + std::to_string(index)).c_str(), "x", 1) == 0);
Require(getenv_nid_postfix("Q7B") == kept && std::strcmp(kept, "b") == 0);
Require(setenv_nid_postfix("Q7C", "abc", 1) == 0);
char* const first = getenv_nid_postfix("Q7C");
Require(setenv_nid_postfix("Q7C", "xy", 1) == 0);
Require(getenv_nid_postfix("Q7C") == first && std::strcmp(first, "xy") == 0);
Require(setenv_nid_postfix("Q7C", "longer value", 1) == 0);
char* const second = getenv_nid_postfix("Q7C");
Require(second != first && std::strcmp(second, "longer value") == 0 && std::strcmp(first, "xy") == 0);
Require(unsetenv_nid_postfix("Q7C") == 0);
Require(getenv_nid_postfix("Q7C") == nullptr && std::strcmp(second, "longer value") == 0);
Require(setenv_nid_postfix("Q7C", "again", 0) == 0);
Require(getenv_nid_postfix("Q7C") == second && std::strcmp(second, "again") == 0);
char lent[] = "Q7C=lent";
Require(putenv_nid_postfix(lent) == 0);
Require(getenv_nid_postfix("Q7C") == lent + 4 && std::strcmp(second, "again") == 0);
Require(setenv_nid_postfix("Q7C", "own", 1) == 0);
Require(std::strcmp(getenv_nid_postfix("Q7C"), "own") == 0 && std::strcmp(lent, "Q7C=lent") == 0 && std::strcmp(second, "again") == 0);
}