Skip to content
Open
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- An error when Clang couldn't find the library.
- A possible corruption on copying library (#91).
- A crash due to incorrect use of signal handler (#40).
- Double printing metrics when using options -fork/-jobs (#89).
3 changes: 3 additions & 0 deletions luzer/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ local function Fuzz(test_one_input, custom_mutator, func_args)
error("args is not a table")
end
local flags = build_flags(arg, luzer_args)
if flags.fork or flags.jobs then
luzer_impl._set_fork_mode()
end
local test_path = arg[0]
local lua_bin = progname(arg)
local test_cmd = ("%s %s"):format(lua_bin, test_path)
Expand Down
86 changes: 85 additions & 1 deletion luzer/luzer.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#ifdef LUA_HAS_JIT
#include "luajit.h"
#endif /* LUA_HAS_JIT */
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
Expand Down Expand Up @@ -50,13 +51,43 @@ static int jit_status = 0;
#endif /* LUA_HAS_JIT && LUAJIT_FRIENDLY_MODE */

int internal_hook_disabled = 0;
static bool fork_mode = false;
static bool fork_mode_main = false;
static bool metrics_printed = false;

#define LUA_SETHOOK(lua_state, hook, mask, count) \
do { \
if (!internal_hook_disabled) \
lua_sethook((lua_state), (hook), (mask), (count)); \
} while(0)

/**
* Checks whether the current call is from the original (parent)
* process or from a forked child process. On first call, stores
* the current process PID. On subsequent calls, compares the
* stored PID with the current PID to detect fork().
*
* @return 1 - original (parent) process
* 0 - forked child process
*/
NO_SANITIZE static int
check_parent_or_child(void)
{
static pid_t saved_pid = -1;
static bool first_call = true;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like this flag is redundant. Let's just use static saved_pid which is defined on library's initialization. Then we don't need this first_call plie.


if (first_call) {
saved_pid = getpid();
first_call = false;
return 1;
}

if (saved_pid == getpid())
return 1;
else
return 0;
}

NO_SANITIZE static void
set_global_lua_state(lua_State *L)
{
Expand Down Expand Up @@ -265,6 +296,14 @@ luaL_set_custom_mutator(lua_State *L)
return 0;
}

NO_SANITIZE static int
luaL_set_fork_mode(lua_State *L)
{
fork_mode = true;
fork_mode_main = true;
return 0;
}

NO_SANITIZE static int
luaL_test_one_input(lua_State *L)
{
Expand All @@ -287,7 +326,20 @@ luaL_test_one_input(lua_State *L)
__attribute__((destructor)) static void
teardown(void)
{
metrics_print();
/*
* In single-process mode, print metrics from the destructor.
* In fork/jobs mode, the main process prints metrics once:
* - from teardown() for -fork (exit() is called inside libFuzzer)
* - after LLVMFuzzerRunDriver() for -jobs (returns normally)
* Child processes (fork/exec) always skip printing.
*/
if (fork_mode_main) {
if (!metrics_printed)
metrics_print();
} else if (!fork_mode) {
if (check_parent_or_child() == 1)
metrics_print();
}
}

NO_SANITIZE int
Expand Down Expand Up @@ -438,6 +490,17 @@ free_argv(int argc, char **argv)
NO_SANITIZE static int
luaL_fuzz(lua_State *L)
{
/* Remember PID. */
check_parent_or_child();

/*
* If LUZER_IN_FORK_MODE is set, this process is a child
* spawned by libFuzzer's -jobs mode. Mark fork_mode without
* setting fork_mode_main so children skip metrics printing.
*/
if (getenv("LUZER_IN_FORK_MODE"))
fork_mode = true;

const char *str = luaL_checkstring(L, -1);
lua_pop(L, 1);
char *argv_0 = strdup(str);
Expand Down Expand Up @@ -534,8 +597,28 @@ luaL_fuzz(lua_State *L)
jit_status = luajit_has_enabled_jit(L);
#endif
set_global_lua_state(L);

/*
* Set an environment variable so child processes spawned
* via -jobs (which use exec, not fork) know they are
* running in fork/jobs mode and should skip printing.
*/
if (fork_mode)
setenv("LUZER_IN_FORK_MODE", "1", 1);

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

/*
* In -jobs mode, the main process returns here after all
* child jobs have completed. Print metrics once.
* In -fork mode, libFuzzer calls exit() inside FuzzWithFork,
* so we never reach this point; printing happens in teardown().
*/
if (fork_mode_main) {
metrics_print();
metrics_printed = true;
}

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

Expand All @@ -548,6 +631,7 @@ static const struct luaL_Reg Module[] = {
{ "Fuzz", luaL_fuzz },
{ "FuzzedDataProvider", luaL_fuzzed_data_provider },
{ "_set_custom_mutator", luaL_set_custom_mutator },
{ "_set_fork_mode", luaL_set_fork_mode },
{ "_mutate", luaL_mutate },
{ NULL, NULL }
};
Expand Down
28 changes: 28 additions & 0 deletions luzer/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,34 @@ set_tests_properties(luzer_options_jobs_test PROPERTIES
PASS_REGULAR_EXPRESSION "Job 4 exited with exit code 0"
)

set(LUAJIT_METRICS_MESSAGE "Total number of recorded traces")
if (NOT LUAJIT_FRIENDLY_MODE)
set(LUAJIT_METRICS_MESSAGE "LuaJIT metrics disabled.")
endif()
add_test(
NAME luzer_options_jobs_metrics_test
COMMAND ${LUA_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/test_luajit_friendly.lua
-runs=100 -jobs=2
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
)
set_tests_properties(luzer_options_jobs_metrics_test PROPERTIES
ENVIRONMENT "DISABLE_LUAJIT_METRICS=1;LUA_CPATH=${LUA_CPATH};LUA_PATH=${LUA_PATH}"
FAIL_REGULAR_EXPRESSION "(${LUAJIT_METRICS_MESSAGE})(.*\\n.*\\1)+"
)

add_test(
NAME luzer_options_fork_metrics_test
COMMAND ${LUA_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/test_luajit_friendly.lua
-runs=100 -fork=1
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
)
set_tests_properties(luzer_options_fork_metrics_test PROPERTIES
ENVIRONMENT "DISABLE_LUAJIT_METRICS=1;LUA_CPATH=${LUA_CPATH};LUA_PATH=${LUA_PATH}"
FAIL_REGULAR_EXPRESSION "(${LUAJIT_METRICS_MESSAGE})(.*\\n.*\\1)+"
)

add_test(
NAME luzer_options_help_test
COMMAND ${LUA_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test_options_2.lua
Expand Down
6 changes: 2 additions & 4 deletions luzer/tests/test_luajit_friendly.lua
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
local luzer = require("luzer")

local chunk = [[
local function fib(n)
if n <= 1 then
return n
end
return fib(n - 1) + fib(n - 2)
end
]]

local function TestOneInput(buf)
local fdp = luzer.FuzzedDataProvider(buf)
local numbers = fdp:consume_numbers(0, 2*10^6, 10)
for _, n in ipairs(numbers) do
if n == 100500 then
assert("Bingo!")
error("Bingo!")
end
end

Expand All @@ -24,7 +22,7 @@ local function TestOneInput(buf)
end

-- Needed for testing LuaJIT metric with parsed functions.
load(chunk)
fib(5)
end

local args = {
Expand Down
Loading