commit - 91af5ba5deda63dfb9987465ef2d2522fd2cacc8
commit + 45231c6008e40c95016e9f7f8abef6b45b006b1f
blob - cd2d8a469ba69377072affd63bf2521f9d02713d
blob + c40732f7974de9c042f41a65346e82dc73766783
--- CHANGELOG.md
+++ CHANGELOG.md
- Integration with libFuzzer's `LLVMFuzzerTestOneInput()`.
- Integration with libFuzzer's `LLVMFuzzerCustomMutator()`.
- Integration with libFuzzer's `FuzzedDataProvider`.
-- libFuzzer custom mutator for Lua.
- Examples with tests.
- Documentation with usecases, API etc.
blob - bb2884fff6f9a12fb76c1e1a9cada7fed3d7714f
blob + da1edbdfd954ac1e81a5bc16f705f967cc0ba8bb
--- CMakeLists.txt
+++ CMakeLists.txt
enable_testing()
endif()
-add_subdirectory(mutator)
add_subdirectory(luzer)
## Install ####################################################################
blob - dfc6a046bddd1ac47adcd3141dbd85d6963419e5
blob + cf5b696695274446967850402c83d7489de5d303
--- docs/api.md
+++ docs/api.md
Learn more about grammar-based fuzzing in the
[documentation](grammar_based_fuzzing.md).
-### Custom mutator
-
-- `LLVMFuzzerCustomMutator(data, max_size, seed)` - optional user-provided
- custom mutator. Mutates raw data in [`data`, `data` + size of `data`) inplace.
- Returns the new size, which is not greater than `max_size`. Given the same
- `seed` produces the same mutation.
-- `LLVMFuzzerCustomCrossOver(data1, data2, max_size, seed)` - optional
- user-provided custom cross-over function. Combines pieces of `data1` & `data2`
- together into `out`. Returns the new size, which is not greater than `max_size`.
- Should produce the same mutation given the same `seed`.
-
[libfuzzer-options-url]: https://llvm.org/docs/LibFuzzer.html#options
blob - 08b64c368dcf8ed5dc24d8379bf0691a4ac8159c (mode 644)
blob + /dev/null
--- mutator/CMakeLists.txt
+++ /dev/null
-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 - 13d11e8d1aab431f7b02fa1956204953ba3d46f1 (mode 644)
blob + /dev/null
--- mutator/crossover.c
+++ /dev/null
-/*
- * 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 - 3dccc65c42f2839278079e4b7542d39a01d98704 (mode 644)
blob + /dev/null
--- mutator/mutator.c
+++ /dev/null
-/*
- * 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 - 3477cff2d884071d10e8811f359e9bc18e4e8b88 (mode 644)
blob + /dev/null
--- mutator/tests/CMakeLists.txt
+++ /dev/null
-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 - aa0cc6ce25dcaa6bc4869da6e2dae1f6a7bf8d85 (mode 644)
blob + /dev/null
--- mutator/tests/crossover_basic_test.c
+++ /dev/null
-#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 - 0c3246676c84583417b2e3939acf59e4fb22bfcd (mode 644)
blob + /dev/null
--- mutator/tests/crossover_e2e_test.c
+++ /dev/null
-#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 - 677e86a9cd6f4c6209cb51de1720e0358ccb6f22 (mode 644)
blob + /dev/null
--- mutator/tests/crossover_seed_test.c
+++ /dev/null
-#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 - b94a884c7deacd4128b697d59d97c00990a67b62 (mode 644)
blob + /dev/null
--- mutator/tests/mutator_basic_test.c
+++ /dev/null
-#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 - 0c3246676c84583417b2e3939acf59e4fb22bfcd (mode 644)
blob + /dev/null
--- mutator/tests/mutator_e2e_test.c
+++ /dev/null
-#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 - 90dc199490f4912d502f71f75baa1f3b21c88b8b (mode 644)
blob + /dev/null
--- mutator/tests/mutator_seed_test.c
+++ /dev/null
-#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 - 5534b285ce376d966905cea86d28413a412c74f7 (mode 644)
blob + /dev/null
--- mutator/tests/script_basic.lua
+++ /dev/null
-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 - 21f0aa67c0286ba0fcf1d670bb720c25fd321322 (mode 644)
blob + /dev/null
--- mutator/tests/script_e2e.lua
+++ /dev/null
-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 - b80ad15623c729591f0c9a5f34f758a18f870066 (mode 644)
blob + /dev/null
--- mutator/tests/script_seed.lua
+++ /dev/null
-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