Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Integration with libFuzzer's `FuzzedDataProvider`.
- Examples with tests.
- Documentation with usecases, API etc.
- Support command-line options.

### Changed

- Disable coverage instrumentation of internal functions (#11).
- Add missed newlines to messages.
- Rename `_VERSION` to a `_LUZER_VERSION`.

### Fixed

- Fix searching Clang RT.
- Stack overflow due to recursive traceback calls.
- Fix a crash due to incorrect `argv` building (#13).
55 changes: 55 additions & 0 deletions cmake/MakeLuaPath.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# make_lua_path provides a convenient way to define LUA_PATH and
# LUA_CPATH variables.
#
# Example usage:
#
# make_lua_path(LUA_PATH
# PATH
# ./?.lua
# ${CMAKE_BINARY_DIR}/?.lua
# ${CMAKE_CURRENT_SOURCE_DIR}/?.lua
# )
#
# This will give you the string:
# "./?.lua;${CMAKE_BINARY_DIR}/?.lua;${CMAKE_CURRENT_SOURCE_DIR}/?.lua;;"
#
# Source: https://github.com/tarantool/luajit/blob/441405c398d625fda304b16bb10f119bfd822696/cmake/MakeLuaPath.cmake

function(make_lua_path path)
set(prefix ARG)
set(noValues)
set(singleValues)
set(multiValues PATHS)

# FIXME: if we update to CMake >= 3.5, can remove this line.
include(CMakeParseArguments)
cmake_parse_arguments(${prefix}
"${noValues}"
"${singleValues}"
"${multiValues}"
${ARGN})

set(_MAKE_LUA_PATH_RESULT "")

foreach(inc ${ARG_PATHS})
# XXX: If one joins two strings with the semicolon, the value
# automatically becomes a list. I found a single working
# solution to make result variable be a string via "escaping"
# the semicolon right in string interpolation.
set(_MAKE_LUA_PATH_RESULT "${_MAKE_LUA_PATH_RESULT}${inc}\;")
endforeach()

if("${_MAKE_LUA_PATH_RESULT}" STREQUAL "")
message(FATAL_ERROR "No paths are given to <make_lua_path> helper.")
endif()

# XXX: This is the sentinel semicolon having special meaning
# for LUA_PATH and LUA_CPATH variables. For more info, see the
# link below:
# https://www.lua.org/manual/5.1/manual.html#pdf-LUA_PATH
set(${path} "${_MAKE_LUA_PATH_RESULT}\;" PARENT_SCOPE)
# XXX: Unset the internal variable to not spoil CMake cache.
# Study the case in CheckIPOSupported.cmake, that affected this
# module: https://gitlab.kitware.com/cmake/cmake/-/commit/4b82977
unset(_MAKE_LUA_PATH_RESULT)
endfunction()
19 changes: 13 additions & 6 deletions luzer/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,17 @@ set(LUZER_SOURCES luzer.c
counters.c
${CMAKE_CURRENT_BINARY_DIR}/version.c)

add_library(${CMAKE_PROJECT_NAME} SHARED ${LUZER_SOURCES})
target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE
add_library(luzer_impl SHARED ${LUZER_SOURCES})
target_include_directories(luzer_impl PRIVATE
${LUA_INCLUDE_DIR}
)
target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE

target_link_libraries(luzer_impl PRIVATE
${LUA_LIBRARIES}
-fsanitize=fuzzer-no-link
${FUZZER_NO_MAIN_LIBRARY}
)
target_compile_options(${CMAKE_PROJECT_NAME} PRIVATE
target_compile_options(luzer_impl PRIVATE
-D_FORTIFY_SOURCE=2
-fpie
-fPIC
Expand All @@ -34,12 +35,13 @@ target_compile_options(${CMAKE_PROJECT_NAME} PRIVATE
-pedantic
-fsanitize=fuzzer-no-link
)
set_target_properties(${CMAKE_PROJECT_NAME} PROPERTIES PREFIX "")
set_target_properties(luzer_impl PROPERTIES PREFIX "")

set(custom_mutator_lib_source ${CMAKE_CURRENT_SOURCE_DIR}/custom_mutator_lib.c)
add_library(custom_mutator SHARED ${custom_mutator_lib_source})
target_include_directories(custom_mutator PRIVATE ${LUA_INCLUDE_DIR})
target_link_libraries(custom_mutator PRIVATE ${LUA_LIBRARIES})
target_link_libraries(custom_mutator PRIVATE luzer_impl)
set_target_properties(custom_mutator PROPERTIES VERSION ${PROJECT_VERSION})
set_target_properties(custom_mutator PROPERTIES SOVERSION 1)

Expand All @@ -48,7 +50,7 @@ if(ENABLE_TESTING)
endif()

install(
TARGETS ${PROJECT_NAME}
TARGETS luzer_impl
LIBRARY
DESTINATION "${CMAKE_LIBDIR}/"
RENAME luzer.so
Expand All @@ -62,3 +64,8 @@ install(
NAMELINK_SKIP
DESTINATION "${CMAKE_LIBDIR}/"
)

install(
FILES ${CMAKE_CURRENT_SOURCE_DIR}/init.lua
DESTINATION "${CMAKE_LIBDIR}/"
)
61 changes: 61 additions & 0 deletions luzer/init.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
local ok, luzer_impl = pcall(require, "luzer_impl")
if not ok then
error(("error on loading luzer_impl: %s"):format(luzer_impl))
end

local function starts_with(str, prefix)
return string.sub(str, 1, string.len(prefix)) == prefix
end

-- The fuzzing test based on LibFuzzer optionally accept a number
-- of flags and zero or more paths to corpus directories as
-- command line arguments:
-- ./fuzzer [-flag1=val1 [-flag2=val2 ...] ] [dir1 [dir2 ...] ]
--
-- 1. https://llvm.org/docs/LibFuzzer.html#options
local function parse_flag(str)
local flag_name, flag_val = string.match(str, "-([%l%p]+)=(%w+)")
if not flag_name or
not flag_val then
error(("bad flag: %s"):format(str))
end
return flag_name, flag_val
end

local function build_flags(arg, func_args)
local flags = {}
for _, arg_str in ipairs(arg) do
local name, value
if starts_with(arg_str, "-") then
name, value = parse_flag(arg_str)
else
name, value = "corpus", arg_str
end
flags[name] = value
end

for flag_name, flag_val in pairs(func_args) do
if not flags[flag_name] then
flags[flag_name] = flag_val
end
end

return flags
end

local function Fuzz(test_one_input, custom_mutator, func_args)
local flags = build_flags(arg, func_args)
luzer_impl.Fuzz(test_one_input, custom_mutator, flags)
end

return {
Fuzz = Fuzz,
FuzzedDataProvider = luzer_impl.FuzzedDataProvider,

_LLVM_VERSION = luzer_impl._LLVM_VERSION,
_LUA_VERSION = luzer_impl._LUA_VERSION,
_LUZER_VERSION = luzer_impl._LUZER_VERSION,

_set_custom_mutator = luzer_impl._set_custom_mutator,
_mutate = luzer_impl._mutate,
}
93 changes: 59 additions & 34 deletions luzer/luzer.c
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ TestOneInput(const uint8_t* data, size_t size) {
*/
lua_sethook(L, debug_hook, LUA_MASKCALL | LUA_MASKLINE, 0);

char *buf = calloc(size + 1, sizeof(char));
char *buf = malloc(size + 1 * sizeof(*buf));
memcpy(buf, data, size);
buf[size] = '\0';
lua_pushlstring(L, buf, size);
Expand Down Expand Up @@ -290,15 +290,13 @@ search_module_path(char *so_path, size_t len) {
*/
NO_SANITIZE static int
load_custom_mutator_lib(void) {
char *so_path = calloc(PATH_MAX, sizeof(char));
char so_path[PATH_MAX];
int rc = search_module_path(so_path, PATH_MAX);
if (rc) {
free(so_path);
DEBUG_PRINT("search_module_path");
return -1;
}
void *custom_mutator_lib = dlopen(so_path, RTLD_LAZY);
free(so_path);
if (!custom_mutator_lib) {
DEBUG_PRINT("dlopen");
return -1;
Expand All @@ -316,49 +314,70 @@ load_custom_mutator_lib(void) {
return 0;
}

/* Find amount of fields in the table on the top of the stack. */
NO_SANITIZE static int
table_nkeys(lua_State *L)
{
int len = 0;
/* Push starting `nil` for iterations. */
lua_pushnil(L);
while (lua_next(L, -2) != 0) {
/*
* Remove `value` from the stack. Keeps `key` for
* the next iteration.
*/
lua_pop(L, 1);
len++;
}
return len;
}

NO_SANITIZE static void
free_argv(int argc, char **argv)
{
/* Free allocated argv strings and the buffer. */
for (int i = 1; i < argc; i++)
free(argv[i]);
free(argv);
}

NO_SANITIZE static int
luaL_fuzz(lua_State *L)
{
if (lua_istable(L, -1) == 0) {
luaL_error(L, "opts is not a table");
}
lua_pushnil(L);

/* Processing a table with options. */
int argc = 0;
char **argv = malloc(1 * sizeof(char*));
/* 0 element -- test name. Last -- ending NULL. */
int argc = table_nkeys(L) + 1;
char **argv = malloc((argc + 1) * sizeof(*argv));
if (!argv)
luaL_error(L, "not enough memory");

argv[0] = "<test name>";
const char *corpus_path = NULL;

/* First key to start iteration. */
lua_pushnil(L);
int n_arg = 1;
while (lua_next(L, -2) != 0) {
char **argvp = realloc(argv, sizeof(char*) * (argc + 1));
if (argvp == NULL) {
free(argv);
luaL_error(L, "not enough memory");
}
const char *key = lua_tostring(L, -2);
const char *value = lua_tostring(L, -1);
if (strcmp(key, "corpus") != 0) {
size_t arg_len = strlen(key) + strlen(value) + 3;
char *arg = calloc(arg_len, sizeof(char));
if (!arg)
luaL_error(L, "not enough memory");
snprintf(arg, arg_len, "-%s=%s", key, value);
argvp[argc] = arg;
argc++;
} else {
if (strcmp(key, "corpus") == 0) {
corpus_path = strdup(value);
lua_pop(L, 1);
continue;
}
size_t arg_len = strlen(key) + strlen(value) + 3;
char *arg = malloc(arg_len * sizeof(*arg));
if (!arg)
luaL_error(L, "not enough memory");
snprintf(arg, arg_len, "-%s=%s", key, value);
argv[n_arg++] = arg;
lua_pop(L, 1);
argv = argvp;
}

if (corpus_path) {
argv[argc] = (char*)corpus_path;
argc++;
}
if (argc == 0) {
argv[argc] = "";
argc++;
argv[argc-1] = (char*)corpus_path;
}
argv[argc] = NULL;
lua_pop(L, 1);
Expand All @@ -373,15 +392,18 @@ luaL_fuzz(lua_State *L)

/* Processing a function with custom mutator. */
if (!lua_isnil(L, -1) && (lua_isfunction(L, -1) == 1)) {
if (load_custom_mutator_lib())
if (load_custom_mutator_lib()) {
free_argv(argc, argv);
luaL_error(L, "function LLVMFuzzerCustomMutator is not available");
}
luaL_set_custom_mutator(L);
} else {
lua_pop(L, 1);
}

/* Processing a function LLVMFuzzerTestOneInput. */
if (lua_isfunction(L, -1) != 1) {
free_argv(argc, argv);
luaL_error(L, "test_one_input is not a Lua function");
}
lua_setglobal(L, TEST_ONE_INPUT_FUNC);
Expand All @@ -395,12 +417,15 @@ luaL_fuzz(lua_State *L)

lua_getglobal(L, TEST_ONE_INPUT_FUNC);
if (lua_isfunction(L, -1) != 1) {
free_argv(argc, argv);
luaL_error(L, "test_one_input is not defined");
}
lua_pop(L, -1);

set_global_lua_state(L);
int rc = LLVMFuzzerRunDriver(&argc, &argv, &TestOneInput);

free_argv(argc, argv);
luaL_cleanup(L);

lua_pushnumber(L, rc);
Expand All @@ -416,16 +441,16 @@ static const struct luaL_Reg Module[] = {
{ NULL, NULL }
};

int luaopen_luzer(lua_State *L)
int luaopen_luzer_impl(lua_State *L)
{
init();

#if LUA_VERSION_NUM == 501
luaL_register(L, "luzer", Module);
luaL_register(L, "luzer_impl", Module);
#else
luaL_newlib(L, Module);
#endif
lua_pushliteral(L, "_VERSION");
lua_pushliteral(L, "_LUZER_VERSION");
lua_pushstring(L, luzer_version_string());
lua_rawset(L, -3);

Expand Down
Loading