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
Two host differences failed all 19 feupdateenv tests on macOS:
- Apple's libc++ looks up /usr/lib/libstdc++.6.dylib with a no-load
dlopen the first time a process builds a std::logic_error, and that
failed lookup leaves errno at ENOENT. The null check builds the first
one, so it saw errno change although feupdateenv never touched it. The
test now builds one std::invalid_argument on macOS before the checks.
- Rosetta 2 keeps the six MXCSR exception masks set and raises no
floating-point trap, x87 included. The mask check now expects the SSE
masks the host keeps, and the pending-trap tests are skipped (exit 77)
on a host that does not keep an unmasked MXCSR exception. A libc debt
entry for feupdateenv records it.
On Windows the UCRT drops a write that directly follows a read on an r+,
w+ or a+ stream: fputc and fprintf report success, fwrite returns 0 and
fputs returns EOF, all without the error indicator, and the file is left
unchanged, also after an fflush. On a stream open only for reading, fputc
after a read reports success as well.
Writes now first do what FreeBSD 11.4's __swsetup does: a stream open only
for reading fails with EBADF and sets the error indicator, and an update
stream that is reading drops its read buffer and ungetc data, clears end
of file and writes at the descriptor's offset. This covers fwrite, fputs,
fputc, putc_unlocked, fprintf, fprintf_s, fputwc, fputws, fwprintf,
fwprintf_s, puts and wprintf, and printf, vprintf and printf_s, which
write to standard output through PrintWindows and now return -1 there
instead of throwing.
Linux is unchanged: glibc writes at the stream position, which the libc
technical debt now records.
A long double %La went to the host, which on Windows (MinGW) prints the
64-bit significand with its integer bit as the leading digit:
0xc.ccccccccccccccdp-7 for 0.1L. The libc.prx that titles ship
normalises it like a double, with a leading 1 and the remaining 63 bits
as 16 hexadecimal digits, 0x1.999999999999999ap-4, and rounds a shorter
precision half up with the carry kept in the leading digit. Share
HexFloat's digit and rounding code between double and long double and
use it for finite long double %a and %A in the narrow and wide families.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Console libSceLibcInternal exports Y9C9GeKyZ3A as a 136-byte STT_OBJECT;
titles that resolve the wide num_get vtable as data need a zeroed 17-slot
object matching the narrow num_get layout on console.