Skip to content

Commit a007577

Browse files
committed
cmake: support coverage-guided testing in Lua
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
1 parent 121592a commit a007577

File tree

7 files changed

+124
-11
lines changed

7 files changed

+124
-11
lines changed

CMakeLists.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ set(CMAKE_INCLUDE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake" ${CMAKE_INCLUDE_PATH}
3333
include(SetBuildParallelLevel)
3434
include(SetHardwareArch)
3535

36+
if (ENABLE_LAPI_TESTS)
37+
include(LibFuzzer)
38+
SetLibFuzzerPath(FUZZER_NO_MAIN_LIBRARY)
39+
SetLibFuzzerObjDir(LibFuzzerObjDir)
40+
endif()
41+
3642
if (USE_LUA AND NOT LUA_VERSION)
3743
set(LUA_VERSION "master")
3844
endif()

cmake/BuildLua.cmake

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ macro(build_lua LUA_VERSION)
6565
# `io.popen()` is not supported by default, it is enabled
6666
# by `LUA_USE_POSIX` flag. Required by a function `random_locale()`.
6767
set(CFLAGS "${CFLAGS} -DLUA_USE_POSIX")
68+
set(LDFLAGS "${LDFLAGS} -lstdc++")
6869
endif()
6970

7071
include(ExternalProject)
@@ -90,6 +91,7 @@ macro(build_lua LUA_VERSION)
9091
BUILD_COMMAND cd <SOURCE_DIR> && make -j CC=${CMAKE_C_COMPILER}
9192
CFLAGS=${CFLAGS}
9293
LDFLAGS=${LDFLAGS}
94+
LF_PATH=${LibFuzzerObjDir}
9395
INSTALL_COMMAND ""
9496

9597
BUILD_BYPRODUCTS ${LUA_LIBRARY} ${LUA_EXECUTABLE}

cmake/BuildLuaJIT.cmake

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ macro(build_luajit LJ_VERSION)
8888
# CMake option LUAJIT_FRIENDLY_MODE in luzer requires
8989
# LUAJIT_ENABLE_CHECKHOOK.
9090
set(CFLAGS "${CFLAGS} -DLUAJIT_ENABLE_CHECKHOOK")
91+
set(LDFLAGS "${LDFLAGS} -lstdc++")
9192
endif()
9293

9394
include(ExternalProject)
@@ -116,6 +117,7 @@ macro(build_luajit LJ_VERSION)
116117
CFLAGS=${CFLAGS}
117118
LDFLAGS=${LDFLAGS}
118119
HOST_CFLAGS=-fno-sanitize=undefined
120+
LF_PATH=${LibFuzzerObjDir}
119121
-C src
120122
INSTALL_COMMAND ""
121123

cmake/BuildLuzer.cmake

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ endif()
3434

3535
ExternalProject_Add(bundled-luzer
3636
GIT_REPOSITORY https://github.com/ligurio/luzer
37-
GIT_TAG fc4a32fe98f1da8b07f74a35f40b678692e7152b
37+
GIT_TAG e4624477735961457f562423ef2ba9afa51ba170
3838
GIT_PROGRESS TRUE
3939
GIT_SHALLOW FALSE
4040
SOURCE_DIR ${LUZER_DIR}/source

cmake/LibFuzzer.cmake

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# The function sets the given variable in a parent scope to a
2+
# string with hardware architecture name and this name should
3+
# match to hardware architecture name used in a library name of
4+
# libclang_rt.fuzzer_no_main: aarch64, x86_64, i386.
5+
function(SetHwArchString outvar)
6+
set(${outvar} ${CMAKE_SYSTEM_PROCESSOR} PARENT_SCOPE)
7+
endfunction()
8+
9+
# The function sets the given variable in a parent scope to a
10+
# value with path to libclang_rt.fuzzer_no_main [1] library.
11+
# The function raises a fatal message if C compiler is not Clang.
12+
#
13+
# $ clang-15 -print-file-name=libclang_rt.fuzzer_no_main-x86_64.a
14+
# $ /usr/lib/llvm-15/lib/clang/15.0.7/lib/linux/libclang_rt.fuzzer_no_main-x86_64.a
15+
#
16+
# 1. https://llvm.org/docs/LibFuzzer.html#using-libfuzzer-as-a-library
17+
function(SetLibFuzzerPath outvar)
18+
if (NOT CMAKE_C_COMPILER_ID STREQUAL "Clang")
19+
message(FATAL_ERROR "C compiler is not a Clang")
20+
endif ()
21+
22+
SetHwArchString(HW_ARCH)
23+
if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
24+
set(lib_name "libclang_rt.fuzzer_no_main-${HW_ARCH}.a")
25+
else()
26+
message(FATAL_ERROR "Unsupported system: ${CMAKE_SYSTEM_NAME}")
27+
endif()
28+
29+
execute_process(COMMAND ${CMAKE_C_COMPILER} "-print-file-name=${lib_name}"
30+
RESULT_VARIABLE CMD_ERROR
31+
OUTPUT_VARIABLE CMD_OUTPUT
32+
OUTPUT_STRIP_TRAILING_WHITESPACE
33+
)
34+
if (CMD_ERROR)
35+
message(FATAL_ERROR "${CMD_ERROR}")
36+
endif()
37+
38+
if(NOT EXISTS ${CMD_OUTPUT})
39+
message(FATAL_ERROR "${lib_name} was not found.")
40+
endif()
41+
42+
set(${outvar} ${CMD_OUTPUT} PARENT_SCOPE)
43+
endfunction()
44+
45+
# The function unpack libFuzzer archive located at <LibFuzzerPath>
46+
# to a directory <LibFuzzerDir> and return a path to a directory
47+
# with libFuzzer's object files.
48+
function(SetLibFuzzerObjDir outvar)
49+
set(LibFuzzerDir ${PROJECT_BINARY_DIR}/libFuzzer_unpacked)
50+
file(MAKE_DIRECTORY ${LibFuzzerDir})
51+
SetLibFuzzerPath(LibFuzzerPath)
52+
execute_process(
53+
COMMAND ${CMAKE_AR} x ${LibFuzzerPath} --output ${LibFuzzerDir}
54+
RESULT_VARIABLE CMD_ERROR
55+
OUTPUT_VARIABLE CMD_OUTPUT
56+
WORKING_DIRECTORY ${LibFuzzerDir}
57+
)
58+
if (CMD_ERROR)
59+
message(FATAL_ERROR "${CMD_ERROR}")
60+
endif()
61+
set(${outvar} ${LibFuzzerDir} PARENT_SCOPE)
62+
endfunction()

patches/luajit-v2.1.patch

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,32 @@
1+
diff --git a/src/Makefile b/src/Makefile
2+
index 969bf289..23c4264b 100644
3+
--- a/src/Makefile
4+
+++ b/src/Makefile
5+
@@ -514,6 +514,10 @@ LJLIB_O= lib_base.o lib_math.o lib_bit.o lib_string.o lib_table.o \
6+
lib_buffer.o
7+
LJLIB_C= $(LJLIB_O:.o=.c)
8+
9+
+ifneq ($(LF_PATH),)
10+
+LIBFUZZER_O= $(shell find $(LF_PATH) -maxdepth 1 -name '*.o')
11+
+endif
12+
+
13+
LJCORE_O= lj_assert.o lj_gc.o lj_err.o lj_char.o lj_bc.o lj_obj.o lj_buf.o \
14+
lj_str.o lj_tab.o lj_func.o lj_udata.o lj_meta.o lj_debug.o \
15+
lj_prng.o lj_state.o lj_dispatch.o lj_vmevent.o lj_vmmath.o \
16+
@@ -748,9 +752,9 @@ $(LUAJIT_SO): $(LJVMCORE_O)
17+
$(Q)$(TARGET_LD) $(TARGET_ASHLDFLAGS) -o $@ $(LJVMCORE_DYNO) $(TARGET_ALIBS)
18+
$(Q)$(TARGET_STRIP) $@
19+
20+
-$(LUAJIT_T): $(TARGET_O) $(LUAJIT_O) $(TARGET_DEP)
21+
+$(LUAJIT_T): $(TARGET_O) $(LUAJIT_O) $(TARGET_DEP) $(LIBFUZZER_O)
22+
$(E) "LINK $@"
23+
- $(Q)$(TARGET_LD) $(TARGET_ALDFLAGS) -o $@ $(LUAJIT_O) $(TARGET_O) $(TARGET_ALIBS)
24+
+ $(Q)$(TARGET_LD) $(TARGET_ALDFLAGS) -o $@ $(LUAJIT_O) $(TARGET_O) $(TARGET_ALIBS) $(LIBFUZZER_O)
25+
$(Q)$(TARGET_STRIP) $@
26+
$(E) "OK Successfully built LuaJIT"
27+
128
diff --git a/src/host/buildvm.c b/src/host/buildvm.c
2-
index ec99e501..d23530c4 100644
29+
index 24db75f4..021e7dbe 100644
330
--- a/src/host/buildvm.c
431
+++ b/src/host/buildvm.c
532
@@ -35,6 +35,10 @@
@@ -14,7 +41,7 @@ index ec99e501..d23530c4 100644
1441

1542
/* DynASM glue definitions. */
1643
diff --git a/src/lj_buf.h b/src/lj_buf.h
17-
index 744e5747..ea299472 100644
44+
index 15a04250..ef701256 100644
1845
--- a/src/lj_buf.h
1946
+++ b/src/lj_buf.h
2047
@@ -165,6 +165,13 @@ LJ_FUNC SBuf * LJ_FASTCALL lj_buf_putchar(SBuf *sb, int c);
@@ -32,7 +59,7 @@ index 744e5747..ea299472 100644
3259
{
3360
return (char *)memcpy(p, q, len) + len;
3461
diff --git a/src/lj_carith.c b/src/lj_carith.c
35-
index 9bea0a33..046dea4c 100644
62+
index b09812c6..98128daa 100644
3663
--- a/src/lj_carith.c
3764
+++ b/src/lj_carith.c
3865
@@ -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
4875
{
4976
if (ctype_isnum(ca->ct[0]->info) && ca->ct[0]->size <= 8 &&
5077
diff --git a/src/lj_opt_fold.c b/src/lj_opt_fold.c
51-
index ce78505b..bc9d64f3 100644
78+
index 456c04b2..4edfa742 100644
5279
--- a/src/lj_opt_fold.c
5380
+++ b/src/lj_opt_fold.c
5481
@@ -260,6 +260,11 @@ LJFOLDF(kfold_numcomp)
@@ -64,10 +91,10 @@ index ce78505b..bc9d64f3 100644
6491
{
6592
switch (op) {
6693
diff --git a/src/lj_parse.c b/src/lj_parse.c
67-
index 5a44f8db..bfe044a8 100644
94+
index 832f6bf4..7d0390e4 100644
6895
--- a/src/lj_parse.c
6996
+++ b/src/lj_parse.c
70-
@@ -934,6 +934,11 @@ static void bcemit_binop(FuncState *fs, BinOpr op, ExpDesc *e1, ExpDesc *e2)
97+
@@ -935,6 +935,11 @@ static void bcemit_binop(FuncState *fs, BinOpr op, ExpDesc *e1, ExpDesc *e2)
7198
}
7299

73100
/* Emit unary operator. */
@@ -80,7 +107,7 @@ index 5a44f8db..bfe044a8 100644
80107
{
81108
if (op == BC_NOT) {
82109
diff --git a/src/lj_snap.c b/src/lj_snap.c
83-
index 6fda08ba..c7f51d7d 100644
110+
index d0d28c81..c8d5ffcc 100644
84111
--- a/src/lj_snap.c
85112
+++ b/src/lj_snap.c
86113
@@ -763,6 +763,13 @@ static void snap_restoreval(jit_State *J, GCtrace *T, ExitState *ex,
@@ -98,7 +125,7 @@ index 6fda08ba..c7f51d7d 100644
98125
static void snap_restoredata(jit_State *J, GCtrace *T, ExitState *ex,
99126
SnapNo snapno, BloomFilter rfilt,
100127
diff --git a/src/lj_str.c b/src/lj_str.c
101-
index cfdaec6f..88f9c765 100644
128+
index f34d6d95..806c67db 100644
102129
--- a/src/lj_str.c
103130
+++ b/src/lj_str.c
104131
@@ -13,6 +13,15 @@
@@ -118,7 +145,7 @@ index cfdaec6f..88f9c765 100644
118145

119146
/* Ordered compare of strings. Assumes string data is 4-byte aligned. */
120147
diff --git a/src/lj_strfmt.c b/src/lj_strfmt.c
121-
index 909255db..ef9bd4f9 100644
148+
index 0936298d..32e93c42 100644
122149
--- a/src/lj_strfmt.c
123150
+++ b/src/lj_strfmt.c
124151
@@ -99,6 +99,11 @@ retlit:

patches/puc-rio-lua.patch

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
diff --git a/makefile b/makefile
2-
index 8674519f..17dabfa8 100644
2+
index 8674519f..1773b0eb 100644
33
--- a/makefile
44
+++ b/makefile
55
@@ -39,7 +39,7 @@ CWARNSC= -Wdeclaration-after-statement \
@@ -26,3 +26,17 @@ index 8674519f..17dabfa8 100644
2626
AR= ar rc
2727
RANLIB= ranlib
2828
RM= rm -f
29+
@@ -96,9 +96,12 @@ CORE_O= lapi.o lcode.o lctype.o ldebug.o ldo.o ldump.o lfunc.o lgc.o llex.o \
30+
AUX_O= lauxlib.o
31+
LIB_O= lbaselib.o ldblib.o liolib.o lmathlib.o loslib.o ltablib.o lstrlib.o \
32+
lutf8lib.o loadlib.o lcorolib.o linit.o
33+
+ifneq ($(LF_PATH),)
34+
+LF_O= $(shell find $(LF_PATH) -maxdepth 1 -name '*.o')
35+
+endif
36+
37+
LUA_T= lua
38+
-LUA_O= lua.o
39+
+LUA_O= lua.o $(LF_O)
40+
41+
42+
ALL_T= $(CORE_T) $(LUA_T)

0 commit comments

Comments
 (0)