From b30efe68fb9506014fd60f2fca18425c8c781f85 Mon Sep 17 00:00:00 2001 From: Levi Neely Date: Tue, 10 Mar 2026 19:59:33 +0100 Subject: [PATCH] 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 --- pclsync/ppath.c | 4 +++- tests/unit-tests/test_ptools_errptr.c | 26 ++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/pclsync/ppath.c b/pclsync/ppath.c index e4a053b..672928f 100644 --- a/pclsync/ppath.c +++ b/pclsync/ppath.c @@ -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))) diff --git a/tests/unit-tests/test_ptools_errptr.c b/tests/unit-tests/test_ptools_errptr.c index 42edb7e..4a0a2a3 100644 --- a/tests/unit-tests/test_ptools_errptr.c +++ b/tests/unit-tests/test_ptools_errptr.c @@ -18,6 +18,28 @@ #include #include +/* 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 +# define LSAN_DISABLE() __lsan_disable() +# define LSAN_ENABLE() __lsan_enable() +#elif defined(__has_feature) +# if __has_feature(address_sanitizer) +# include +# 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; }