commit - /dev/null
commit + e52d181485a11b13f0951adb604e18be363ab8c9
blob - /dev/null
blob + 3a71546d1f7c2c33d99148523db474e789717f7a (mode 644)
--- /dev/null
+++ .github/workflows/check.yaml
+name: Static analysis
+
+on:
+ push:
+ pull_request:
+
+jobs:
+ static-analysis:
+ if: |
+ github.event_name == 'push' ||
+ github.event_name == 'pull_request' &&
+ github.event.pull_request.head.repo.full_name != github.repository
+
+ runs-on: ubuntu-20.04
+
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Setup luarocks
+ run: sudo apt install -y luarocks
+
+ - name: Setup luacheck
+ run: luarocks --local install luacheck
+
+ - run: echo $(luarocks path --lr-bin) >> $GITHUB_PATH
+
+ - name: Run luacheck
+ run: luacheck .
+
+ - run: luarocks lint luzer-scm-1.rockspec
blob - /dev/null
blob + e4f6a7e2b507acb55d5d21d70e5454ed917663af (mode 644)
--- /dev/null
+++ .github/workflows/publish.yaml
+name: Publish
+
+on:
+ push:
+ branches: [master]
+ tags: ['*']
+
+jobs:
+ publish-scm-1:
+ if: github.ref == 'refs/heads/master'
+ runs-on: ubuntu-22.04
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Setup luarocks
+ run: sudo apt install -y luarocks
+
+ - name: Setup cjson (required for upload)
+ run: luarocks install --local lua-cjson
+
+ - name: Upload rockspec scm-1
+ run: luarocks upload --force --api-key=${{ secrets.LUAROCKS_API_KEY }} luzer-scm-1.rockspec
+
+ publish-tag:
+ if: startsWith(github.ref, 'refs/tags/')
+ runs-on: ubuntu-latest
+ steps:
+ # https://github.com/luarocks/luarocks/wiki/Types-of-rocks
+ - uses: actions/checkout@v3
+
+ - name: Setup luarocks
+ run: sudo apt install -y luarocks
+
+ # Make a release.
+ - run: |
+ echo TAG=${GITHUB_REF##*/} >> $GITHUB_ENV
+ luarocks new_version --tag ${{ env.TAG }}
+ luarocks install luzer-${{ env.TAG }}-1.rockspec
+ luarocks pack luzer-${{ env.TAG }}-1.rockspec
+
+ - name: Upload .rockspec and .src.rock ${{ env.TAG }}
+ run: |
+ luarocks upload --api-key=${{ secrets.LUAROCKS_API_KEY }} luzer-${{ env.TAG }}-1.rockspec
+ luarocks upload --api-key=${{ secrets.LUAROCKS_API_KEY }} luzer-${{ env.TAG }}-1.src.rock
+
+ build-rock:
+ if: |
+ github.event_name == 'push' ||
+ github.event_name == 'pull_request' &&
+ github.event.pull_request.head.repo.full_name != github.repository
+ runs-on: ubuntu-22.04
+ steps:
+ - uses: actions/checkout@v3
+
+ - run: sudo apt install -y luarocks lua5.2 liblua5.2-dev libclang-common-14-dev clang-14
+
+ - run: luarocks --local build luzer-scm-1.rockspec
+
+ - run: luarocks --local make
blob - /dev/null
blob + a29216567e10d17dc8b98ba28d41722ea451689c (mode 644)
--- /dev/null
+++ .github/workflows/test.yaml
+name: Testing
+
+on:
+ push:
+ pull_request:
+
+jobs:
+ testing:
+ if: |
+ github.event_name == 'push' ||
+ github.event_name == 'pull_request' &&
+ github.event.pull_request.head.repo.full_name != github.repository
+ strategy:
+ matrix:
+ LIBLUA:
+ - "5.4"
+ - "5.3"
+ - "5.2"
+ - "5.1"
+ fail-fast: false
+ runs-on: ubuntu-22.04
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Setup common packages
+ run: sudo apt install -y clang-14 libclang-common-14-dev
+
+ - name: Setup Lua 5.1 packages
+ run: sudo apt install -y lua5.1 liblua5.1-0-dev
+ if: ${{ matrix.LIBLUA == '5.1' }}
+
+ - name: Setup Lua 5.2 packages
+ run: sudo apt install -y lua5.2 liblua5.2-dev
+ if: ${{ matrix.LIBLUA == '5.2' }}
+
+ - name: Setup Lua 5.3 packages
+ run: sudo apt install -y lua5.3 liblua5.3-dev
+ if: ${{ matrix.LIBLUA == '5.3' }}
+
+ - name: Setup Lua 5.4 packages
+ run: sudo apt install -y lua5.4 liblua5.4-dev
+ if: ${{ matrix.LIBLUA == '5.4' }}
+
+ - name: Running CMake
+ run: cmake -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DENABLE_TESTING=ON -S . -B build
+
+ - name: Building
+ run: cmake --build build --parallel $(nproc)
+
+ - name: Testing
+ run: cmake --build build --target test
+ env:
+ CTEST_OUTPUT_ON_FAILURE: 1
blob - /dev/null
blob + 502322504fbd132167191eb3561230099309a556 (mode 644)
--- /dev/null
+++ .gitignore
+build
+build.luarocks
+crash-*
+.ccls-cache
+.luarc.json
+.rocks
+tags
blob - /dev/null
blob + d996543956e1608c5c91cd113d67d3db29d5ca83 (mode 644)
--- /dev/null
+++ .luacheckrc
+files["mutator/mutator_example.lua"] = {
+ globals = {
+ "LLVMFuzzerCustomMutator",
+ "LLVMFuzzerMutate",
+ },
+}
+
+files["luzer/tests/*.lua"] = {
+ globals = {
+ "luzer_test_one_input",
+ "luzer_custom_mutator",
+ },
+}
+
+include_files = {
+ '.luacheckrc',
+ '*.rockspec',
+ '**/*.lua',
+}
+
+exclude_files = {
+ '.rocks',
+ 'luzer-tests/',
+ 'patches/',
+ 'build/',
+ 'trash/',
+}
blob - /dev/null
blob + b9e81c8dc7ffbf08ff895aea08a0228091f1acde (mode 644)
--- /dev/null
+++ CHANGELOG.md
+# Changelog
+
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## [Unreleased]
+
+### Added
+
+- Integration with libFuzzer's `LLVMFuzzerTestOneInput()`.
+- Integration with libFuzzer's `LLVMFuzzerCustomMutator()`.
+- Integration with libFuzzer's `FuzzedDataProvider`.
blob - /dev/null
blob + bb2884fff6f9a12fb76c1e1a9cada7fed3d7714f (mode 644)
--- /dev/null
+++ CMakeLists.txt
+cmake_minimum_required(VERSION 3.10.2)
+
+project(luzer
+ LANGUAGES C CXX
+ VERSION "1.0.0"
+)
+
+find_package(Lua 5.1 REQUIRED)
+find_package(LLVM REQUIRED CONFIG)
+find_library(LIBRT rt)
+
+set(LUA_NAME "lua${LUA_VERSION_MAJOR}.${LUA_VERSION_MINOR}")
+find_program(LUA_EXECUTABLE "${LUA_NAME}")
+if(NOT EXISTS ${LUA_EXECUTABLE})
+ message(FATAL_ERROR "${LUA_NAME} is required")
+endif()
+
+message(STATUS "Found Lua ${LUA_VERSION_STRING}")
+message(STATUS "Found Lua interpreter ${LUA_EXECUTABLE}")
+message(STATUS "Found LLVM ${LLVM_VERSION}")
+
+if(${LLVM_PACKAGE_VERSION} VERSION_LESS 5.0.0)
+ message(FATAL_ERROR "LLVM 5.0.0 or newer is required")
+endif()
+
+if(NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR
+ NOT CMAKE_C_COMPILER_ID STREQUAL "Clang")
+ message(FATAL_ERROR
+ "\n"
+ "Building is supported with Clang compiler only.\n"
+ " $ rm -rf build\n"
+ " $ cmake -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -S . -B build\n"
+ " $ cmake --build build --parallel\n"
+ "\n")
+endif()
+
+if(ENABLE_TESTING AND NOT EXISTS ${LUA_EXECUTABLE})
+ message(WARNING "Lua executable is not found, testing is not available.")
+ unset(ENABLE_TESTING)
+else()
+ enable_testing()
+endif()
+
+add_subdirectory(mutator)
+add_subdirectory(luzer)
+
+## Install ####################################################################
+###############################################################################
+
+if (NOT CMAKE_LUADIR)
+ set(CMAKE_LUADIR "${CMAKE_PREFIX_PATH}")
+endif()
+
+if (NOT CMAKE_LIBDIR)
+ set(CMAKE_LIBDIR "${CMAKE_INCLUDE_PATH}")
+endif()
+
+install(
+ FILES
+ ${CMAKE_CURRENT_SOURCE_DIR}/README.md
+ ${CMAKE_CURRENT_SOURCE_DIR}/docs/api.md
+ ${CMAKE_CURRENT_SOURCE_DIR}/docs/grammar_based_fuzzing.md
+ ${CMAKE_CURRENT_SOURCE_DIR}/docs/index.md
+ ${CMAKE_CURRENT_SOURCE_DIR}/docs/test_management.md
+ ${CMAKE_CURRENT_SOURCE_DIR}/docs/usage.md
+ DESTINATION ${CMAKE_LUADIR}/doc
+)
blob - /dev/null
blob + 75bf65a6163e8f3eb55d5072ada1ddceabf328cf (mode 644)
--- /dev/null
+++ CMakePresets.json
+{
+ "version": 6,
+ "cmakeMinimumRequired": {
+ "major": 3,
+ "minor": 20,
+ "patch": 0
+ },
+ "configurePresets": [
+ {
+ "name": "default",
+ "displayName": "Default Config (Unix Makefile)",
+ "description": "Default build using Unix Makefile generator",
+ "binaryDir": "${sourceDir}/build",
+ "cacheVariables": {
+ "CMAKE_BUILD_TYPE": "Debug",
+ "CMAKE_C_COMPILER": "clang",
+ "CMAKE_CXX_COMPILER": "clang++",
+ "CMAKE_EXPORT_COMPILE_COMMANDS": {
+ "type": "BOOL",
+ "value": "ON"
+ },
+ "ENABLE_TESTING": {
+ "type": "BOOL",
+ "value": "ON"
+ }
+ }
+ }
+ ],
+ "buildPresets": [
+ {
+ "name": "default",
+ "configurePreset": "default",
+ "jobs": 10
+ }
+ ],
+ "testPresets": [
+ {
+ "name": "default",
+ "configurePreset": "default",
+ "output": {"outputOnFailure": true},
+ "execution": {"noTestsAction": "error", "stopOnFailure": true}
+ }
+ ],
+ "workflowPresets": [
+ {
+ "name": "default",
+ "steps": [
+ {
+ "type": "configure",
+ "name": "default"
+ },
+ {
+ "type": "build",
+ "name": "default"
+ },
+ {
+ "type": "test",
+ "name": "default"
+ }
+ ]
+ }
+ ]
+}
blob - /dev/null
blob + d70059b9ce65a47a49fd8161ff744b16d01722ef (mode 644)
--- /dev/null
+++ CONTRIBUTING.md
+## Hacking
+
+For developing `luzer` you need to install required packages. On Debian: `apt
+install -y liblua5.1-0-dev llvm-dev libclang-common-13-dev clang cmake`.
+
+```sh
+$ cmake -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DENABLE_TESTING=ON -S . -B build
+$ cmake --build build --parallel
+$ cmake --build build --target test
+```
+
+You are ready to make patches!
blob - /dev/null
blob + e52f0d131d5e2ddd9ab5647a45e7fa741272f35c (mode 644)
--- /dev/null
+++ LICENSE
+ISC License
+
+Copyright (c) 2022-2023 Sergey Bronnikov
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+PERFORMANCE OF THIS SOFTWARE.
blob - /dev/null
blob + a45083a78cd0f2a92a2d050ed1c058a14c25f93d (mode 644)
--- /dev/null
+++ README.md
+[![Static analysis](https://github.com/ligurio/luzer/actions/workflows/check.yaml/badge.svg)](https://github.com/ligurio/luzer/actions/workflows/check.yaml)
+[![Testing](https://github.com/ligurio/luzer/actions/workflows/test.yaml/badge.svg)](https://github.com/ligurio/luzer/actions/workflows/test.yaml)
+[![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg)](https://opensource.org/licenses/ISC)
+[![Luarocks](https://img.shields.io/luarocks/v/ligurio/luzer/scm-1)](https://luarocks.org/modules/ligurio/luzer)
+
+# luzer
+
+a coverage-guided, native Lua fuzzer.
+
+## Overview
+
+Fuzzing is a type of automated testing which continuously manipulates inputs to
+a program to find bugs. `luzer` uses coverage guidance to intelligently walk
+through the code being fuzzed to find and report failures to the user. Since it
+can reach edge cases which humans often miss, fuzz testing can be particularly
+valuable for finding security exploits and vulnerabilities.
+
+`luzer` is a coverage-guided Lua fuzzing engine. It supports fuzzing of Lua
+code, but also C extensions written for Lua. Luzer is based off of
+[libFuzzer][libfuzzer-url]. When fuzzing native code, `luzer` can be used in
+combination with Address Sanitizer or Undefined Behavior Sanitizer to catch
+extra bugs.
+
+## Quickstart
+
+To use luzer in your own project follow these few simple steps:
+
+1. Setup `luzer` module:
+
+```sh
+$ luarocks --local install luzer
+$ eval $(luarocks path)
+```
+
+2. Create a fuzz target invoking your code:
+
+```lua
+local luzer = require("luzer")
+
+local function TestOneInput(buf)
+ local b = {}
+ buf:gsub(".", function(c) table.insert(b, c) end)
+ if b[1] == 'c' then
+ if b[2] == 'r' then
+ if b[3] == 'a' then
+ if b[4] == 's' then
+ if b[5] == 'h' then
+ assert(nil)
+ end
+ end
+ end
+ end
+ end
+end
+
+luzer.Fuzz(TestOneInput)
+```
+
+3. Start the fuzzer using the fuzz target
+
+```
+$ luajit examples/example_basic.lua
+INFO: Running with entropic power schedule (0xFF, 100).
+INFO: Seed: 1557779137
+INFO: Loaded 1 modules (151 inline 8-bit counters): 151 [0x7f0640e706e3, 0x7f0640e7077a),
+INFO: Loaded 1 PC tables (151 PCs): 151 [0x7f0640e70780,0x7f0640e710f0),
+INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 4096 bytes
+INFO: A corpus is not provided, starting from an empty corpus
+#2 INITED cov: 17 ft: 18 corp: 1/1b exec/s: 0 rss: 26Mb
+#32 NEW cov: 17 ft: 24 corp: 2/4b lim: 4 exec/s: 0 rss: 26Mb L: 3/3 MS: 5 ShuffleBytes-ShuffleBytes-CopyPart-ChangeByte-CMP- DE: "\x00\x00"-
+...
+```
+
+While fuzzing is in progress, the fuzzing engine generates new inputs and runs
+them against the provided fuzz target. By default, it continues to run until a
+failing input is found, or the user cancels the process (e.g. with `Ctrl^C`).
+
+The first lines indicate that the "baseline coverage" is gathered before
+fuzzing begins.
+
+To gather baseline coverage, the fuzzing engine executes both the seed corpus
+and the generated corpus, to ensure that no errors occurred and to understand
+the code coverage the existing corpus already provides.
+
+## License
+
+Copyright © 2022-2023 [Sergey Bronnikov][bronevichok-url].
+
+Distributed under the ISC License.
+
+[libfuzzer-url]: https://llvm.org/docs/LibFuzzer.html
+[bronevichok-url]: https://bronevichok.ru/
blob - /dev/null
blob + 785a9e297fabab0f521d678b5d673a9840829a24 (mode 644)
--- /dev/null
+++ luzer/CMakeLists.txt
+# Locate compiler-rt libraries.
+# Location is LLVM_LIBRARY_DIRS/clang/<version>/lib/<OS>/,
+# for example LLVM_LIBRARY_DIRS/clang/4.0.0/lib/darwin/.
+#
+# See https://llvm.org/docs/LibFuzzer.html#using-libfuzzer-as-a-library
+
+set(LLVM_BASE ${LLVM_LIBRARY_DIRS}/clang/${LLVM_PACKAGE_VERSION})
+string(TOLOWER ${CMAKE_HOST_SYSTEM_NAME} OS_NAME)
+set(LIBCLANG_RT ${LLVM_BASE}/lib/${OS_NAME}/libclang_rt.fuzzer_no_main-x86_64.a)
+if(EXISTS ${LIBCLANG_RT})
+ message(STATUS "Found libclang_rt ${LIBCLANG_RT}")
+else()
+ message(FATAL_ERROR "libclang_rt is not found")
+endif()
+
+configure_file(
+ ${CMAKE_CURRENT_SOURCE_DIR}/version.c
+ ${CMAKE_CURRENT_BINARY_DIR}/version.c
+ @ONLY
+)
+
+set(LUZER_SOURCES luzer.c
+ fuzzed_data_provider.cc
+ tracer.c
+ counters.c
+ ${CMAKE_CURRENT_BINARY_DIR}/version.c)
+
+add_library(${CMAKE_PROJECT_NAME} SHARED ${LUZER_SOURCES})
+target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE
+ ${LUA_INCLUDE_DIR}
+)
+target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE
+ ${LUA_LIBRARIES}
+ ${LIBRT}
+ ${LIBCLANG_RT}
+ -fsanitize=fuzzer-no-link
+)
+target_compile_options(${CMAKE_PROJECT_NAME} PRIVATE
+ -D_FORTIFY_SOURCE=2
+ -fpie
+ -fPIC
+ -Wall
+ -Wextra
+ -Werror
+ -Wpedantic
+ -Wno-unused-parameter
+ -pedantic
+ -fsanitize=fuzzer-no-link
+)
+set_target_properties(${CMAKE_PROJECT_NAME} 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})
+set_target_properties(custom_mutator PROPERTIES VERSION ${PROJECT_VERSION})
+set_target_properties(custom_mutator PROPERTIES SOVERSION 1)
+
+if(ENABLE_TESTING)
+ add_subdirectory(tests)
+endif()
+
+install(
+ TARGETS ${PROJECT_NAME}
+ LIBRARY
+ DESTINATION "${CMAKE_LIBDIR}/"
+ RENAME luzer.so
+)
+
+# See description of NAMELINK_SKIP in
+# https://cmake.org/cmake/help/latest/command/install.html
+install(
+ TARGETS custom_mutator
+ LIBRARY
+ NAMELINK_SKIP
+ DESTINATION "${CMAKE_LIBDIR}/"
+)
blob - /dev/null
blob + 68d1ebb363710e621de92a97a061474f6cc270ec (mode 644)
--- /dev/null
+++ luzer/counters.c
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright 2022-2023, Sergey Bronnikov
+ */
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/mman.h>
+
+#include "counters.h"
+#include "macros.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+void __sanitizer_cov_8bit_counters_init(uint8_t* start, uint8_t* stop);
+void __sanitizer_cov_pcs_init(uint8_t* pcs_beg, uint8_t* pcs_end);
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+static const int kDefaultNumCounters = 1 << 20;
+
+// Number of counters requested by Lua instrumentation.
+int counter_index = 0;
+// Number of counters given to Libfuzzer.
+int counter_index_registered = 0;
+// Maximum number of counters and pctable entries that may be reserved and also
+// the number that are allocated.
+int max_counters = 0;
+// Counter Allocations. These are allocated once, before __sanitize_... are
+// called and can only be deallocated by test_only_reset_counters.
+unsigned char* counters = NULL;
+struct PCTableEntry* pctable = NULL;
+
+NO_SANITIZE void
+test_only_reset_counters(void) {
+ if (counters) {
+ munmap(counters, max_counters);
+ counters = NULL;
+ }
+ if (pctable) {
+ munmap(pctable, max_counters);
+ pctable = NULL;
+ }
+ max_counters = 0;
+ counter_index = 0;
+ counter_index_registered = 0;
+}
+
+NO_SANITIZE int
+reserve_counters(int counters) {
+ int ret = counter_index;
+ counter_index += counters;
+ return ret;
+}
+
+NO_SANITIZE int
+reserve_counter(void)
+{
+ return counter_index++;
+}
+
+NO_SANITIZE void
+increment_counter(int counter_index)
+{
+ if (counters != NULL && pctable != NULL) {
+ // `counters` is an allocation of length `max_counters`. If we reserve more
+ // than the allocated number of counters, we'll wrap around and overload
+ // old counters, trading away fuzzing quality for limits on memory usage.
+ counters[counter_index % max_counters]++;
+ }
+}
+
+NO_SANITIZE void
+set_max_counters(int max)
+{
+ if (counters != NULL && pctable != NULL) {
+ fprintf(stderr, "Internal error: attempt to set max number of counters after "
+ "counters were passed to the sanitizer!\n");
+ _exit(1);
+ }
+ if (max < 1)
+ _exit(1);
+
+ max_counters = max;
+}
+
+NO_SANITIZE int
+get_max_counters(void)
+{
+ return max_counters;
+}
+
+NO_SANITIZE counter_and_pc_table_range
+allocate_counters_and_pcs(void) {
+ if (max_counters < 1) {
+ set_max_counters(kDefaultNumCounters);
+ }
+ if (counter_index < counter_index_registered) {
+ fprintf(stderr, "Internal error: The counter index is "
+ "greater than the number of counters registered.\n");
+ _exit(1);
+ }
+ // Allocate memory.
+ if (counters == NULL || pctable == NULL) {
+ // We mmap memory for pctable and counters, instead of std::vector, ensuring
+ // that there is no initialization. The untouched memory will only cost
+ // virtual memory, which is cheap.
+ counters = (unsigned char*)(
+ mmap(NULL, max_counters, PROT_READ | PROT_WRITE,
+ MAP_ANONYMOUS | MAP_PRIVATE, -1, 0));
+ pctable = (struct PCTableEntry*)(
+ mmap(NULL, max_counters * sizeof(struct PCTableEntry),
+ PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0));
+ if (counters == MAP_FAILED || pctable == MAP_FAILED) {
+ fprintf(stderr, "Internal error: Failed to mmap counters.\n");
+ _exit(1);
+ }
+ }
+
+ const int next_index = MIN(counter_index, max_counters);
+ if (counter_index_registered >= next_index) {
+ // There are no counters to pass. Perhaps because we've reserved more than
+ // max_counters, or because no counters have been reserved since this was
+ // last called.
+ counter_index_registered = counter_index;
+ return (counter_and_pc_table_range){NULL, NULL, NULL, NULL};
+ } else {
+ counter_and_pc_table_range ranges = {
+ .counters_start = counters + counter_index_registered,
+ .counters_end = counters + next_index,
+ .pctable_start = (uint8_t*)(pctable + counter_index_registered),
+ .pctable_end = (uint8_t*)(pctable + next_index)
+ };
+ counter_index_registered = counter_index;
+ return ranges;
+ }
+}
blob - /dev/null
blob + 4bfa9f469bfa0035448118474ecbb0303f8f4e57 (mode 644)
--- /dev/null
+++ luzer/counters.h
+#ifndef LUZER_COUNTERS_H_
+#define LUZER_COUNTERS_H_
+
+struct PCTableEntry {
+ void* pc;
+ long flags;
+};
+
+// Sets the global number of counters.
+// Must not be called after InitializeCountersWithLLVM is called.
+void set_max_counters(int max);
+
+// Returns the maximum number of allocatable luzer counters. If more than this
+// many counters are reserved, luzer reuses counters, lowering fuzz quality.
+int get_max_counters(void);
+
+// Returns a new counter index.
+int reserve_counter(void);
+// Reserves a number of counters with contiguous indices, and returns the first
+// index.
+int reserve_counters(int counters);
+
+// Increments a counter at the given index. If more than the maximum number of
+// counters has been reserved, reuse counters.
+void increment_counter(int counter_index);
+
+typedef struct counter_and_pc_table_range {
+ unsigned char* counters_start;
+ unsigned char* counters_end;
+ unsigned char* pctable_start;
+ unsigned char* pctable_end;
+} counter_and_pc_table_range;
+
+// Returns pointers to a range of memory for counters and another for pctable.
+// The intent is for this memory to be handed to Libfuzzer. It will only be
+// deallocated by test_only_reset_counters. The size of the ranges is proportional
+// to the number of counters reserved, unless no new counters were reserved or
+// more than max_counters were already reserved, in which case returns nullptrs.
+counter_and_pc_table_range allocate_counters_and_pcs(void);
+
+// Resets counters' state to defaults. This is not safe for use with the actual
+// fuzzer as, once fuzzing begins, the fuzzer is given access to the counters'
+// memory. Unless you swapped out the fuzzer and know it will not access the
+// previous counters and pctable entries again, you'll probably segfault.
+void test_only_reset_counters(void);
+
+#endif // LUZER_COUNTERS_H_
blob - /dev/null
blob + 4895b6396567a8b59f833420b99efef74192f70f (mode 644)
--- /dev/null
+++ luzer/custom_mutator_lib.c
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright 2022-2023, Sergey Bronnikov
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <lua.h>
+#include <lauxlib.h>
+
+#include "luzer.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+int luaL_error(lua_State *L, const char *fmt, ...);
+size_t lua_objlen(lua_State *L, int index);
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+size_t
+LLVMFuzzerCustomMutator(uint8_t* data, size_t size,
+ size_t max_size, unsigned int seed)
+{
+ lua_State *L = get_global_lua_state();
+ lua_pushlstring(L, (char *)data, size);
+ lua_pushinteger(L, max_size);
+ lua_pushinteger(L, seed);
+ luaL_mutate(L);
+
+ size_t sz = lua_objlen(L, -1);
+ if (sz > max_size)
+ luaL_error(L, "The size of mutated data cannot be larger than a max_size.");
+ const char *buf = lua_tostring(L, -1);
+ free(data);
+ data = (uint8_t *)buf;
+
+ return sz;
+}
blob - /dev/null
blob + 93424cfee62cdbe00123e97f5bcd3b9c62450e3f (mode 644)
--- /dev/null
+++ luzer/fuzzed_data_provider.cc
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright 2022-2023, Sergey Bronnikov
+ */
+
+#include <lua.h>
+#include <lauxlib.h>
+#include <lualib.h>
+#include <float.h>
+#include <fuzzer/FuzzedDataProvider.h>
+
+#include "fuzzed_data_provider.h"
+#include "macros.h"
+
+/**
+ * Unique name for userdata metatables.
+ */
+#define FDP_LUA_UDATA_NAME "fdp"
+
+/*
+ * A convenience wrapper turning the raw fuzzer input bytes into Lua primitive
+ * types. The methods behave similarly to math.random(), with all returned
+ * values depending deterministically on the fuzzer input for the current run.
+ */
+
+typedef struct {
+ FuzzedDataProvider *fdp;
+} lua_userdata_t;
+
+/* Consumes a string from the fuzzer input. */
+static int
+luaL_consume_string(lua_State *L)
+{
+ lua_userdata_t *lfdp;
+ lfdp = (lua_userdata_t *)luaL_checkudata(L, 1, FDP_LUA_UDATA_NAME);
+ size_t max_length = luaL_checkinteger(L, 2);
+ if (!lfdp)
+ luaL_error(L, "Usage: <FuzzedDataProvider>:consume_string(max_length)");
+
+ std::string str = lfdp->fdp->ConsumeRandomLengthString(max_length);
+ const char *cstr = str.c_str();
+ lua_pushlstring(L, cstr, str.length());
+
+ return 1;
+}
+
+/* Consumes a table with specified number of strings from the fuzzer input. */
+static int
+luaL_consume_strings(lua_State *L)
+{
+ lua_userdata_t *lfdp;
+ lfdp = (lua_userdata_t *)luaL_checkudata(L, 1, FDP_LUA_UDATA_NAME);
+ if (!lfdp)
+ luaL_error(L, "Usage: <FuzzedDataProvider>:consume_strings(count, max_length)");
+ size_t count = luaL_checkinteger(L, 2);
+ size_t max_length = luaL_checkinteger(L, 3);
+
+ std::string str;
+ const char *cstr;
+
+ lua_newtable(L);
+ for (int i = 1; i <= (int)count; i++) {
+ str = lfdp->fdp->ConsumeRandomLengthString(max_length);
+ cstr = str.c_str();
+ lua_pushnumber(L, i);
+ lua_pushlstring(L, cstr, str.length());
+ lua_settable(L, -3);
+ }
+
+ return 1;
+}
+
+/* Consumes a boolean from the fuzzer input. */
+static int
+luaL_consume_boolean(lua_State *L)
+{
+ lua_userdata_t *lfdp;
+ lfdp = (lua_userdata_t *)luaL_checkudata(L, 1, FDP_LUA_UDATA_NAME);
+ if (!lfdp)
+ luaL_error(L, "Usage: <FuzzedDataProvider>:consume_boolean()");
+
+ bool b = lfdp->fdp->ConsumeBool();
+ lua_pushboolean(L, (int)b);
+
+ return 1;
+}
+
+/* Consumes a table with specified number of booleans from the fuzzer input. */
+static int
+luaL_consume_booleans(lua_State *L)
+{
+ lua_userdata_t *lfdp;
+ lfdp = (lua_userdata_t *)luaL_checkudata(L, 1, FDP_LUA_UDATA_NAME);
+ if (!lfdp)
+ luaL_error(L, "Usage: <FuzzedDataProvider>:consume_booleans(count)");
+ int count = luaL_checkinteger(L, 2);
+
+ lua_newtable(L);
+ for (int i = 1; i <= (int)count; i++) {
+ bool b = lfdp->fdp->ConsumeBool();
+ lua_pushnumber(L, i);
+ lua_pushboolean(L, (int)b);
+ lua_settable(L, -3);
+ }
+
+ return 1;
+}
+
+/* Consumes a float from the fuzzer input. */
+static int
+luaL_consume_number(lua_State *L)
+{
+ lua_userdata_t *lfdp;
+ lfdp = (lua_userdata_t *)luaL_checkudata(L, 1, FDP_LUA_UDATA_NAME);
+ if (!lfdp)
+ luaL_error(L, "Usage: <FuzzedDataProvider>:consume_number(min, max)");
+ double min = luaL_checknumber(L, 2);
+ double max = luaL_checknumber(L, 3);
+ if (min > max)
+ luaL_error(L, "min must be less than or equal to max");
+
+ auto number = lfdp->fdp->ConsumeFloatingPointInRange(min, max);
+ lua_pushnumber(L, number);
+
+ return 1;
+}
+
+/* Consumes a table with specified number of numbers from the fuzzer input. */
+static int
+luaL_consume_numbers(lua_State *L)
+{
+ lua_userdata_t *lfdp;
+ lfdp = (lua_userdata_t *)luaL_checkudata(L, 1, FDP_LUA_UDATA_NAME);
+ if (!lfdp)
+ luaL_error(L, "Usage: <FuzzedDataProvider>:consume_numbers(count, min, max)");
+ int count = luaL_checkinteger(L, 2);
+ double min = luaL_checkinteger(L, 3);
+ double max = luaL_checkinteger(L, 4);
+ if (min > max)
+ luaL_error(L, "min must be less than or equal to max");
+
+ lua_newtable(L);
+ for (int i = 1; i <= count; i++) {
+ auto number = lfdp->fdp->ConsumeFloatingPointInRange(min, max);
+ lua_pushnumber(L, i);
+ lua_pushnumber(L, number);
+ lua_settable(L, -3);
+ }
+
+ return 1;
+}
+
+/* Consumes an arbitrary int or an int between min and max from the fuzzer
+ input. */
+static int
+luaL_consume_integer(lua_State *L)
+{
+ lua_userdata_t *lfdp;
+ lfdp = (lua_userdata_t *)luaL_checkudata(L, 1, FDP_LUA_UDATA_NAME);
+ if (!lfdp)
+ luaL_error(L, "Usage: <FuzzedDataProvider>:consume_integer(min, max)");
+ int min = luaL_checkinteger(L, 2);
+ int max = luaL_checkinteger(L, 3);
+ if (min > max)
+ luaL_error(L, "min must be less than or equal to max");
+
+ auto number = lfdp->fdp->ConsumeIntegralInRange(min, max);
+ lua_pushnumber(L, number);
+
+ return 1;
+}
+
+/* Consumes an int array from the fuzzer input. */
+static int
+luaL_consume_integers(lua_State *L)
+{
+ lua_userdata_t *lfdp;
+ lfdp = (lua_userdata_t *)luaL_checkudata(L, 1, FDP_LUA_UDATA_NAME);
+ if (!lfdp)
+ luaL_error(L, "Usage: <FuzzedDataProvider>:consume_integers(count, min, max)");
+ int count = luaL_checkinteger(L, 2);
+ int min = luaL_checkinteger(L, 3);
+ int max = luaL_checkinteger(L, 4);
+ if (min > max)
+ luaL_error(L, "min must be less than or equal to max");
+
+ lua_newtable(L);
+ for (int i = 1; i <= (int)count; i++) {
+ auto number = lfdp->fdp->ConsumeIntegralInRange(min, max);
+ lua_pushnumber(L, i);
+ lua_pushinteger(L, number);
+ lua_settable(L, -3);
+ }
+
+ return 1;
+}
+
+static int
+luaL_consume_probability(lua_State *L)
+{
+ lua_userdata_t *lfdp;
+ lfdp = (lua_userdata_t *)luaL_checkudata(L, 1, FDP_LUA_UDATA_NAME);
+ if (!lfdp)
+ luaL_error(L, "Usage: <FuzzedDataProvider>:consume_probability()");
+
+ auto probability = lfdp->fdp->ConsumeFloatingPointInRange(0.0, 1.0);
+ lua_pushnumber(L, probability);
+
+ return 1;
+}
+
+/* Returns the number of unconsumed bytes in the fuzzer input. */
+static int
+luaL_remaining_bytes(lua_State *L)
+{
+ lua_userdata_t *lfdp;
+ lfdp = (lua_userdata_t *)luaL_checkudata(L, 1, FDP_LUA_UDATA_NAME);
+ if (!lfdp)
+ luaL_error(L, "Usage: <FuzzedDataProvider>:remaining_bytes()");
+
+ size_t sz = lfdp->fdp->remaining_bytes();
+ lua_pushnumber(L, sz);
+
+ return 1;
+}
+
+static int close(lua_State *L) {
+ lua_userdata_t *lfdp;
+ lfdp = (lua_userdata_t *)luaL_checkudata(L, 1, FDP_LUA_UDATA_NAME);
+ delete lfdp->fdp;
+
+ return 0;
+}
+
+static int tostring(lua_State *L) {
+ lua_pushstring(L, "FuzzedDataProvider");
+ return 1;
+}
+
+const luaL_Reg methods[] =
+{
+ { "consume_string", luaL_consume_string },
+ { "consume_strings", luaL_consume_strings },
+ { "consume_boolean", luaL_consume_boolean },
+ { "consume_booleans", luaL_consume_booleans },
+ { "consume_number", luaL_consume_number },
+ { "consume_numbers", luaL_consume_numbers },
+ { "consume_integer", luaL_consume_integer },
+ { "consume_integers", luaL_consume_integers },
+ { "consume_probability", luaL_consume_probability },
+ { "remaining_bytes", luaL_remaining_bytes },
+ { "__gc", close },
+ { "__tostring", tostring },
+ { NULL, NULL }
+};
+
+int
+luaL_fuzzed_data_provider(lua_State *L)
+{
+ int index = lua_gettop(L);
+ if (index != 1)
+ luaL_error(L, "Usage: luzer.FuzzedDataProvider(string)");
+
+ const char *data = luaL_checkstring(L, 1);
+ size_t size = strlen(data);
+
+ luaL_newmetatable(L, FDP_LUA_UDATA_NAME);
+ lua_pushvalue(L, -1);
+ lua_setfield(L, -2, "__index");
+#if LUA_VERSION_NUM == 501
+ luaL_register(L, NULL, methods);
+#else
+ luaL_setfuncs(L, methods, 0);
+#endif
+
+ lua_userdata_t *lfdp;
+ lfdp = (lua_userdata_t*)lua_newuserdata(L, sizeof(*lfdp));
+ FuzzedDataProvider *fdp = new FuzzedDataProvider((const unsigned char *)data, size);
+ lfdp->fdp = fdp;
+
+ luaL_getmetatable(L, FDP_LUA_UDATA_NAME);
+ lua_setmetatable(L, -2);
+
+ return 1;
+}
blob - /dev/null
blob + 74a999d20d31db793b7a6d70f0d84d4f5280b351 (mode 644)
--- /dev/null
+++ luzer/fuzzed_data_provider.h
+#ifndef LUZER_FUZZED_DATA_PROVIDER_H_
+#define LUZER_FUZZED_DATA_PROVIDER_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+ int luaL_fuzzed_data_provider(lua_State *L);
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif // LUZER_FUZZED_DATA_PROVIDER_H_
blob - /dev/null
blob + d776a6c05ebf95e627c8ad87bc39b0401b517a70 (mode 644)
--- /dev/null
+++ luzer/luzer.c
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright 2022-2023, Sergey Bronnikov
+ */
+
+#define _GNU_SOURCE
+#include <lua.h>
+#include <lualib.h>
+#include <lauxlib.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <signal.h>
+#include <string.h>
+#include <dlfcn.h>
+#include <libgen.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <linux/limits.h>
+
+#include "fuzzed_data_provider.h"
+#include "counters.h"
+#include "macros.h"
+#include "tracer.h"
+#include "version.h"
+#include "luzer.h"
+
+#define TEST_ONE_INPUT_FUNC "luzer_test_one_input"
+#define CUSTOM_MUTATOR_FUNC "luzer_custom_mutator"
+#define CUSTOM_MUTATOR_LIB "libcustom_mutator.so.1"
+#define DEBUG_HOOK_FUNC "luzer_custom_hook"
+
+static lua_State *LL;
+
+static void
+set_global_lua_state(lua_State *L)
+{
+ LL = L;
+}
+
+lua_State *
+get_global_lua_state(void)
+{
+ if (!LL)
+ luaL_error(LL, "Lua state is not initialized.");
+
+ return LL;
+}
+
+#if LUA_VERSION_NUM < 502
+static int
+luaL_traceback(lua_State *L) {
+ lua_getfield(L, LUA_GLOBALSINDEX, "debug");
+ if (!lua_istable(L, -1)) {
+ lua_pop(L, 1);
+ return 1;
+ }
+ lua_getfield(L, -1, "traceback");
+ if (!lua_isfunction(L, -1)) {
+ lua_pop(L, 2);
+ return 1;
+ }
+ lua_pushvalue(L, 1);
+ lua_pushinteger(L, 2);
+ lua_call(L, 2, 1);
+ fprintf(stderr, "%s\n", lua_tostring(L, -1));
+ return 1;
+}
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+typedef int (*UserCb)(const uint8_t* Data, size_t Size);
+int LLVMFuzzerRunDriver(int* argc, char*** argv,
+ int (*UserCb)(const uint8_t* Data, size_t Size));
+void __sanitizer_cov_8bit_counters_init(uint8_t* start, uint8_t* stop);
+
+// [pcs_beg, pcs_end) is an array of ptr-sized integers representing
+// pairs [PC, PCFlags] for every instrumented block in the current DSO.
+// Capture this array in order to read the PCs and their Flags.
+// The number of PCs and PCFlags for a given DSO is the same as the number
+// of 8-bit counters (-fsanitize-coverage=inline-8bit-counters), or
+// boolean flags (-fsanitize-coverage=inline=bool-flags), or trace_pc_guard
+// callbacks (-fsanitize-coverage=trace-pc-guard).
+// A PCFlags describes the basic block:
+// * bit0: 1 if the block is the function entry block, 0 otherwise.
+void __sanitizer_cov_pcs_init(uint8_t* pcs_beg, uint8_t* pcs_end);
+
+/**
+ * Sets the callback to be called right before death on error.
+ * Passing 0 will unset the callback. Called in libfuzzer_driver.cpp.
+ */
+NO_SANITIZE void
+__sanitizer_set_death_callback(void (*callback)(void))
+{
+ /* cleanup(); */
+}
+
+/**
+ * Suppress libFuzzer warnings about missing sanitizer methods in non-sanitizer
+ * builds.
+ */
+NO_SANITIZE int
+__sanitizer_acquire_crash_state(void)
+{
+ return 1;
+}
+
+/**
+ * Print the stack trace leading to this call. Useful for debugging user code.
+ * See:
+ * - https://github.com/keplerproject/lua-compat-5.2/blob/master/c-api/compat-5.2.c#L229
+ * - http://www.lua.org/manual/5.2/manual.html#luaL_traceback
+ */
+NO_SANITIZE void
+__sanitizer_print_stack_trace(void)
+{
+ lua_State *L = get_global_lua_state();
+#if LUA_VERSION_NUM < 502
+ luaL_traceback(L);
+#else
+ luaL_traceback(L, L, "traceback", 3);
+#endif
+}
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+NO_SANITIZE const char *
+GetLibFuzzerSymbolsLocation(void) {
+ Dl_info dl_info;
+ if (!dladdr((void*)&LLVMFuzzerRunDriver, &dl_info)) {
+ return "<Not a shared object>";
+ }
+ return (dl_info.dli_fname);
+}
+
+NO_SANITIZE const char *
+GetCoverageSymbolsLocation(void) {
+ Dl_info dl_info;
+ if (!dladdr((void*)&__sanitizer_cov_8bit_counters_init, &dl_info)) {
+ return "<Not a shared object>";
+ }
+ return (dl_info.dli_fname);
+}
+
+void
+init(void)
+{
+ if (!&LLVMFuzzerRunDriver) {
+ printf("LLVMFuzzerRunDriver symbol not found. This means "
+ "you had an old version of Clang installed when you built luzer.");
+ /* TODO: exit */
+ assert(NULL);
+ }
+
+ if (strcmp(GetCoverageSymbolsLocation(), GetLibFuzzerSymbolsLocation()) != 0) {
+ fprintf(stderr,
+ "WARNING: Coverage symbols are being provided by a library other than "
+ "libFuzzer. This will result in a broken Lua code coverage and "
+ "severely impacted native extension code coverage. Symbols are coming "
+ "from this library: %s"
+ "\nYou can likely resolve this issue by linking libFuzzer into "
+ "Lua directly, and using `atheris_no_libfuzzer` instead of "
+ "`atheris`. See documentation for details.", GetCoverageSymbolsLocation());
+ }
+}
+
+static void
+sig_handler(int sig)
+{
+ switch (sig) {
+ case SIGINT:
+ exit(0);
+ break;
+ case SIGSEGV:
+ __sanitizer_print_stack_trace();
+ break;
+ }
+}
+
+NO_SANITIZE int
+luaL_mutate(lua_State *L)
+{
+ int index = lua_gettop(L);
+ if (index != 4) {
+ luaL_error(L, "required arguments: data, size, max_size, seed");
+ }
+ lua_getglobal(L, CUSTOM_MUTATOR_FUNC);
+ if (lua_isfunction(L, -1) != 1) {
+ luaL_error(L, "no luzer_custom_mutator is defined");
+ }
+ lua_insert(L, 5);
+ lua_call(L, 6, 1);
+
+ if (lua_isstring(L, -1) != 1) {
+ luaL_error(L, "_mutate() must return a string");
+ }
+
+ return 1;
+}
+
+NO_SANITIZE static int
+luaL_set_custom_mutator(lua_State *L)
+{
+ if (lua_isfunction(L, -1) != 1)
+ luaL_error(L, "custom_mutator is not a Lua function.");
+ lua_setglobal(L, CUSTOM_MUTATOR_FUNC);
+
+ return 0;
+}
+
+NO_SANITIZE static int
+luaL_test_one_input(lua_State *L)
+{
+ lua_getglobal(L, TEST_ONE_INPUT_FUNC);
+ if (lua_isfunction(L, -1) != 1) {
+ lua_settop(L, 0);
+ luaL_error(L, "no luzer_test_one_input is defined");
+ }
+ lua_insert(L, -2);
+ lua_call(L, 1, 1);
+
+ int rc = 0;
+ if (lua_isnumber(L, 1) == 1)
+ rc = lua_tonumber(L, 1);
+ lua_settop(L, 0);
+
+ return rc;
+}
+
+NO_SANITIZE int
+TestOneInput(const uint8_t* data, size_t size) {
+ const counter_and_pc_table_range alloc = allocate_counters_and_pcs();
+ if (alloc.counters_start && alloc.counters_end) {
+ __sanitizer_cov_8bit_counters_init(alloc.counters_start,
+ alloc.counters_end);
+ }
+ if (alloc.pctable_start && alloc.pctable_end) {
+ __sanitizer_cov_pcs_init(alloc.pctable_start, alloc.pctable_end);
+ }
+
+ lua_State *L = get_global_lua_state();
+ char *buf = calloc(size + 1, sizeof(char));
+ memcpy(buf, data, size);
+ buf[size] = '\0';
+ lua_pushlstring(L, buf, size);
+ int rc = luaL_test_one_input(L);
+ free(buf);
+
+ return rc;
+}
+
+NO_SANITIZE static int
+luaL_cleanup(lua_State *L)
+{
+ lua_sethook(L, debug_hook, 0, 0);
+ lua_pushnil(L);
+ lua_setglobal(L, TEST_ONE_INPUT_FUNC);
+ lua_pushnil(L);
+ lua_setglobal(L, DEBUG_HOOK_FUNC);
+ lua_pushnil(L);
+ lua_setglobal(L, CUSTOM_MUTATOR_FUNC);
+ return 0;
+}
+
+NO_SANITIZE static int
+search_module_path(char *so_path, size_t len) {
+ char *lua_cpath = getenv("LUA_CPATH");
+ if (!lua_cpath)
+ lua_cpath = "./";
+ int rc = -1;
+ char *cpath = NULL;
+ while ((cpath = strsep(&lua_cpath, ";")) != NULL) {
+ const char *dir = dirname(cpath);
+ snprintf(so_path, len, "%s/%s", dir, CUSTOM_MUTATOR_LIB);
+ if (access(so_path, F_OK) == 0) {
+ rc = 0;
+ break;
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * We couldn't define custom mutator function in a compile-time,
+ * so we define it in runtime - when user has specified a Lua
+ * function with custom mutator. LibFuzzer uses custom mutator
+ * defined by user when a function LLVMFuzzerCustomMutator has been defined.
+ * We define that function in a shared library and preload it when
+ * user defines a Lua function with custom mutator.
+ * LLVMFuzzerCustomMutator executes a Lua function, mutates portion of data
+ * and returns it back to LibFuzzer. Shared library is located
+ * at the same directory where the main shared library with luzer's
+ * implementation is placed. To search it's location we search
+ * shared library CUSTOM_MUTATOR_LIB in directories listed in
+ * environment variable LUA_CPATH.
+ */
+NO_SANITIZE static int
+load_custom_mutator_lib(void) {
+ char *so_path = calloc(PATH_MAX, sizeof(char));
+ 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;
+ }
+ void *custom_mutator = dlsym(custom_mutator_lib, "LLVMFuzzerCustomMutator");
+ if (!custom_mutator) {
+ DEBUG_PRINT("dlsym");
+ return -1;
+ }
+ rc = dlclose(custom_mutator_lib);
+ if (rc) {
+ DEBUG_PRINT("dlclose");
+ return -1;
+ }
+ return 0;
+}
+
+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*));
+ if (!argv)
+ luaL_error(L, "not enough memory");
+ const char *corpus_path = NULL;
+ 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 {
+ corpus_path = strdup(value);
+ }
+ lua_pop(L, 1);
+ argv = argvp;
+ }
+ if (corpus_path) {
+ argv[argc] = (char*)corpus_path;
+ argc++;
+ }
+ if (argc == 0) {
+ argv[argc] = "";
+ argc++;
+ }
+ argv[argc] = NULL;
+ lua_pop(L, 1);
+
+#ifdef DEBUG
+ char **p = argv;
+ while(*p++) {
+ if (*p)
+ DEBUG_PRINT("libFuzzer arg - '%s'\n", *p);
+ }
+#endif /* DEBUG */
+
+ /* Processing a function with custom mutator. */
+ if (!lua_isnil(L, -1) && (lua_isfunction(L, -1) == 1)) {
+ if (load_custom_mutator_lib())
+ 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) {
+ luaL_error(L, "test_one_input is not a Lua function");
+ }
+ lua_setglobal(L, TEST_ONE_INPUT_FUNC);
+
+ /**
+ * Hook is called when the Lua interpreter calls a function and when the
+ * interpreter is about to start the execution of a new line of code, or
+ * when it jumps back in the code (even to the same line).
+ * https://www.lua.org/pil/23.2.html
+ */
+ lua_sethook(L, debug_hook, LUA_MASKCALL | LUA_MASKLINE, 0);
+ lua_pushboolean(L, 1);
+
+ struct sigaction act;
+ act.sa_handler = sig_handler;
+ sigaction(SIGINT, &act, NULL);
+ sigaction(SIGSEGV, &act, NULL);
+
+ lua_getglobal(L, TEST_ONE_INPUT_FUNC);
+ if (lua_isfunction(L, -1) != 1) {
+ luaL_error(L, "test_one_input is not defined");
+ }
+ lua_pop(L, -1);
+
+ set_global_lua_state(L);
+ int rc = LLVMFuzzerRunDriver(&argc, &argv, &TestOneInput);
+ luaL_cleanup(L);
+
+ lua_pushnumber(L, rc);
+
+ return 1;
+}
+
+static const struct luaL_Reg Module[] = {
+ { "Fuzz", luaL_fuzz },
+ { "FuzzedDataProvider", luaL_fuzzed_data_provider },
+ { "_set_custom_mutator", luaL_set_custom_mutator },
+ { "_mutate", luaL_mutate },
+ { NULL, NULL }
+};
+
+int luaopen_luzer(lua_State *L)
+{
+ init();
+
+#if LUA_VERSION_NUM == 501
+ luaL_register(L, "luzer", Module);
+#else
+ luaL_newlib(L, Module);
+#endif
+ lua_pushliteral(L, "_VERSION");
+ lua_pushstring(L, luzer_version_string());
+ lua_rawset(L, -3);
+
+ lua_pushliteral(L, "_LLVM_VERSION");
+ lua_pushstring(L, llvm_version_string());
+ lua_rawset(L, -3);
+
+ lua_pushliteral(L, "_LUA_VERSION");
+ lua_pushstring(L, LUA_RELEASE);
+ lua_rawset(L, -3);
+
+ return 1;
+}
blob - /dev/null
blob + 234669009442b8c3ab58624ebec62d59a2b84cdd (mode 644)
--- /dev/null
+++ luzer/luzer.h
+#ifndef LUZER_MACROS_H_
+#define LUZER_MACROS_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+lua_State *get_global_lua_state();
+int luaL_mutate(lua_State *L);
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif // LUZER_MACROS_H_
blob - /dev/null
blob + 984ed6829d6733ae2bf7b3b9d942c865339bfbc9 (mode 644)
--- /dev/null
+++ luzer/macros.h
+#ifndef LUZER_MACROS_H_
+#define LUZER_MACROS_H_
+
+#include <assert.h>
+#include <stdbool.h>
+
+#ifdef DEBUG
+#define DEBUG_PRINT(...) do{ fprintf( stderr, __VA_ARGS__ ); } while( false )
+#else
+#define DEBUG_PRINT(...) do{ } while ( false )
+#endif /* DEBUG */
+
+#define MIN(X, Y) (((X) < (Y)) ? (X) : (Y))
+#define MAX(X, Y) (((X) > (Y)) ? (X) : (Y))
+
+/**
+ * If control flow reaches the point of the unreachable(), the program is
+ * undefined. It is useful in situations where the compiler cannot deduce
+ * the unreachability of the code.
+ */
+#if __has_builtin(__builtin_unreachable) || defined(__GNUC__)
+# define unreachable() (assert(0), __builtin_unreachable())
+#else
+# define unreachable() (assert(0))
+#endif
+
+#define NO_SANITIZE_ADDRESS __attribute__((no_sanitize_address))
+
+#ifdef __has_attribute
+#if __has_attribute(no_sanitize)
+#define NO_SANITIZE_MEMORY __attribute__((no_sanitize("memory")))
+#else
+#define NO_SANITIZE_MEMORY
+#endif // __has_attribute(no_sanitize)
+#else
+#define NO_SANITIZE_MEMORY
+#endif // __has_attribute
+
+#define NO_SANITIZE NO_SANITIZE_ADDRESS NO_SANITIZE_MEMORY
+
+#endif // LUZER_MACROS_H_
blob - /dev/null
blob + f6587371ddb17cb67ea6740fce29f8dd5494d7cb (mode 644)
--- /dev/null
+++ luzer/tests/CMakeLists.txt
+set(LUA_CPATH "\;${PROJECT_BINARY_DIR}/luzer/?.so\;")
+
+add_test(
+ NAME luzer_unit_test
+ COMMAND ${LUA_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test_unit.lua
+ WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+)
+set_tests_properties(luzer_unit_test PROPERTIES
+ ENVIRONMENT "LUA_CPATH='${LUA_CPATH}'"
+)
+
+add_test(
+ NAME luzer_e2e_test
+ COMMAND ${LUA_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test_e2e.lua
+ WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+)
+set_tests_properties(luzer_e2e_test PROPERTIES
+ ENVIRONMENT "LUA_CPATH='${LUA_CPATH}'"
+ PASS_REGULAR_EXPRESSION "test_e2e.lua:7: assert has triggered"
+)
+
+add_test(
+ NAME luzer_options_test
+ COMMAND ${LUA_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test_options.lua
+ WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+)
+set_tests_properties(luzer_options_test PROPERTIES
+ ENVIRONMENT "LUA_CPATH='${LUA_CPATH}'"
+ PASS_REGULAR_EXPRESSION "ERROR: The required directory \"undefined\" does not exist"
+)
blob - /dev/null
blob + db7e5fdc3775e4b03159084ccb4860c634ea6eb1 (mode 644)
--- /dev/null
+++ luzer/tests/test_e2e.lua
+local luzer = require("luzer")
+
+local function TestOneInput(buf)
+ local fdp = luzer.FuzzedDataProvider(buf)
+ local str = fdp:consume_string(1)
+ if str == "c" then
+ assert(nil, "assert has triggered")
+ end
+ return
+end
+
+local opts = {
+ max_len = 4096,
+}
+luzer.Fuzz(TestOneInput, nil, opts)
blob - /dev/null
blob + c6b81656386b76e4e03bdd3539f93333b807d054 (mode 644)
--- /dev/null
+++ luzer/tests/test_options.lua
+local luzer = require("luzer")
+
+local args = {
+ max_len = 1024,
+ print_pcs = 1,
+ corpus = "undefined",
+ max_total_time = 60,
+ print_final_stats = 1,
+}
+luzer.Fuzz(function() end, nil, args)
blob - /dev/null
blob + 53ca0019ccba07a3bc6ec9483868090c029a6d77 (mode 644)
--- /dev/null
+++ luzer/tests/test_unit.lua
+local luzer = require("luzer")
+
+local function trace(_, line)
+ local s = debug.getinfo(2).short_src
+ print(s .. ":" .. line)
+end
+
+debug.sethook(trace, "l")
+
+-- luzer._VERSION
+assert(type(luzer._VERSION) == "string")
+assert(type(luzer._LLVM_VERSION) == "string")
+assert(type(luzer._LUA_VERSION) == "string")
+
+local ok
+local err
+local fdp
+local res
+
+-- luzer.FuzzedDataProvider()
+assert(type(luzer.FuzzedDataProvider) == "function")
+ok, err = pcall(luzer.FuzzedDataProvider)
+assert(ok == false)
+assert(err ~= nil)
+fdp = luzer.FuzzedDataProvider(string.rep('A', 1024))
+assert(type(fdp) == "userdata")
+
+-- luzer.FuzzedDataProvider.remaining_bytes()
+fdp = luzer.FuzzedDataProvider("A")
+assert(type(fdp.remaining_bytes) == "function")
+res = fdp:remaining_bytes()
+assert(type(res) == "number")
+assert(res == 1)
+fdp = luzer.FuzzedDataProvider("ABC")
+res = fdp:remaining_bytes()
+assert(type(res) == "number")
+assert(res == 3)
+
+-- luzer.FuzzedDataProvider.consume_string()
+fdp = luzer.FuzzedDataProvider("ABCD")
+assert(type(fdp.consume_string) == "function")
+
+assert(fdp:remaining_bytes() == 4)
+res = fdp:consume_string(2)
+assert(type(res) == "string")
+assert(res == "AB")
+assert(fdp:remaining_bytes() == 2)
+res = fdp:consume_string(2)
+assert(type(res) == "string")
+assert(res == "CD")
+res = fdp:consume_string(2)
+assert(fdp:remaining_bytes() == 0)
+assert(type(res) == "string")
+assert(res == "")
+
+ok = pcall(fdp.consume_string)
+assert(ok == false)
+assert(err ~= nil)
+
+-- luzer.FuzzedDataProvider.consume_strings()
+fdp = luzer.FuzzedDataProvider("ABCDEF")
+assert(type(fdp.consume_strings) == "function")
+
+res = fdp:consume_strings(2, 3)
+assert(type(res) == "table")
+assert(#res == 2, #res)
+assert(fdp:remaining_bytes() == 0)
+
+ok = pcall(fdp.consume_strings)
+assert(ok == false)
+assert(err ~= nil)
+
+-- luzer.FuzzedDataProvider.consume_boolean()
+fdp = luzer.FuzzedDataProvider("AB")
+assert(type(fdp.consume_boolean) == "function")
+
+assert(fdp:remaining_bytes() == 2)
+res = fdp:consume_boolean()
+assert(type(res) == "boolean")
+assert(fdp:remaining_bytes() == 1)
+res = fdp:consume_boolean()
+assert(type(res) == "boolean")
+assert(fdp:remaining_bytes() == 0)
+res = fdp:consume_boolean()
+assert(type(res) == "boolean")
+assert(res == false)
+assert(fdp:remaining_bytes() == 0)
+res = fdp:consume_boolean()
+assert(type(res) == "boolean")
+assert(res == false)
+
+-- luzer.FuzzedDataProvider.consume_booleans()
+fdp = luzer.FuzzedDataProvider("AB")
+assert(type(fdp.consume_booleans) == "function")
+
+res = fdp:consume_booleans(2)
+assert(type(res) == "table")
+assert(type(res[1]) == "boolean")
+assert(type(res[2]) == "boolean")
+assert(fdp:remaining_bytes() == 0)
+res = fdp:consume_booleans(2)
+assert(type(res) == "table")
+assert(res[1] == false)
+assert(res[2] == false)
+
+ok = pcall(fdp.consume_booleans)
+assert(ok == false)
+
+-- luzer.FuzzedDataProvider.consume_number()
+fdp = luzer.FuzzedDataProvider("AB")
+assert(type(fdp.consume_number) == "function")
+
+res = fdp:consume_number(1, 10)
+assert(type(res) == "number")
+assert(res >= 1)
+assert(res <= 10, res)
+
+ok, err = pcall(fdp.consume_number)
+assert(ok == false)
+assert(err ~= nil)
+
+-- luzer.FuzzedDataProvider.consume_numbers()
+fdp = luzer.FuzzedDataProvider("ABCDEF")
+assert(type(fdp.consume_numbers) == "function")
+
+res = fdp:consume_numbers(2, 1, 3)
+assert(type(res) == "table")
+assert(type(res[1]) == "number")
+assert(type(res[2]) == "number")
+assert(res[3] == nil, res[3])
+
+ok, err = pcall(fdp.consume_numbers, fdp)
+assert(ok == false)
+assert(err ~= nil)
+
+-- luzer.FuzzedDataProvider.consume_integer()
+fdp = luzer.FuzzedDataProvider("AB")
+assert(type(fdp.consume_integer) == "function")
+
+res = fdp:consume_integer(10, 20)
+assert(type(res) == "number")
+assert(res >= 10)
+assert(res <= 20)
+
+ok, err = pcall(fdp.consume_integer)
+assert(ok == false)
+assert(err ~= nil)
+
+-- luzer.FuzzedDataProvider.consume_integers()
+fdp = luzer.FuzzedDataProvider("AB")
+assert(type(fdp.consume_integers) == "function")
+
+local min = 1
+local max = 6
+res = fdp:consume_integers(1, min, max)
+assert(type(res) == "table")
+assert(type(res[1]) == "number")
+assert(res[1] <= max, res[1])
+assert(res[1] >= min, res[1])
+assert(res[2] == nil)
+
+ok, err = pcall(fdp.consume_integers)
+assert(ok == false)
+assert(err ~= nil)
+
+-- luzer.FuzzedDataProvider.consume_probability()
+fdp = luzer.FuzzedDataProvider("AB")
+assert(type(fdp.consume_probability) == "function")
+
+local p1 = fdp:consume_probability()
+local p2 = fdp:consume_probability()
+assert(type(p1) == "number")
+assert(type(p2) == "number")
+assert(p1 >= 0 and p2 >= 0)
+assert(p1 <= 1 and p2 <= 1)
+assert(p1 ~= p2)
+
+local function custom_mutator(data, size, max_size, seed)
+ assert(type(data) == "string")
+ assert(type(size) == "number")
+ assert(size == #data)
+ assert(type(max_size) == "number")
+ assert(max_size == size)
+ assert(type(seed) == "number")
+ return data
+end
+
+-- luzer._set_custom_mutator()
+assert(luzer_custom_mutator == nil)
+luzer._set_custom_mutator(custom_mutator)
+assert(luzer_custom_mutator ~= nil)
+assert(type(luzer_custom_mutator) == "function")
+local buf = "data"
+assert(luzer_custom_mutator(buf, #buf, #buf, math.random(1, 10)) == buf)
+luzer_custom_mutator = nil -- Clean up.
+
+-- luzer._mutate()
+luzer._set_custom_mutator(custom_mutator)
+assert(luzer_custom_mutator ~= nil)
+-- luzer._mutate(buf, #buf, #buf, math.random(1, 10)) -- TODO
+luzer_custom_mutator = nil -- Clean up.
+
+print("Success!")
blob - /dev/null
blob + 793c5d733ef0a06be24d96eae9e628f590742ee3 (mode 644)
--- /dev/null
+++ luzer/tracer.c
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright 2022-2023, Sergey Bronnikov
+ */
+
+/**
+ * SanitizerCoverage
+ * https://clang.llvm.org/docs/SanitizerCoverage.html
+ *
+ * SanCov: Above and Below the Sanitizer Interface
+ * https://calabi-yau.space/blog/sanitizer-coverage-interface.html
+ *
+ * Jazzer:
+ * jazzer/src/main/native/com/code_intelligence/jazzer/driver/coverage_tracker.cpp
+ * jazzer/src/main/native/com/code_intelligence/jazzer/jazzer_preload.c
+ *
+ * Atheris:
+ * atheris/src/native/core.cc
+ * atheris/src/native/counters.cc
+ */
+
+#include <lua.h>
+#include <stdint.h>
+#include <string.h> /* strlen */
+
+#include "counters.h"
+#include "macros.h"
+
+/**
+ * From afl-python
+ * https://github.com/jwilk/python-afl/blob/8df6bfefac5de78761254bf5d7724e0a52d254f5/afl.pyx#L74-L87
+ */
+#define LHASH_INIT 0x811C9DC5
+#define LHASH_MAGIC_MULT 0x01000193
+#define LHASH_NEXT(x) h = ((h ^ (unsigned char)(x)) * LHASH_MAGIC_MULT)
+
+NO_SANITIZE void
+_trace_branch(uint64_t idx)
+{
+ increment_counter(idx);
+}
+
+static inline unsigned int lhash(const char *key, size_t offset)
+{
+ const char *const last = &key[strlen(key) - 1];
+ uint32_t h = LHASH_INIT;
+ while (key <= last) LHASH_NEXT(*key++);
+ for (; offset != 0; offset >>= 8) LHASH_NEXT(offset);
+ return h;
+}
+
+/**
+ * luzer gathers coverage using a debug hook, and patches coroutine
+ * library to set it on created threads when under standard Lua, where each
+ * coroutine has its own hook. If a coroutine is created using Lua C API
+ * or before the monkey-patching, this wrapper should be applied to the
+ * main function of the coroutine. Under LuaJIT this function is redundant,
+ * as there is only one, global debug hook.
+ *
+ * https://github.com/lunarmodules/luacov/blob/master/src/luacov/runner.lua#L102-L117
+ * https://github.com/lunarmodules/luacov/blob/78f3d5058c65f9712e6c50a0072ad8160db4d00e/src/luacov/runner.lua#L439-L450
+ */
+void debug_hook(lua_State *L, lua_Debug *ar)
+{
+ lua_getinfo(L, "Sln", ar);
+ if (ar && ar->source && ar->currentline) {
+ const unsigned int new_location = lhash(ar->source, ar->currentline);
+ _trace_branch(new_location);
+ }
+}
blob - /dev/null
blob + 232d4a3b20e14ecab3a4d55c388835d037ed36f8 (mode 644)
--- /dev/null
+++ luzer/tracer.h
+#ifndef LUZER_TRACER_H_
+#define LUZER_TRACER_H_
+
+void debug_hook(lua_State *L, lua_Debug *ar);
+
+#endif // LUZER_TRACER_H_
blob - /dev/null
blob + dc593020abaf976a4eed9982743a9b901a2bfb35 (mode 644)
--- /dev/null
+++ luzer/version.c
+const char *llvm_version_string(void) {
+ return "@LLVM_VERSION@";
+}
+
+const char *luzer_version_string(void) {
+ return "@CMAKE_PROJECT_VERSION@";
+}
blob - /dev/null
blob + 5023ec08bd491d659aa495c4531f8596419eb9b0 (mode 644)
--- /dev/null
+++ luzer/version.h
+#ifndef LUZER_VERSION_H_
+#define LUZER_VERSION_H_
+
+const char *llvm_version_string(void);
+const char *luzer_version_string(void);
+
+#endif /* LUZER_VERSION_H_ */
blob - /dev/null
blob + 6caabeca3f436beb8c1ffe5fb8e5df74e8c0b854 (mode 644)
--- /dev/null
+++ luzer-scm-1.rockspec
+package = "luzer"
+version = "scm-1"
+source = {
+ url = "git+https://github.com/ligurio/luzer",
+ branch = "master",
+}
+
+description = {
+ summary = "A coverage-guided, native Lua fuzzer",
+ detailed = [[ luzer is a coverage-guided Lua fuzzing engine. It supports
+fuzzing of Lua code, but also C extensions written for Lua. Luzer is based off
+of libFuzzer. When fuzzing native code, luzer can be used in combination with
+Address Sanitizer or Undefined Behavior Sanitizer to catch extra bugs. ]],
+ homepage = "https://github.com/ligurio/luzer",
+ maintainer = "Sergey Bronnikov <estetus@gmail.com>",
+ license = "ISC",
+}
+
+dependencies = {
+ "lua >= 5.1",
+}
+
+build = {
+ type = "cmake",
+ -- https://github.com/luarocks/luarocks/wiki/Config-file-format#variables
+ variables = {
+ CMAKE_LUADIR = "$(LUADIR)",
+ CMAKE_LIBDIR = "$(LIBDIR)",
+ CMAKE_BUILD_TYPE = "RelWithDebInfo",
+ CMAKE_C_COMPILER = "clang",
+ CMAKE_CXX_COMPILER = "clang++",
+ },
+}