commit 253efe7357b42df840d4dc45a55f0cf4a765df34 from: Sergey Bronnikov via: Sergey Bronnikov date: Tue Oct 14 18:41:01 2025 UTC luzer: fix memory leak in FDP Fixes #52 commit - fc4a32fe98f1da8b07f74a35f40b678692e7152b commit + 253efe7357b42df840d4dc45a55f0cf4a765df34 blob - ef7cf4a9aa6384454941dd31063c85a568046ce3 blob + 0dc4fe20be52bd11a239b08f679bae64e99f86a2 --- CHANGELOG.md +++ CHANGELOG.md @@ -48,3 +48,4 @@ and this project adheres to [Semantic Versioning](http - Arguments checking in `Fuzz()` (#41). - A memory leak in a Lua-based implementation of `TestOneInput()`. - An initial buffer size in FuzzedDataProvider. +- Memory leak in FuzzedDataProvider (#52). blob - 35f0c1738ff5cc86ffeb13dee4f8730d693633f5 blob + 431246052f162aa44e83323c8d2ae2a22dabaf28 --- luzer/luzer.c +++ luzer/luzer.c @@ -345,6 +345,8 @@ TestOneInput(const uint8_t* data, size_t size) { /* Disable debug hook. */ LUA_SETHOOK(L, debug_hook, 0, 0); + lua_gc(L, LUA_GCCOLLECT, 0); + return rc; } @@ -438,6 +440,66 @@ free_argv(int argc, char **argv) } NO_SANITIZE static int +os_exit(int exit_code) { + lua_State *L = luaL_newstate(); + luaL_openlibs(L); + lua_getglobal(L, "os"); + if (!lua_istable(L, -1)) { + fprintf(stderr, "Error: 'os' table not found.\n"); + lua_close(L); + return 1; + } + + lua_getfield(L, -1, "exit"); + if (!lua_isfunction(L, -1)) { + fprintf(stderr, "Error: 'os.exit' function not found.\n"); + lua_close(L); + return 1; + } + + // Remove the 'os' table from the stack, leaving only 'os.exit' + lua_remove(L, -2); + // Optional: Push an exit code argument (e.g., 1 for error) + lua_pushinteger(L, exit_code); + lua_pushboolean(L, 1); + lua_call(L, 1, 0); + + // This part will only be reached if os.exit somehow fails + // or doesn't exit the process. + lua_close(L); + return 0; +} + +NO_SANITIZE static void +shutdown_lua(void) +{ + lua_State *L = get_global_lua_state(); + lua_gc(L, LUA_GCCOLLECT, 0); + luaL_cleanup(L); + lua_close(L); + set_global_lua_state(NULL); +} + +int atexit_retcode; + +NO_SANITIZE void +xxx(void) { + _exit(atexit_retcode); +} + +NO_SANITIZE static void +graceful_exit(int retcode, bool prevent_crash_report) { + prevent_crash_report = true; + if (prevent_crash_report) { + // Disable libfuzzer's atexit. + atexit_retcode = retcode; + atexit(&xxx); + } + shutdown_lua(); + os_exit(retcode); +} + +NO_SANITIZE static int luaL_fuzz(lua_State *L) { const char *str = luaL_checkstring(L, -1); @@ -534,14 +596,9 @@ luaL_fuzz(lua_State *L) jit_status = luajit_has_enabled_jit(L); set_global_lua_state(L); - int rc = LLVMFuzzerRunDriver(&argc, &argv, &TestOneInput); + graceful_exit(LLVMFuzzerRunDriver(&argc, &argv, &TestOneInput), true); - free_argv(argc, argv); - luaL_cleanup(L); - - lua_pushnumber(L, rc); - - return 1; + return 0; } static const struct luaL_Reg Module[] = { blob - fe734815c350ac052811554b84c94905ec236be5 blob + 3a1ae9f89c17714f98a88349ebe0ee19c079960d --- luzer/tests/CMakeLists.txt +++ luzer/tests/CMakeLists.txt @@ -237,11 +237,9 @@ if (LUA_HAS_JIT) "${TEST_ENV};FFI_LIB_NAME=testlib.so" "Done 10 runs in 0 second" ) - # XXX: Memory leak in FDP is expected, should be fixed in [1]. - # 1. https://github.com/ligurio/luzer/issues/52 generate_ffi_test(luzer_ffi_asan "${TEST_ENV};LD_PRELOAD=${ASAN_DSO_PATH};FFI_LIB_NAME=testlib_asan.so" - "LeakSanitizer: detected memory leaks" + "Done 10 runs in 0 second" ) generate_ffi_test(luzer_ffi_ubsan "${TEST_ENV};LD_PRELOAD=${UBSAN_DSO_PATH};FFI_LIB_NAME=testlib_ubsan.so"