From 121592a2656e9f465f9025d09c138e75f8b1591c Mon Sep 17 00:00:00 2001 From: Sergey Bronnikov Date: Fri, 30 May 2025 18:10:06 +0300 Subject: [PATCH 1/2] cmake: replace string with env as a list Needed for the following commit. --- tests/lapi/CMakeLists.txt | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/lapi/CMakeLists.txt b/tests/lapi/CMakeLists.txt index 8f2836d..0f95643 100644 --- a/tests/lapi/CMakeLists.txt +++ b/tests/lapi/CMakeLists.txt @@ -16,6 +16,13 @@ lapi_tests_make_lua_path(LUA_PATH ${CMAKE_CURRENT_SOURCE_DIR}/?.lua ) +list(APPEND TEST_ENV + "LUA_PATH=${LUA_PATH}" + "LUA_CPATH=${LUA_CPATH}" + ASAN_OPTIONS=detect_odr_violation=0 + LD_DYNAMIC_WEAK=1 +) + function(create_test) cmake_parse_arguments( FUZZ @@ -45,7 +52,7 @@ function(create_test) ) set_tests_properties(${test_name} PROPERTIES LABELS "lapi" - ENVIRONMENT "LUA_PATH=${LUA_PATH};LUA_CPATH=${LUA_CPATH};ASAN_OPTIONS=detect_odr_violation=0;LD_DYNAMIC_WEAK=1" + ENVIRONMENT "${TEST_ENV}" DEPENDS ${LUA_EXECUTABLE} ${LUZER_LIBRARY} ) endfunction() From a0075775eca6dd022eec4335c99231c89a46f878 Mon Sep 17 00:00:00 2001 From: Sergey Bronnikov Date: Fri, 30 May 2025 18:10:34 +0300 Subject: [PATCH 2/2] cmake: support coverage-guided testing in Lua MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When using a sanitizer like Address Sanitizer with luzer, it’s necessary to LD_PRELOAD the sanitizer's shared object. ASan requires that it be loaded first, before anything else; it must either be preloaded, or statically linked into the executable (in this case, the Lua interpreter). ASan and UBSan define many of the same code coverage symbols as libFuzzer. In typical libFuzzer usage, this isn’t an issue, since ASan/UBSan declare those symbols weak; the libFuzzer ones take precedence. But when libFuzzer is loaded in a shared object later, that doesn't work. The symbols from ASan/UBSan have already been loaded via LD_PRELOAD, and coverage information therefore goes to those libraries, leaving libFuzzer very broken. The only good way to solve this is to link libFuzzer into Lua itself, instead of luzer. Since it's therefore part of the proper executable rather than a shared object that's dynamically loaded later, symbol resolution works correctly and libFuzzer symbols take precedence. ``` cd build/lua-master/source mkdir lf cp /usr/lib/llvm-21/lib/clang/21/lib/linux/libclang_rt.fuzzer_no_main-x86_64.a lf/ cd lf/ ar x libclang_rt.fuzzer_no_main-x86_64.a ``` The patch make the following changes: - rename cmake/SetClangLibRT.cmake to cmake/LibFuzzer.cmake - bump luzer version to the latest - add a CMake function that unpacks LibFuzzer - update PUC Rio Lua and LuaJIT patches so their build systems can link LibFuzzer object files with Lua runtime executable binaries 1. https://security.googleblog.com/2020/12/how-atheris-python-fuzzer-works.html 2. https://github.com/google/atheris/blob/master/native_extension_fuzzing.md#option-b-linking-libfuzzer-into-python 3. https://github.com/google/atheris/blob/master/libfuzzer_mod/cpython-3.8.6-add-libFuzzer.patch Follows up tarantool/tarantool#11884 --- CMakeLists.txt | 6 ++++ cmake/BuildLua.cmake | 2 ++ cmake/BuildLuaJIT.cmake | 2 ++ cmake/BuildLuzer.cmake | 2 +- cmake/LibFuzzer.cmake | 62 +++++++++++++++++++++++++++++++++++++++ patches/luajit-v2.1.patch | 45 ++++++++++++++++++++++------ patches/puc-rio-lua.patch | 16 +++++++++- 7 files changed, 124 insertions(+), 11 deletions(-) create mode 100644 cmake/LibFuzzer.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 5fa8dc2..17c657e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,6 +33,12 @@ set(CMAKE_INCLUDE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake" ${CMAKE_INCLUDE_PATH} include(SetBuildParallelLevel) include(SetHardwareArch) +if (ENABLE_LAPI_TESTS) + include(LibFuzzer) + SetLibFuzzerPath(FUZZER_NO_MAIN_LIBRARY) + SetLibFuzzerObjDir(LibFuzzerObjDir) +endif() + if (USE_LUA AND NOT LUA_VERSION) set(LUA_VERSION "master") endif() diff --git a/cmake/BuildLua.cmake b/cmake/BuildLua.cmake index b9e27c5..d1a39c3 100644 --- a/cmake/BuildLua.cmake +++ b/cmake/BuildLua.cmake @@ -65,6 +65,7 @@ macro(build_lua LUA_VERSION) # `io.popen()` is not supported by default, it is enabled # by `LUA_USE_POSIX` flag. Required by a function `random_locale()`. set(CFLAGS "${CFLAGS} -DLUA_USE_POSIX") + set(LDFLAGS "${LDFLAGS} -lstdc++") endif() include(ExternalProject) @@ -90,6 +91,7 @@ macro(build_lua LUA_VERSION) BUILD_COMMAND cd && make -j CC=${CMAKE_C_COMPILER} CFLAGS=${CFLAGS} LDFLAGS=${LDFLAGS} + LF_PATH=${LibFuzzerObjDir} INSTALL_COMMAND "" BUILD_BYPRODUCTS ${LUA_LIBRARY} ${LUA_EXECUTABLE} diff --git a/cmake/BuildLuaJIT.cmake b/cmake/BuildLuaJIT.cmake index 28630f2..e8ccae5 100644 --- a/cmake/BuildLuaJIT.cmake +++ b/cmake/BuildLuaJIT.cmake @@ -88,6 +88,7 @@ macro(build_luajit LJ_VERSION) # CMake option LUAJIT_FRIENDLY_MODE in luzer requires # LUAJIT_ENABLE_CHECKHOOK. set(CFLAGS "${CFLAGS} -DLUAJIT_ENABLE_CHECKHOOK") + set(LDFLAGS "${LDFLAGS} -lstdc++") endif() include(ExternalProject) @@ -116,6 +117,7 @@ macro(build_luajit LJ_VERSION) CFLAGS=${CFLAGS} LDFLAGS=${LDFLAGS} HOST_CFLAGS=-fno-sanitize=undefined + LF_PATH=${LibFuzzerObjDir} -C src INSTALL_COMMAND "" diff --git a/cmake/BuildLuzer.cmake b/cmake/BuildLuzer.cmake index 833204b..dd68036 100644 --- a/cmake/BuildLuzer.cmake +++ b/cmake/BuildLuzer.cmake @@ -34,7 +34,7 @@ endif() ExternalProject_Add(bundled-luzer GIT_REPOSITORY https://github.com/ligurio/luzer - GIT_TAG fc4a32fe98f1da8b07f74a35f40b678692e7152b + GIT_TAG e4624477735961457f562423ef2ba9afa51ba170 GIT_PROGRESS TRUE GIT_SHALLOW FALSE SOURCE_DIR ${LUZER_DIR}/source diff --git a/cmake/LibFuzzer.cmake b/cmake/LibFuzzer.cmake new file mode 100644 index 0000000..a763d32 --- /dev/null +++ b/cmake/LibFuzzer.cmake @@ -0,0 +1,62 @@ +# The function sets the given variable in a parent scope to a +# string with hardware architecture name and this name should +# match to hardware architecture name used in a library name of +# libclang_rt.fuzzer_no_main: aarch64, x86_64, i386. +function(SetHwArchString outvar) + set(${outvar} ${CMAKE_SYSTEM_PROCESSOR} PARENT_SCOPE) +endfunction() + +# The function sets the given variable in a parent scope to a +# value with path to libclang_rt.fuzzer_no_main [1] library. +# The function raises a fatal message if C compiler is not Clang. +# +# $ clang-15 -print-file-name=libclang_rt.fuzzer_no_main-x86_64.a +# $ /usr/lib/llvm-15/lib/clang/15.0.7/lib/linux/libclang_rt.fuzzer_no_main-x86_64.a +# +# 1. https://llvm.org/docs/LibFuzzer.html#using-libfuzzer-as-a-library +function(SetLibFuzzerPath outvar) + if (NOT CMAKE_C_COMPILER_ID STREQUAL "Clang") + message(FATAL_ERROR "C compiler is not a Clang") + endif () + + SetHwArchString(HW_ARCH) + if (CMAKE_SYSTEM_NAME STREQUAL "Linux") + set(lib_name "libclang_rt.fuzzer_no_main-${HW_ARCH}.a") + else() + message(FATAL_ERROR "Unsupported system: ${CMAKE_SYSTEM_NAME}") + endif() + + execute_process(COMMAND ${CMAKE_C_COMPILER} "-print-file-name=${lib_name}" + RESULT_VARIABLE CMD_ERROR + OUTPUT_VARIABLE CMD_OUTPUT + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + if (CMD_ERROR) + message(FATAL_ERROR "${CMD_ERROR}") + endif() + + if(NOT EXISTS ${CMD_OUTPUT}) + message(FATAL_ERROR "${lib_name} was not found.") + endif() + + set(${outvar} ${CMD_OUTPUT} PARENT_SCOPE) +endfunction() + +# The function unpack libFuzzer archive located at +# to a directory and return a path to a directory +# with libFuzzer's object files. +function(SetLibFuzzerObjDir outvar) + set(LibFuzzerDir ${PROJECT_BINARY_DIR}/libFuzzer_unpacked) + file(MAKE_DIRECTORY ${LibFuzzerDir}) + SetLibFuzzerPath(LibFuzzerPath) + execute_process( + COMMAND ${CMAKE_AR} x ${LibFuzzerPath} --output ${LibFuzzerDir} + RESULT_VARIABLE CMD_ERROR + OUTPUT_VARIABLE CMD_OUTPUT + WORKING_DIRECTORY ${LibFuzzerDir} + ) + if (CMD_ERROR) + message(FATAL_ERROR "${CMD_ERROR}") + endif() + set(${outvar} ${LibFuzzerDir} PARENT_SCOPE) +endfunction() diff --git a/patches/luajit-v2.1.patch b/patches/luajit-v2.1.patch index 2ee1561..a97e91e 100644 --- a/patches/luajit-v2.1.patch +++ b/patches/luajit-v2.1.patch @@ -1,5 +1,32 @@ +diff --git a/src/Makefile b/src/Makefile +index 969bf289..23c4264b 100644 +--- a/src/Makefile ++++ b/src/Makefile +@@ -514,6 +514,10 @@ LJLIB_O= lib_base.o lib_math.o lib_bit.o lib_string.o lib_table.o \ + lib_buffer.o + LJLIB_C= $(LJLIB_O:.o=.c) + ++ifneq ($(LF_PATH),) ++LIBFUZZER_O= $(shell find $(LF_PATH) -maxdepth 1 -name '*.o') ++endif ++ + LJCORE_O= lj_assert.o lj_gc.o lj_err.o lj_char.o lj_bc.o lj_obj.o lj_buf.o \ + lj_str.o lj_tab.o lj_func.o lj_udata.o lj_meta.o lj_debug.o \ + lj_prng.o lj_state.o lj_dispatch.o lj_vmevent.o lj_vmmath.o \ +@@ -748,9 +752,9 @@ $(LUAJIT_SO): $(LJVMCORE_O) + $(Q)$(TARGET_LD) $(TARGET_ASHLDFLAGS) -o $@ $(LJVMCORE_DYNO) $(TARGET_ALIBS) + $(Q)$(TARGET_STRIP) $@ + +-$(LUAJIT_T): $(TARGET_O) $(LUAJIT_O) $(TARGET_DEP) ++$(LUAJIT_T): $(TARGET_O) $(LUAJIT_O) $(TARGET_DEP) $(LIBFUZZER_O) + $(E) "LINK $@" +- $(Q)$(TARGET_LD) $(TARGET_ALDFLAGS) -o $@ $(LUAJIT_O) $(TARGET_O) $(TARGET_ALIBS) ++ $(Q)$(TARGET_LD) $(TARGET_ALDFLAGS) -o $@ $(LUAJIT_O) $(TARGET_O) $(TARGET_ALIBS) $(LIBFUZZER_O) + $(Q)$(TARGET_STRIP) $@ + $(E) "OK Successfully built LuaJIT" + diff --git a/src/host/buildvm.c b/src/host/buildvm.c -index ec99e501..d23530c4 100644 +index 24db75f4..021e7dbe 100644 --- a/src/host/buildvm.c +++ b/src/host/buildvm.c @@ -35,6 +35,10 @@ @@ -14,7 +41,7 @@ index ec99e501..d23530c4 100644 /* DynASM glue definitions. */ diff --git a/src/lj_buf.h b/src/lj_buf.h -index 744e5747..ea299472 100644 +index 15a04250..ef701256 100644 --- a/src/lj_buf.h +++ b/src/lj_buf.h @@ -165,6 +165,13 @@ LJ_FUNC SBuf * LJ_FASTCALL lj_buf_putchar(SBuf *sb, int c); @@ -32,7 +59,7 @@ index 744e5747..ea299472 100644 { return (char *)memcpy(p, q, len) + len; diff --git a/src/lj_carith.c b/src/lj_carith.c -index 9bea0a33..046dea4c 100644 +index b09812c6..98128daa 100644 --- a/src/lj_carith.c +++ b/src/lj_carith.c @@ -159,6 +159,11 @@ static int carith_ptr(lua_State *L, CTState *cts, CDArith *ca, MMS mm) @@ -48,7 +75,7 @@ index 9bea0a33..046dea4c 100644 { if (ctype_isnum(ca->ct[0]->info) && ca->ct[0]->size <= 8 && diff --git a/src/lj_opt_fold.c b/src/lj_opt_fold.c -index ce78505b..bc9d64f3 100644 +index 456c04b2..4edfa742 100644 --- a/src/lj_opt_fold.c +++ b/src/lj_opt_fold.c @@ -260,6 +260,11 @@ LJFOLDF(kfold_numcomp) @@ -64,10 +91,10 @@ index ce78505b..bc9d64f3 100644 { switch (op) { diff --git a/src/lj_parse.c b/src/lj_parse.c -index 5a44f8db..bfe044a8 100644 +index 832f6bf4..7d0390e4 100644 --- a/src/lj_parse.c +++ b/src/lj_parse.c -@@ -934,6 +934,11 @@ static void bcemit_binop(FuncState *fs, BinOpr op, ExpDesc *e1, ExpDesc *e2) +@@ -935,6 +935,11 @@ static void bcemit_binop(FuncState *fs, BinOpr op, ExpDesc *e1, ExpDesc *e2) } /* Emit unary operator. */ @@ -80,7 +107,7 @@ index 5a44f8db..bfe044a8 100644 { if (op == BC_NOT) { diff --git a/src/lj_snap.c b/src/lj_snap.c -index 6fda08ba..c7f51d7d 100644 +index d0d28c81..c8d5ffcc 100644 --- a/src/lj_snap.c +++ b/src/lj_snap.c @@ -763,6 +763,13 @@ static void snap_restoreval(jit_State *J, GCtrace *T, ExitState *ex, @@ -98,7 +125,7 @@ index 6fda08ba..c7f51d7d 100644 static void snap_restoredata(jit_State *J, GCtrace *T, ExitState *ex, SnapNo snapno, BloomFilter rfilt, diff --git a/src/lj_str.c b/src/lj_str.c -index cfdaec6f..88f9c765 100644 +index f34d6d95..806c67db 100644 --- a/src/lj_str.c +++ b/src/lj_str.c @@ -13,6 +13,15 @@ @@ -118,7 +145,7 @@ index cfdaec6f..88f9c765 100644 /* Ordered compare of strings. Assumes string data is 4-byte aligned. */ diff --git a/src/lj_strfmt.c b/src/lj_strfmt.c -index 909255db..ef9bd4f9 100644 +index 0936298d..32e93c42 100644 --- a/src/lj_strfmt.c +++ b/src/lj_strfmt.c @@ -99,6 +99,11 @@ retlit: diff --git a/patches/puc-rio-lua.patch b/patches/puc-rio-lua.patch index f25b584..2598a82 100644 --- a/patches/puc-rio-lua.patch +++ b/patches/puc-rio-lua.patch @@ -1,5 +1,5 @@ diff --git a/makefile b/makefile -index 8674519f..17dabfa8 100644 +index 8674519f..1773b0eb 100644 --- a/makefile +++ b/makefile @@ -39,7 +39,7 @@ CWARNSC= -Wdeclaration-after-statement \ @@ -26,3 +26,17 @@ index 8674519f..17dabfa8 100644 AR= ar rc RANLIB= ranlib RM= rm -f +@@ -96,9 +96,12 @@ CORE_O= lapi.o lcode.o lctype.o ldebug.o ldo.o ldump.o lfunc.o lgc.o llex.o \ + AUX_O= lauxlib.o + LIB_O= lbaselib.o ldblib.o liolib.o lmathlib.o loslib.o ltablib.o lstrlib.o \ + lutf8lib.o loadlib.o lcorolib.o linit.o ++ifneq ($(LF_PATH),) ++LF_O= $(shell find $(LF_PATH) -maxdepth 1 -name '*.o') ++endif + + LUA_T= lua +-LUA_O= lua.o ++LUA_O= lua.o $(LF_O) + + + ALL_T= $(CORE_T) $(LUA_T)