commit - e52d181485a11b13f0951adb604e18be363ab8c9
commit + 847dd20811eb947fa67fb6e3c076f815f80d1a51
blob - b9e81c8dc7ffbf08ff895aea08a0228091f1acde
blob + 1c304d5b09927c1b1034ca87595766651c44ab11
--- CHANGELOG.md
+++ CHANGELOG.md
- Integration with libFuzzer's `LLVMFuzzerTestOneInput()`.
- Integration with libFuzzer's `LLVMFuzzerCustomMutator()`.
- Integration with libFuzzer's `FuzzedDataProvider`.
+- libFuzzer custom mutator for Lua.
blob - /dev/null
blob + 08b64c368dcf8ed5dc24d8379bf0691a4ac8159c (mode 644)
--- /dev/null
+++ mutator/CMakeLists.txt
+if(ENABLE_TESTING)
+ add_subdirectory(tests)
+endif()
+
+install(
+ FILES
+ ${CMAKE_CURRENT_SOURCE_DIR}/mutator.c
+ ${CMAKE_CURRENT_SOURCE_DIR}/crossover.c
+ DESTINATION ${CMAKE_LUADIR}/mutator/
+)
blob - /dev/null
blob + 13d11e8d1aab431f7b02fa1956204953ba3d46f1 (mode 644)
--- /dev/null
+++ mutator/crossover.c
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright 2022-2023, Sergey Bronnikov
+ */
+
+#include <lua.h>
+#include <lauxlib.h>
+#include <lualib.h>
+#include <assert.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+static const char *script_default = "./mutator.lua";
+
+static size_t
+luaL_custom_crossover(lua_State* L, const char *path, const char *func_name,
+ const uint8_t *data1, size_t size1,
+ const uint8_t *data2, size_t size2,
+ size_t max_out_size, unsigned int seed)
+{
+ luaL_dofile(L, path);
+ lua_getglobal(L, func_name);
+ if (!lua_isfunction(L, -1)) {
+ luaL_error(L, "'%s' is not a function", func_name);
+ }
+ lua_pushlstring(L, (const char*)data1, size1);
+ lua_pushlstring(L, (const char*)data2, size2);
+ lua_pushinteger(L, max_out_size);
+ lua_pushinteger(L, seed);
+ const int num_args = 4;
+ const int num_return_values = 2;
+ lua_pcall(L, num_args, num_return_values, 0);
+
+ if (!lua_isnumber(L, -1)) {
+ luaL_error(L, "'%s' must return an integer value", func_name);
+ }
+ size_t ret_size = lua_tointeger(L, -1);
+ lua_pop(L, 1);
+
+ if (!lua_isstring(L, -1)) {
+ luaL_error(L, "'%s' must return a string value", func_name);
+ }
+ data1 = (uint8_t *)lua_tostring(L, -1); /* FIXME */
+ lua_pop(L, 1);
+
+ return ret_size;
+}
+
+size_t LLVMFuzzerCustomCrossOver(const uint8_t *Data1, size_t Size1,
+ const uint8_t *Data2, size_t Size2,
+ uint8_t *Out, size_t MaxOutSize,
+ unsigned int Seed)
+{
+ const char *script_env = "LIBFUZZER_LUA_SCRIPT";
+ const char *script_func = "LLVMFuzzerCustomCrossOver";
+ const char *script_path = getenv(script_env) ? : script_default;
+
+ if (access(script_path, F_OK) != 0) {
+ fprintf(stderr, "Script (%s) is not accessible.\n", script_path);
+ _exit(1);
+ }
+
+ lua_State* L = luaL_newstate();
+ if (!L) {
+ fprintf(stderr, "Unable to create Lua state.\n");
+ abort();
+ }
+ luaL_openlibs(L);
+ size_t size = luaL_custom_crossover(L, script_path, script_func,
+ Data1, Size1, Data2, Size2, MaxOutSize, Seed);
+ lua_close(L);
+
+ return size;
+}
blob - /dev/null
blob + 3dccc65c42f2839278079e4b7542d39a01d98704 (mode 644)
--- /dev/null
+++ mutator/mutator.c
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright 2022-2023, Sergey Bronnikov
+ */
+
+#include <lua.h>
+#include <lauxlib.h>
+#include <lualib.h>
+#include <assert.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+static const char *script_default = "./mutator.lua";
+
+static size_t
+luaL_custom_mutator(lua_State* L, const char *path, const char *func_name,
+ uint8_t *data, size_t size,
+ size_t max_size, unsigned int seed)
+{
+ luaL_dofile(L, path);
+ lua_getglobal(L, func_name);
+ if (!lua_isfunction(L, -1))
+ luaL_error(L, "'%s' is not a function", func_name);
+ lua_pushlstring(L, (const char*)data, size);
+ lua_pushinteger(L, max_size);
+ lua_pushinteger(L, seed);
+ /* do the call (3 arguments, 2 results) */
+ if (lua_pcall(L, 3, 2, 0) != 0)
+ luaL_error(L, "error running function '%s': %s",
+ func_name, lua_tostring(L, -1));
+
+ if (!lua_isnumber(L, -1)) {
+ luaL_error(L, "'%s' must return a number", func_name);
+ }
+ size_t ret_size = lua_tonumber(L, -1) - 1;
+ lua_pop(L, 1);
+
+ if (!lua_isstring(L, -1)) {
+ luaL_error(L, "'%s' must return a string", func_name);
+ }
+ const char *res = lua_tolstring(L, -1, &ret_size);
+ lua_pop(L, 1);
+
+ *data = *res;
+
+ return ret_size;
+}
+
+size_t
+LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size,
+ size_t MaxSize, unsigned int Seed)
+{
+ const char *script_env = "LIBFUZZER_LUA_SCRIPT";
+ const char *script_func = "LLVMFuzzerCustomMutator";
+ const char *script_path = getenv(script_env) ? : script_default;
+
+ if (access(script_path, F_OK) != 0) {
+ fprintf(stderr, "Script (%s) is not accessible.\n", script_path);
+ _exit(1);
+ }
+
+ lua_State* L = luaL_newstate();
+ if (!L) {
+ fprintf(stderr, "Unable to create Lua state.\n");
+ abort();
+ }
+ luaL_openlibs(L);
+ size_t ret_size = luaL_custom_mutator(L, script_path, script_func,
+ Data, Size, MaxSize, Seed);
+ lua_close(L);
+
+ return ret_size;
+}
blob - /dev/null
blob + 3477cff2d884071d10e8811f359e9bc18e4e8b88 (mode 644)
--- /dev/null
+++ mutator/tests/CMakeLists.txt
+add_executable(mutator_basic_test
+ ${PROJECT_SOURCE_DIR}/mutator/mutator.c
+ ${CMAKE_CURRENT_SOURCE_DIR}/mutator_basic_test.c)
+target_include_directories(mutator_basic_test PRIVATE ${LUA_INCLUDE_DIR})
+target_link_libraries(mutator_basic_test PRIVATE ${LUA_LIBRARIES})
+target_compile_options(mutator_basic_test PUBLIC -Wall -Wextra -Wno-unused-parameter)
+
+add_executable(mutator_seed_test
+ ${PROJECT_SOURCE_DIR}/mutator/mutator.c
+ ${CMAKE_CURRENT_SOURCE_DIR}/mutator_seed_test.c)
+target_include_directories(mutator_seed_test PRIVATE ${LUA_INCLUDE_DIR})
+target_link_libraries(mutator_seed_test PRIVATE ${LUA_LIBRARIES})
+target_compile_options(mutator_seed_test PUBLIC -Wall -Wextra -Wno-unused-parameter)
+
+add_executable(mutator_e2e_test
+ ${PROJECT_SOURCE_DIR}/mutator/mutator.c
+ ${CMAKE_CURRENT_SOURCE_DIR}/mutator_e2e_test.c)
+target_include_directories(mutator_e2e_test PRIVATE ${LUA_INCLUDE_DIR})
+target_link_libraries(mutator_e2e_test PRIVATE ${LUA_LIBRARIES} -fsanitize=address,fuzzer)
+target_compile_options(mutator_e2e_test PUBLIC -Wall -Wextra -Wno-unused-parameter)
+
+add_executable(crossover_basic_test
+ ${PROJECT_SOURCE_DIR}/mutator/crossover.c
+ ${CMAKE_CURRENT_SOURCE_DIR}/crossover_basic_test.c)
+target_include_directories(crossover_basic_test PRIVATE ${LUA_INCLUDE_DIR})
+target_link_libraries(crossover_basic_test PRIVATE ${LUA_LIBRARIES})
+target_compile_options(crossover_basic_test PUBLIC -Wall -Wextra -Wno-unused-parameter)
+
+add_executable(crossover_seed_test
+ ${PROJECT_SOURCE_DIR}/mutator/crossover.c
+ ${CMAKE_CURRENT_SOURCE_DIR}/crossover_seed_test.c)
+target_include_directories(crossover_seed_test PRIVATE ${LUA_INCLUDE_DIR})
+target_link_libraries(crossover_seed_test PRIVATE ${LUA_LIBRARIES})
+target_compile_options(crossover_seed_test PUBLIC -Wall -Wextra -Wno-unused-parameter)
+
+add_executable(crossover_e2e_test
+ ${PROJECT_SOURCE_DIR}/mutator/crossover.c
+ ${CMAKE_CURRENT_SOURCE_DIR}/crossover_e2e_test.c)
+target_include_directories(crossover_e2e_test PRIVATE ${LUA_INCLUDE_DIR})
+target_link_libraries(crossover_e2e_test PRIVATE ${LUA_LIBRARIES} -fsanitize=address,fuzzer)
+target_compile_options(crossover_e2e_test PUBLIC -Wall -Wextra -Wno-unused-parameter)
+
+add_test(
+ NAME mutator_basic_test
+ COMMAND ${CMAKE_CURRENT_BINARY_DIR}/mutator_basic_test
+ WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+)
+set_tests_properties(mutator_basic_test PROPERTIES
+ ENVIRONMENT "LIBFUZZER_LUA_SCRIPT=${CMAKE_CURRENT_SOURCE_DIR}/script_basic.lua"
+)
+
+add_test(
+ NAME mutator_seed_test
+ COMMAND ${CMAKE_CURRENT_BINARY_DIR}/mutator_seed_test
+ WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+)
+set_tests_properties(mutator_seed_test PROPERTIES
+ ENVIRONMENT "LIBFUZZER_LUA_SCRIPT=${CMAKE_CURRENT_SOURCE_DIR}/script_seed.lua"
+)
+
+add_test(
+ NAME mutator_e2e_test
+ COMMAND ${CMAKE_CURRENT_BINARY_DIR}/mutator_e2e_test
+ WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+)
+set_tests_properties(mutator_e2e_test PROPERTIES
+ ENVIRONMENT "LIBFUZZER_LUA_SCRIPT=${CMAKE_CURRENT_SOURCE_DIR}/script_e2e.lua"
+ PASS_REGULAR_EXPRESSION "BINGO: Found the target, exiting."
+ DISABLED True
+)
+
+add_test(
+ NAME mutator_no_script_test
+ COMMAND ${CMAKE_CURRENT_BINARY_DIR}/mutator_basic_test
+ WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+)
+set_tests_properties(mutator_no_script_test PROPERTIES
+ ENVIRONMENT "LIBFUZZER_LUA_SCRIPT=unknown"
+ PASS_REGULAR_EXPRESSION "is not accessible"
+)
+
+add_test(
+ NAME crossover_basic_test
+ COMMAND ${CMAKE_CURRENT_BINARY_DIR}/crossover_basic_test
+ WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+)
+set_tests_properties(crossover_basic_test PROPERTIES
+ ENVIRONMENT "LIBFUZZER_LUA_SCRIPT=${CMAKE_CURRENT_SOURCE_DIR}/script_basic.lua"
+)
+
+add_test(
+ NAME crossover_seed_test
+ COMMAND ${CMAKE_CURRENT_BINARY_DIR}/crossover_seed_test
+ WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+)
+set_tests_properties(crossover_seed_test PROPERTIES
+ ENVIRONMENT "LIBFUZZER_LUA_SCRIPT=${CMAKE_CURRENT_SOURCE_DIR}/script_seed.lua"
+)
+
+add_test(
+ NAME crossover_e2e_test
+ COMMAND ${CMAKE_CURRENT_BINARY_DIR}/crossover_e2e_test
+ WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+)
+set_tests_properties(crossover_e2e_test PROPERTIES
+ ENVIRONMENT "LIBFUZZER_LUA_SCRIPT=${CMAKE_CURRENT_SOURCE_DIR}/script_e2e.lua"
+ PASS_REGULAR_EXPRESSION "BINGO: Found the target, exiting."
+ DISABLED True
+)
+
+add_test(
+ NAME crossover_no_script_test
+ COMMAND ${CMAKE_CURRENT_BINARY_DIR}/crossover_basic_test
+ WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+)
+set_tests_properties(crossover_no_script_test PROPERTIES
+ ENVIRONMENT "LIBFUZZER_LUA_SCRIPT=unknown"
+ PASS_REGULAR_EXPRESSION "is not accessible"
+)
blob - /dev/null
blob + aa0cc6ce25dcaa6bc4869da6e2dae1f6a7bf8d85 (mode 644)
--- /dev/null
+++ mutator/tests/crossover_basic_test.c
+#include <assert.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#define COUNT_OF(x) ((sizeof(x)/sizeof(0[x])) / ((size_t)(!(sizeof(x) % sizeof(0[x])))))
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+size_t
+LLVMFuzzerCustomCrossOver(const uint8_t *Data1, size_t Size1,
+ const uint8_t *Data2, size_t Size2,
+ uint8_t *Out, size_t MaxOutSize,
+ unsigned int Seed);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+static void
+test_basic()
+{
+ uint8_t data[] = { 'L', 'U', 'A' };
+ size_t size = COUNT_OF(data);
+ size_t max_size = size + 1;
+ size_t seed = 100;
+ size_t res = LLVMFuzzerCustomCrossOver(data, size, data, size,
+ NULL, max_size, seed);
+ assert(res != 0);
+ /* assert(strcmp((char *)data, "luzer") == 0); */
+}
+
+int main(void)
+{
+ test_basic();
+}
blob - /dev/null
blob + 0c3246676c84583417b2e3939acf59e4fb22bfcd (mode 644)
--- /dev/null
+++ mutator/tests/crossover_e2e_test.c
+#include <assert.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+int
+LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size)
+{
+ assert(Data);
+ char *buf = calloc(Size, sizeof(char *));
+ memcpy(buf, (char *)Data, Size);
+ buf[Size] = '\0';
+ if (strcmp((char *)buf, "A") == 0) {
+ fprintf(stderr, "BINGO: Found the target, exiting.\n");
+ _exit(1);
+ }
+ free(buf);
+ return 0;
+}
blob - /dev/null
blob + 677e86a9cd6f4c6209cb51de1720e0358ccb6f22 (mode 644)
--- /dev/null
+++ mutator/tests/crossover_seed_test.c
+#include <assert.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <time.h>
+
+#define COUNT_OF(x) ((sizeof(x)/sizeof(0[x])) / ((size_t)(!(sizeof(x) % sizeof(0[x])))))
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+size_t
+LLVMFuzzerCustomCrossOver(const uint8_t *Data1, size_t Size1,
+ const uint8_t *Data2, size_t Size2,
+ uint8_t *Out, size_t MaxOutSize,
+ unsigned int Seed);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+static void
+test_seed()
+{
+ time_t t;
+ srand((unsigned) time(&t));
+
+ uint8_t data[] = { 'L', 'U', 'A' };
+ size_t size = COUNT_OF(data);
+ size_t max_size = size;
+ size_t seed = rand();
+ size_t res = LLVMFuzzerCustomCrossOver(data, size, data, size,
+ NULL, max_size, seed);
+ assert(res != 0);
+}
+
+int
+main(void)
+{
+ test_seed();
+}
blob - /dev/null
blob + b94a884c7deacd4128b697d59d97c00990a67b62 (mode 644)
--- /dev/null
+++ mutator/tests/mutator_basic_test.c
+#include <assert.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#define COUNT_OF(x) ((sizeof(x)/sizeof(0[x])) / ((size_t)(!(sizeof(x) % sizeof(0[x])))))
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+size_t
+LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size,
+ size_t MaxSize, unsigned int Seed);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+static void
+test_basic()
+{
+ uint8_t data[] = { 'L', 'U', 'A' };
+ size_t size = COUNT_OF(data);
+ size_t max_size = size + 1;
+ size_t seed = 0;
+ size_t res = LLVMFuzzerCustomMutator(data, size, max_size, seed);
+ assert(res != 0);
+ data[res] = '\0';
+ assert(strcmp((char *)data, "XUA") == 0);
+}
+
+int
+main(void)
+{
+ test_basic();
+}
blob - /dev/null
blob + 0c3246676c84583417b2e3939acf59e4fb22bfcd (mode 644)
--- /dev/null
+++ mutator/tests/mutator_e2e_test.c
+#include <assert.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+int
+LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size)
+{
+ assert(Data);
+ char *buf = calloc(Size, sizeof(char *));
+ memcpy(buf, (char *)Data, Size);
+ buf[Size] = '\0';
+ if (strcmp((char *)buf, "A") == 0) {
+ fprintf(stderr, "BINGO: Found the target, exiting.\n");
+ _exit(1);
+ }
+ free(buf);
+ return 0;
+}
blob - /dev/null
blob + 90dc199490f4912d502f71f75baa1f3b21c88b8b (mode 644)
--- /dev/null
+++ mutator/tests/mutator_seed_test.c
+#include <assert.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <time.h>
+
+#define COUNT_OF(x) ((sizeof(x)/sizeof(0[x])) / ((size_t)(!(sizeof(x) % sizeof(0[x])))))
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+size_t
+LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size,
+ size_t MaxSize, unsigned int Seed);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+static void
+test_seed()
+{
+ time_t t;
+ srand((unsigned) time(&t));
+
+ uint8_t data[] = { 'L', 'U', 'A' };
+ size_t size = COUNT_OF(data);
+ size_t max_size = size;
+ size_t seed = rand();
+ size_t res = LLVMFuzzerCustomMutator(data, size, max_size, seed);
+ assert(res != 0);
+}
+
+int
+main(void)
+{
+ test_seed();
+}
blob - /dev/null
blob + 5534b285ce376d966905cea86d28413a412c74f7 (mode 644)
--- /dev/null
+++ mutator/tests/script_basic.lua
+function LLVMFuzzerCustomMutator(data, max_size, seed) -- luacheck: ignore
+ assert(type(data) == "string")
+ assert(data == "LUA")
+
+ assert(type(max_size) == "number")
+ assert(max_size == #data + 1)
+
+ assert(type(seed) == "number")
+ assert(seed ~= nil)
+ assert(seed == 0)
+
+ local b = {}
+ data:gsub(".", function(c) table.insert(b, c) end)
+ b[1] = "X"
+ local buf = table.concat(b, "")
+
+ return buf, #buf
+end
+
+function LLVMFuzzerCustomCrossOver(data1, data2, max_size, seed) -- luacheck: ignore
+ assert(type(data1) == "string")
+ assert(data1 == "LUA")
+
+ assert(type(data2) == "string")
+ assert(data2 == "LUA")
+
+ assert(type(max_size) == "number")
+
+ assert(type(seed) == "number")
+ assert(seed ~= nil)
+
+ local buf = "luzer"
+
+ return buf, #buf
+end
blob - /dev/null
blob + 21f0aa67c0286ba0fcf1d670bb720c25fd321322 (mode 644)
--- /dev/null
+++ mutator/tests/script_e2e.lua
+function LLVMFuzzerCustomMutator(data, max_size, seed) -- luacheck: ignore
+ return string.rep("A", #data), #data
+end
+
+function LLVMFuzzerCustomCrossOver(data1, data2, max_size, seed) -- luacheck: ignore
+ return "", 0
+end
blob - /dev/null
blob + b80ad15623c729591f0c9a5f34f758a18f870066 (mode 644)
--- /dev/null
+++ mutator/tests/script_seed.lua
+local set_seed = require("math").randomseed
+
+function LLVMFuzzerCustomMutator(data, max_size, seed) -- luacheck: ignore
+ set_seed(seed)
+ return data .. "xxx", 10
+end
+
+function LLVMFuzzerCustomCrossOver(data1, data2, max_size, seed) -- luacheck: ignore
+ set_seed(seed)
+ return data1 .. "xxx", 10
+end