Commit Diff


commit - 5ba54aa0b94bf4491a5e92872ff8e57458f38270
commit + 2ec5436e90f16c2a59636119dfa46556fcae4c1c
blob - b2814cfdaa625db0fd5fab1dd629c8e5ac8df175
blob + b31d8b458fd75313e13770a17a3803989008f0d4
--- CMakeLists.txt
+++ CMakeLists.txt
@@ -80,4 +80,5 @@ endif()
 enable_testing()
 
 add_subdirectory(extra)
+add_subdirectory(libluamut)
 add_subdirectory(tests)
blob - /dev/null
blob + cfd54a6ca3bd3bac958432ecfd56d6e2efad05f7 (mode 644)
--- /dev/null
+++ libluamut/CMakeLists.txt
@@ -0,0 +1,24 @@
+set(CFLAGS -Wall -Wextra -Wpedantic -Wno-unused-parameter)
+
+if (ENABLE_COV)
+  set(CFLAGS ${CFLAGS} -fprofile-instr-generate -fprofile-arcs
+             -fcoverage-mapping -ftest-coverage)
+  set(LDFLAGS ${LDFLAGS} -fprofile-instr-generate -fprofile-arcs
+              -fcoverage-mapping -ftest-coverage)
+endif (ENABLE_COV)
+
+set(LIB_LUA_MUTATE lua_mutate)
+add_library(${LIB_LUA_MUTATE} STATIC mutate.c)
+target_link_libraries(${LIB_LUA_MUTATE} PRIVATE ${LUA_LIBRARIES} ${LDFLAGS})
+target_include_directories(${LIB_LUA_MUTATE} PRIVATE ${LUA_INCLUDE_DIR})
+target_compile_options(${LIB_LUA_MUTATE} PRIVATE ${CFLAGS})
+add_dependencies(${LIB_LUA_MUTATE} ${LUA_TARGET})
+
+set(LIB_LUA_CROSSOVER lua_crossover)
+add_library(${LIB_LUA_CROSSOVER} STATIC crossover.c)
+target_link_libraries(${LIB_LUA_CROSSOVER} PRIVATE ${LUA_LIBRARIES} ${LDFLAGS})
+target_include_directories(${LIB_LUA_CROSSOVER} PRIVATE ${LUA_INCLUDE_DIR})
+target_compile_options(${LIB_LUA_CROSSOVER} PRIVATE ${CFLAGS})
+add_dependencies(${LIB_LUA_CROSSOVER} ${LUA_TARGET})
+
+add_subdirectory(tests)
blob - /dev/null
blob + 01f3787a24f393feb339642938921c3b4f70df19 (mode 644)
--- /dev/null
+++ libluamut/README.md
@@ -0,0 +1,20 @@
+### libluamut
+
+is two shared libraries that allows using custom mutation and
+crossover functions written in Lua programming language in
+LibFuzzer. When defined these Lua functions will be executed
+instead default LibFuzzer functions `LLVMFuzzerCustomMutator` and
+`LLVMFuzzerCustomCrossover`.
+
+For implementing a custom mutation function in Lua one need to
+create a Lua script with a function `LLVMFuzzerCustomMutator` and
+set a path to the script in an environment variable with name
+`LIBFUZZER_LUA_SCRIPT`. When this environment variable is not set
+default script name `libfuzzer_lua_script.lua` will be used.
+The same with custom crossover function - one need create
+a Lua script with defined Lua function `LLVMFuzzerCustomCrossover`
+and set a path to the script in environment variable
+`LIBFUZZER_LUA_SCRIPT`.
+
+Pay attention that both functions uses its own Lua state
+internally.
blob - /dev/null
blob + 70dd9736b29312418e0fe22da367df1c9c7ed7fa (mode 644)
--- /dev/null
+++ libluamut/crossover.c
@@ -0,0 +1,97 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright 2022-2024, Sergey Bronnikov
+ */
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "lua.h"
+#include "lauxlib.h"
+#include "lualib.h"
+
+static const char *script_default = "./libfuzzer_lua_script.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);
+	}
+	lua_pop(L, 1);
+
+	return ret_size;
+}
+
+/*
+ * The libFuzzer can specify a "Custom Crossover" function for combining two
+ * inputs from the corpus. This function is sometimes called by libFuzzer
+ * when mutating inputs.
+ *
+ * data1: location of first input
+ * size1: length of first input
+ * data1: location of second input
+ * size1: length of second input
+ * out: where to place the resulting, mutated input
+ * max_out_size: the maximum length of the input that can be placed in out
+ * seed: the seed that should be used to make mutations deterministic, when
+ *       needed
+ *
+ * See libfuzzer's LLVMFuzzerCustomCrossOver API for more info.
+ *
+ * Can be NULL.
+ */
+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) ? 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 + 0ad05752d6a92356f69e75715e30c7fd9980b245 (mode 644)
--- /dev/null
+++ libluamut/mutate.c
@@ -0,0 +1,84 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright 2022-2024, Sergey Bronnikov
+ */
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "lua.h"
+#include "lauxlib.h"
+#include "lualib.h"
+
+static const char *script_default = "./libfuzzer_lua_script.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) ? 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);
+
+	if (getenv("") && ret_size != 0) {
+		fprintf(stderr, "-------------------------");
+		fprintf(stderr, "%s\n", Data);
+	}
+
+	lua_close(L);
+
+	return ret_size;
+}
blob - /dev/null
blob + 5a91dca379f8a19f27707a89fd2572b1719b73d6 (mode 644)
--- /dev/null
+++ libluamut/tests/CMakeLists.txt
@@ -0,0 +1,117 @@
+set(ENV_NAME_PATH "LIBFUZZER_LUA_SCRIPT")
+
+add_executable(mutator_basic_test mutator_basic_test.c)
+target_include_directories(mutator_basic_test PRIVATE ${LUA_INCLUDE_DIR})
+target_link_libraries(mutator_basic_test PRIVATE ${LUA_LIBRARIES}
+                                                 ${LDFLAGS}
+                                                 ${LIB_LUA_MUTATE})
+target_compile_options(mutator_basic_test PRIVATE ${CFLAGS})
+add_test(
+  NAME libluamut_mutator_basic_test
+  COMMAND ${CMAKE_CURRENT_BINARY_DIR}/mutator_basic_test
+  WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+)
+set_tests_properties(libluamut_mutator_basic_test PROPERTIES
+  ENVIRONMENT "${ENV_NAME_PATH}=${CMAKE_CURRENT_SOURCE_DIR}/script_basic.lua"
+  LABELS internal
+)
+add_test(
+  NAME libluamut_mutator_no_script_test
+  COMMAND ${CMAKE_CURRENT_BINARY_DIR}/mutator_basic_test
+  WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+)
+set_tests_properties(libluamut_mutator_no_script_test PROPERTIES
+  ENVIRONMENT "${ENV_NAME_PATH}=unknown"
+  PASS_REGULAR_EXPRESSION "is not accessible"
+  LABELS internal
+)
+
+add_executable(mutator_seed_test mutator_seed_test.c)
+target_include_directories(mutator_seed_test PRIVATE ${LUA_INCLUDE_DIR})
+target_link_libraries(mutator_seed_test PRIVATE ${LUA_LIBRARIES}
+                                                ${LDFLAGS}
+                                                ${LIB_LUA_MUTATE})
+target_compile_options(mutator_seed_test PRIVATE ${CFLAGS})
+add_test(
+  NAME libluamut_mutator_seed_test
+  COMMAND ${CMAKE_CURRENT_BINARY_DIR}/mutator_seed_test
+  WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+)
+set_tests_properties(libluamut_mutator_seed_test PROPERTIES
+  ENVIRONMENT "${ENV_NAME_PATH}=${CMAKE_CURRENT_SOURCE_DIR}/script_seed.lua"
+  LABELS internal
+)
+
+add_executable(mutator_e2e_test mutator_e2e_test.c)
+target_include_directories(mutator_e2e_test PRIVATE ${LUA_INCLUDE_DIR})
+target_link_libraries(mutator_e2e_test PRIVATE ${LUA_LIBRARIES}
+                                               ${LDFLAGS} -fsanitize=fuzzer
+                                               ${LIB_LUA_MUTATE})
+target_compile_options(mutator_e2e_test PRIVATE ${CFLAGS} -fsanitize=fuzzer)
+add_test(
+  NAME libluamut_mutator_e2e_test
+  COMMAND ${CMAKE_CURRENT_BINARY_DIR}/mutator_e2e_test
+  WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+)
+set_tests_properties(libluamut_mutator_e2e_test PROPERTIES
+  ENVIRONMENT "${ENV_NAME_PATH}=${CMAKE_CURRENT_SOURCE_DIR}/script_e2e.lua"
+  PASS_REGULAR_EXPRESSION "BINGO: Found the target, exiting."
+  LABELS internal
+)
+
+add_executable(crossover_basic_test crossover_basic_test.c)
+target_include_directories(crossover_basic_test PRIVATE ${LUA_INCLUDE_DIR})
+target_link_libraries(crossover_basic_test PRIVATE
+                      ${LUA_LIBRARIES} ${LDFLAGS} lua_crossover)
+target_compile_options(crossover_basic_test PRIVATE ${CFLAGS})
+add_test(
+  NAME libluamut_crossover_basic_test
+  COMMAND ${CMAKE_CURRENT_BINARY_DIR}/crossover_basic_test
+  WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+)
+set_tests_properties(libluamut_crossover_basic_test PROPERTIES
+  ENVIRONMENT "${ENV_NAME_PATH}=${CMAKE_CURRENT_SOURCE_DIR}/script_basic.lua"
+  LABELS internal
+)
+add_test(
+  NAME libluamut_crossover_no_script_test
+  COMMAND ${CMAKE_CURRENT_BINARY_DIR}/crossover_basic_test
+  WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+)
+set_tests_properties(libluamut_crossover_no_script_test PROPERTIES
+  ENVIRONMENT "${ENV_NAME_PATH}=unknown"
+  PASS_REGULAR_EXPRESSION "is not accessible"
+  LABELS internal
+)
+
+add_executable(crossover_seed_test crossover_seed_test.c)
+target_include_directories(crossover_seed_test PRIVATE
+                           ${LUA_INCLUDE_DIR})
+target_link_libraries(crossover_seed_test PRIVATE
+                      ${LUA_LIBRARIES} ${LDFLAGS} lua_crossover)
+target_compile_options(crossover_seed_test PRIVATE ${CFLAGS})
+add_test(
+  NAME libluamut_crossover_seed_test
+  COMMAND ${CMAKE_CURRENT_BINARY_DIR}/crossover_seed_test
+  WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+)
+set_tests_properties(libluamut_crossover_seed_test PROPERTIES
+  ENVIRONMENT "${ENV_NAME_PATH}=${CMAKE_CURRENT_SOURCE_DIR}/script_seed.lua"
+  LABELS internal
+)
+
+add_executable(crossover_e2e_test crossover_e2e_test.c)
+target_include_directories(crossover_e2e_test PRIVATE ${LUA_INCLUDE_DIR})
+target_link_libraries(crossover_e2e_test PRIVATE
+                      ${LUA_LIBRARIES} ${LDFLAGS} -fsanitize=fuzzer lua_crossover)
+target_compile_options(crossover_e2e_test PRIVATE ${CFLAGS} -fsanitize=fuzzer)
+add_test(
+  NAME libluamut_crossover_e2e_test
+  COMMAND ${CMAKE_CURRENT_BINARY_DIR}/crossover_e2e_test
+  WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+)
+set_tests_properties(libluamut_crossover_e2e_test PROPERTIES
+  ENVIRONMENT "${ENV_NAME_PATH}=${CMAKE_CURRENT_SOURCE_DIR}/script_e2e.lua"
+  PASS_REGULAR_EXPRESSION "BINGO: Found the target, exiting."
+  LABELS internal
+)
blob - /dev/null
blob + 51dad72701a0b3308fd132554207c6beb376ca68 (mode 644)
--- /dev/null
+++ libluamut/tests/crossover_basic_test.c
@@ -0,0 +1,45 @@
+#include <assert.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#ifndef lengthof
+#  define lengthof(array) (sizeof (array) / sizeof ((array)[0]))
+#endif
+
+#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(void);
+
+static void
+test_basic(void)
+{
+	uint8_t data[] = { 'L', 'U', 'A' };
+	size_t size = lengthof(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(memcmp((char *)data, "LUA", size) == 0);
+}
+
+int
+main(void)
+{
+	test_basic();
+}
blob - /dev/null
blob + 3a13c165b5abfd90c17387732e19b9e45c883958 (mode 644)
--- /dev/null
+++ libluamut/tests/crossover_e2e_test.c
@@ -0,0 +1,22 @@
+#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 = malloc(Size + 1);
+	assert(buf);
+	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 + 134c44bbafd5d3b57f67828f028b1c6bf486d167 (mode 644)
--- /dev/null
+++ libluamut/tests/crossover_seed_test.c
@@ -0,0 +1,44 @@
+#include <assert.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <time.h>
+
+#ifndef lengthof
+#  define lengthof(array) (sizeof (array) / sizeof ((array)[0]))
+#endif
+
+#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(void)
+{
+	time_t t;
+	srand((unsigned) time(&t));
+
+    uint8_t data[] = { 'L', 'U', 'A' };
+	size_t size = lengthof(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 + 559220aab3c00458a3c9348f868707ed34d5f3cc (mode 644)
--- /dev/null
+++ libluamut/tests/mutator_basic_test.c
@@ -0,0 +1,41 @@
+#include <assert.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#ifndef lengthof
+#  define lengthof(array) (sizeof (array) / sizeof ((array)[0]))
+#endif
+
+#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(void)
+{
+	uint8_t data[] = { 'L', 'U', 'A' };
+	size_t size = lengthof(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 + 3a13c165b5abfd90c17387732e19b9e45c883958 (mode 644)
--- /dev/null
+++ libluamut/tests/mutator_e2e_test.c
@@ -0,0 +1,22 @@
+#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 = malloc(Size + 1);
+	assert(buf);
+	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 + 0a1d31271efd3e6f6b236fca2ce3d44566c133aa (mode 644)
--- /dev/null
+++ libluamut/tests/mutator_seed_test.c
@@ -0,0 +1,41 @@
+#include <assert.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <time.h>
+
+#ifndef lengthof
+#  define lengthof(array) (sizeof (array) / sizeof ((array)[0]))
+#endif
+
+#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(void)
+{
+	time_t t;
+	srand((unsigned) time(&t));
+
+	uint8_t data[] = { 'L', 'U', 'A' };
+	size_t size = lengthof(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
+++ libluamut/tests/script_basic.lua
@@ -0,0 +1,35 @@
+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
+++ libluamut/tests/script_e2e.lua
@@ -0,0 +1,7 @@
+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
+++ libluamut/tests/script_seed.lua
@@ -0,0 +1,11 @@
+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