Fix ASAN/LSAN failures: ppath_home stack-use-after-scope + intentional leak

pclsync/ppath.c: Move buff[4096] to function scope in ppath_home() so
the pointer stored in dir via result->pw_dir remains live through the
putil_strdup(dir) call. Previously buff went out of scope at the if-block
close, causing a stack-use-after-scope ASAN report on every call that fell
through the getpwuid_r path.

tests/unit-tests/test_ptools_errptr.c: run_unfixed() intentionally leaks
errPtr to demonstrate the pre-fix bug. Wrap the allocation with
LSAN_DISABLE() / LSAN_ENABLE() so LSAN does not abort the process at exit
before stdio flushes, which was causing a non-zero exit code. The guard
uses nested #ifdef/__has_feature to remain compatible with both GCC
(__SANITIZE_ADDRESS__) and Clang (__has_feature(address_sanitizer))
without triggering "missing binary operator" errors on GCC.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Levi Neely 2026-03-10 19:59:33 +01:00
parent 92bfab2ba4
commit b30efe68fb
2 changed files with 29 additions and 1 deletions

View File

@ -58,12 +58,14 @@ int64_t ppath_free_space(const char *path) {
char *ppath_home() {
struct stat st;
const char *dir;
/* buff must be function-scoped: dir may point into it (via pw_dir) and
* must remain valid through the putil_strdup(dir) call below. */
char buff[4096];
dir = getenv("HOME");
if (pdbg_unlikely(!dir) || pdbg_unlikely(stat(dir, &st)) ||
pdbg_unlikely(!pfile_stat_mode_ok(&st, 7))) {
struct passwd pwd;
struct passwd *result;
char buff[4096];
if (pdbg_unlikely(getpwuid_r(getuid(), &pwd, buff, sizeof(buff), &result)) ||
pdbg_unlikely(stat(result->pw_dir, &st)) ||
pdbg_unlikely(!pfile_stat_mode_ok(&st, 7)))

View File

@ -18,6 +18,28 @@
#include <string.h>
#include <stdint.h>
/* Suppress LSAN reports for intentional leaks used to verify bug behaviour.
* GCC sets __SANITIZE_ADDRESS__; Clang exposes __has_feature as a built-in.
* Use nested #if so the __has_feature() call is only evaluated when the
* compiler actually understands it (avoids "missing binary operator" on GCC). */
#ifdef __SANITIZE_ADDRESS__
# include <sanitizer/lsan_interface.h>
# define LSAN_DISABLE() __lsan_disable()
# define LSAN_ENABLE() __lsan_enable()
#elif defined(__has_feature)
# if __has_feature(address_sanitizer)
# include <sanitizer/lsan_interface.h>
# define LSAN_DISABLE() __lsan_disable()
# define LSAN_ENABLE() __lsan_enable()
# else
# define LSAN_DISABLE() do {} while (0)
# define LSAN_ENABLE() do {} while (0)
# endif
#else
# define LSAN_DISABLE() do {} while (0)
# define LSAN_ENABLE() do {} while (0)
#endif
/* ------------------------------------------------------------------ */
/* Allocation tracking via --wrap */
/* ------------------------------------------------------------------ */
@ -119,6 +141,9 @@ static int run_unfixed(void) {
char *errPtr = NULL;
int callRes;
/* Intentional leak: suppress LSAN so the process exits cleanly and
* stdio flushes before the sanitizer reports it. */
LSAN_DISABLE();
callRes = mock_backend_call_1(&errPtr);
(void)callRes;
@ -129,6 +154,7 @@ static int run_unfixed(void) {
if (errPtr)
free(errPtr);
LSAN_ENABLE();
return 0;
}