Skip to content

Commit b29dc88

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 b29dc88

File tree

7 files changed

+137
-13
lines changed

7 files changed

+137
-13
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: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,41 @@
1+
diff --git a/src/Makefile b/src/Makefile
2+
index 969bf289..2375daf3 100644
3+
--- a/src/Makefile
4+
+++ b/src/Makefile
5+
@@ -189,7 +189,7 @@ endif
6+
7+
ASOPTIONS= $(CCOPT) $(CCWARN) $(XCFLAGS) $(CFLAGS)
8+
CCOPTIONS= $(CCDEBUG) $(ASOPTIONS)
9+
-LDOPTIONS= $(CCDEBUG) $(LDFLAGS)
10+
+LDOPTIONS= $(CCDEBUG) $(LDFLAGS) -Wl,--whole-archive $(LIBFUZZER) -Wl,--no-whole-archive
11+
12+
HOST_CC= $(CC)
13+
HOST_RM?= rm -f
14+
@@ -514,6 +514,10 @@ LJLIB_O= lib_base.o lib_math.o lib_bit.o lib_string.o lib_table.o \
15+
lib_buffer.o
16+
LJLIB_C= $(LJLIB_O:.o=.c)
17+
18+
+ifneq ($(LF_PATH),)
19+
+LIBFUZZER_O= $(shell find $(LF_PATH) -maxdepth 1 -name '*.o')
20+
+endif
21+
+
22+
LJCORE_O= lj_assert.o lj_gc.o lj_err.o lj_char.o lj_bc.o lj_obj.o lj_buf.o \
23+
lj_str.o lj_tab.o lj_func.o lj_udata.o lj_meta.o lj_debug.o \
24+
lj_prng.o lj_state.o lj_dispatch.o lj_vmevent.o lj_vmmath.o \
25+
@@ -748,9 +752,9 @@ $(LUAJIT_SO): $(LJVMCORE_O)
26+
$(Q)$(TARGET_LD) $(TARGET_ASHLDFLAGS) -o $@ $(LJVMCORE_DYNO) $(TARGET_ALIBS)
27+
$(Q)$(TARGET_STRIP) $@
28+
29+
-$(LUAJIT_T): $(TARGET_O) $(LUAJIT_O) $(TARGET_DEP)
30+
+$(LUAJIT_T): $(TARGET_O) $(LUAJIT_O) $(TARGET_DEP) $(LIBFUZZER_O)
31+
$(E) "LINK $@"
32+
- $(Q)$(TARGET_LD) $(TARGET_ALDFLAGS) -o $@ $(LUAJIT_O) $(TARGET_O) $(TARGET_ALIBS)
33+
+ $(Q)$(TARGET_LD) $(TARGET_ALDFLAGS) -o $@ $(LUAJIT_O) $(TARGET_O) $(TARGET_ALIBS) $(LIBFUZZER_O)
34+
$(Q)$(TARGET_STRIP) $@
35+
$(E) "OK Successfully built LuaJIT"
36+
137
diff --git a/src/host/buildvm.c b/src/host/buildvm.c
2-
index ec99e501..d23530c4 100644
38+
index 24db75f4..021e7dbe 100644
339
--- a/src/host/buildvm.c
440
+++ b/src/host/buildvm.c
541
@@ -35,6 +35,10 @@
@@ -14,7 +50,7 @@ index ec99e501..d23530c4 100644
1450

1551
/* DynASM glue definitions. */
1652
diff --git a/src/lj_buf.h b/src/lj_buf.h
17-
index 744e5747..ea299472 100644
53+
index 15a04250..ef701256 100644
1854
--- a/src/lj_buf.h
1955
+++ b/src/lj_buf.h
2056
@@ -165,6 +165,13 @@ LJ_FUNC SBuf * LJ_FASTCALL lj_buf_putchar(SBuf *sb, int c);
@@ -32,7 +68,7 @@ index 744e5747..ea299472 100644
3268
{
3369
return (char *)memcpy(p, q, len) + len;
3470
diff --git a/src/lj_carith.c b/src/lj_carith.c
35-
index 9bea0a33..046dea4c 100644
71+
index b09812c6..98128daa 100644
3672
--- a/src/lj_carith.c
3773
+++ b/src/lj_carith.c
3874
@@ -159,6 +159,11 @@ static int carith_ptr(lua_State *L, CTState *cts, CDArith *ca, MMS mm)
@@ -48,7 +84,7 @@ index 9bea0a33..046dea4c 100644
4884
{
4985
if (ctype_isnum(ca->ct[0]->info) && ca->ct[0]->size <= 8 &&
5086
diff --git a/src/lj_opt_fold.c b/src/lj_opt_fold.c
51-
index ce78505b..bc9d64f3 100644
87+
index 456c04b2..4edfa742 100644
5288
--- a/src/lj_opt_fold.c
5389
+++ b/src/lj_opt_fold.c
5490
@@ -260,6 +260,11 @@ LJFOLDF(kfold_numcomp)
@@ -64,10 +100,10 @@ index ce78505b..bc9d64f3 100644
64100
{
65101
switch (op) {
66102
diff --git a/src/lj_parse.c b/src/lj_parse.c
67-
index 5a44f8db..bfe044a8 100644
103+
index 832f6bf4..7d0390e4 100644
68104
--- a/src/lj_parse.c
69105
+++ b/src/lj_parse.c
70-
@@ -934,6 +934,11 @@ static void bcemit_binop(FuncState *fs, BinOpr op, ExpDesc *e1, ExpDesc *e2)
106+
@@ -935,6 +935,11 @@ static void bcemit_binop(FuncState *fs, BinOpr op, ExpDesc *e1, ExpDesc *e2)
71107
}
72108

73109
/* Emit unary operator. */
@@ -80,7 +116,7 @@ index 5a44f8db..bfe044a8 100644
80116
{
81117
if (op == BC_NOT) {
82118
diff --git a/src/lj_snap.c b/src/lj_snap.c
83-
index 6fda08ba..c7f51d7d 100644
119+
index d0d28c81..c8d5ffcc 100644
84120
--- a/src/lj_snap.c
85121
+++ b/src/lj_snap.c
86122
@@ -763,6 +763,13 @@ static void snap_restoreval(jit_State *J, GCtrace *T, ExitState *ex,
@@ -98,7 +134,7 @@ index 6fda08ba..c7f51d7d 100644
98134
static void snap_restoredata(jit_State *J, GCtrace *T, ExitState *ex,
99135
SnapNo snapno, BloomFilter rfilt,
100136
diff --git a/src/lj_str.c b/src/lj_str.c
101-
index cfdaec6f..88f9c765 100644
137+
index f34d6d95..806c67db 100644
102138
--- a/src/lj_str.c
103139
+++ b/src/lj_str.c
104140
@@ -13,6 +13,15 @@
@@ -118,7 +154,7 @@ index cfdaec6f..88f9c765 100644
118154

119155
/* Ordered compare of strings. Assumes string data is 4-byte aligned. */
120156
diff --git a/src/lj_strfmt.c b/src/lj_strfmt.c
121-
index 909255db..ef9bd4f9 100644
157+
index 0936298d..32e93c42 100644
122158
--- a/src/lj_strfmt.c
123159
+++ b/src/lj_strfmt.c
124160
@@ -99,6 +99,11 @@ retlit:

patches/puc-rio-lua.patch

Lines changed: 19 additions & 3 deletions
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..e205ffa2 100644
33
--- a/makefile
44
+++ b/makefile
55
@@ -39,7 +39,7 @@ CWARNSC= -Wdeclaration-after-statement \
@@ -11,11 +11,13 @@ index 8674519f..17dabfa8 100644
1111

1212
# Some useful compiler options for internal tests:
1313
# -DLUAI_ASSERT turns on all assertions inside Lua.
14-
@@ -73,11 +73,11 @@ LOCAL = $(TESTS) $(CWARNS)
14+
@@ -72,12 +72,12 @@ LOCAL = $(TESTS) $(CWARNS)
15+
# For C89, "-std=c89 -DLUA_USE_C89"
1516
# Note that Linux/Posix options are not compatible with C89
1617
MYCFLAGS= $(LOCAL) -std=c99 -DLUA_USE_LINUX
17-
MYLDFLAGS= -Wl,-E
18+
-MYLDFLAGS= -Wl,-E
1819
-MYLIBS= -ldl
20+
+MYLDFLAGS= -Wl,-E -Wl,--whole-archive -Wl,--no-whole-archive
1921
+MYLIBS= -ldl $(LDFLAGS)
2022

2123

@@ -26,3 +28,17 @@ index 8674519f..17dabfa8 100644
2628
AR= ar rc
2729
RANLIB= ranlib
2830
RM= rm -f
31+
@@ -96,9 +96,12 @@ CORE_O= lapi.o lcode.o lctype.o ldebug.o ldo.o ldump.o lfunc.o lgc.o llex.o \
32+
AUX_O= lauxlib.o
33+
LIB_O= lbaselib.o ldblib.o liolib.o lmathlib.o loslib.o ltablib.o lstrlib.o \
34+
lutf8lib.o loadlib.o lcorolib.o linit.o
35+
+ifneq ($(LF_PATH),)
36+
+LF_O= $(shell find $(LF_PATH) -maxdepth 1 -name '*.o')
37+
+endif
38+
39+
LUA_T= lua
40+
-LUA_O= lua.o
41+
+LUA_O= lua.o $(LF_O)
42+
43+
44+
ALL_T= $(CORE_T) $(LUA_T)

0 commit comments

Comments
 (0)