Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 158 additions & 0 deletions docs/getting-started/new-project-guide/lua_lang.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
---
layout: default
title: Integrating a Lua project
parent: Setting up a new project
grand_parent: Getting started
nav_order: 4
permalink: /getting-started/new-project-guide/lua-lang/
---

# Integrating a Lua project
{: .no_toc}

- TOC
{:toc}
---

The process of integrating a project written in Lua with OSS-Fuzz is very
similar to the general [Setting up a new project]({{ site.baseurl
}}/getting-started/new-project-guide/) process. The key specifics of
integrating a Lua project are outlined below.

## luzer

Lua fuzzing in OSS-Fuzz is powered by
[luzer](https://github.com/ligurio/luzer), which is installed during the build
step. As luzer operates directly on the Lua source code level, it can be
applied to any project written in a language that can be transpiled into Lua
such as [MoonScript](https://moonscript.org/),
[TypeScriptToLua](https://typescripttolua.github.io/),
[Fennel](https://fennel-lang.org/), and [Urn](https://urn-lang.com/). More
information on how luzer fuzz targets looks like can be found in its [README's
Quickstart section](https://github.com/ligurio/luzer#quickstart).

## Project files

### Example project

We recommend viewing
[lua-example](https://github.com/google/oss-fuzz/tree/master/projects/lua-example)
as an example of a simple Lua fuzzing project. This example also demonstrates
how to use luzer's fuzzed data provider.

### project.yaml

The `language` attribute must be specified as follows:

```yaml
language: lua
```

The only supported fuzzing engine is libFuzzer (`libfuzzer`). The supported
sanitizers are AddressSanitizer (`address`) and
UndefinedBehaviorSanitizer (`undefined`). These must be explicitly specified.
(`none`) is the default sanitizer for Lua projects, so setting it up in
`project.yaml` is optional.

```yaml
fuzzing_engines:
- libfuzzer
sanitizers:
- none
```

### Dockerfile

The Dockerfile should start by `FROM gcr.io/oss-fuzz-base/base-builder`.

The OSS-Fuzz base Docker images come without any pre-installed components
required for Lua fuzzing. Apart from that, you should usually need to install
Lua runtime, luzer module, clone the project, set a `WORKDIR`, and copy any
necessary files, or install any project-specific dependencies here as you normally would.

### Fuzzers

In the simplest case, every fuzzer consists of a single Lua file that defines
a function `TestOneInput` and executes a function named `luzer.Fuzz()`.
An example fuzz target could thus be a file `fuzz_basic.lua` with contents:

```lua
local parser = require("src.luacheck.parser")
local decoder = require("luacheck.decoder")
local luzer = require("luzer")

local function TestOneInput(buf)
parser.parse(decoder.decode(buf))
end

local args = {
print_final_stats = 1,
}
luzer.Fuzz(TestOneInput, nil, args)
```

### build.sh

The OSS-Fuzz base docker image for Lua comes with the [`compile_lua_fuzzer`
script](https://github.com/google/oss-fuzz/blob/master/infra/base-images/base-builder/compile_lua_fuzzer)
preinstalled. In `build.sh`, you should install dependencies for your project,
and if necessary compile the code into Lua. Then, you can use the script to
build the fuzzers. The script ensures that
[luzer](https://luarocks.org/modules/ligurio/luzer) is installed so that its
CLI can be used to execute your fuzz tests. It also generates a wrapper script
that can be used as a drop-in replacement for libFuzzer. This means that the
generated script accepts the same command line flags for libFuzzer. Under the
hood these flags are simply forwarded to the libFuzzer native addon used by
luzer.

A usage example from the lua-example project is

```shell
compile_lua_fuzzer lua lua-example fuzz_basic.lua
```

Arguments are:

* a Lua runtime name
* relative path of the project in the $SRC directory
* relative path to the fuzz test inside the project

The [lua-example](https://github.com/google/oss-fuzz/blob/master/projects/lua-example/build.sh)
project contains an example of a `build.sh` for a Lua projects.

## FuzzedDataProvider

luzer provides a `FuzzedDataProvider` that can simplify the task of creating a
fuzz target by translating the raw input bytes received from the fuzzer into
useful primitive Lua types. Its functionality is similar to
`FuzzedDataProviders` available in other languages, such as
[Java](https://codeintelligencetesting.github.io/jazzer-docs/jazzer-api/com/code_intelligence/jazzer/api/FuzzedDataProvider.html) and
[C++](https://github.com/google/fuzzing/blob/master/docs/split-inputs.md).

A fuzz target using the `FuzzedDataProvider` would look as follows:

```lua
local luzer = require("luzer")

local function TestOneInput(buf)
local fdp = luzer.FuzzedDataProvider(buf)
local str = fdp:consume_string(4)

local b = {}
str:gsub(".", function(c) table.insert(b, c) end)
local count = 0
if b[1] == "o" then count = count + 1 end
if b[2] == "o" then count = count + 1 end
if b[3] == "p" then count = count + 1 end
if b[4] == "s" then count = count + 1 end

if count == 4 then assert(nil) end
end

local args = {
only_ascii = 1,
print_pcs = 1,
}

luzer.Fuzz(TestOneInput, nil, args)
```
1 change: 1 addition & 0 deletions docs/getting-started/new_project_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ Programming language the project is written in. Values you can specify include:
* [`jvm` (Java, Kotlin, Scala and other JVM-based languages)]({{ site.baseurl }}//getting-started/new-project-guide/jvm-lang/)
* [`swift`]({{ site.baseurl }}//getting-started/new-project-guide/swift-lang/)
* [`javascript`]({{ site.baseurl }}//getting-started/new-project-guide/javascript-lang/)
* [`lua`]({{ site.baseurl }}//getting-started/new-project-guide/lua-lang/)

### primary_contact, auto_ccs {#primary}
The primary contact and list of other contacts to be CCed. Each person listed gets access to ClusterFuzz, including crash reports and fuzzer statistics, and are auto-cced on new bugs filed in the OSS-Fuzz
Expand Down
2 changes: 2 additions & 0 deletions infra/base-images/base-runner/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ ENV PATH="$PATH:/usr/local/rvm/rubies/ruby-3.3.1/bin"
ENV GEM_HOME="$OUT/fuzz-gem"
ENV GEM_PATH="/install/ruzzy"

RUN apt-get update && apt-get install -y luarocks

# Do this last to make developing these files easier/faster due to caching.
COPY bad_build_check \
coverage \
Expand Down
9 changes: 9 additions & 0 deletions infra/base-images/base-runner/bad_build_check
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,11 @@ function check_mixed_sanitizers {
return 0
fi

if [ "${FUZZING_LANGUAGE:-}" = "lua" ]; then
# Sanitizer runtime is loaded via LD_PRELOAD, so this check does not apply.
return 0
fi

# For fuzztest fuzzers point to the binary instead of launcher script.
if [[ $FUZZER == *"@"* ]]; then
FUZZER=(${FUZZER//@/ }[0])
Expand Down Expand Up @@ -426,6 +431,10 @@ function check_architecture {
FUZZER=${FUZZER}.pkg
fi

if [ "${FUZZING_LANGUAGE:-}" = "lua" ]; then
return 0;
fi

# For fuzztest fuzzers point to the binary instead of launcher script.
if [[ $FUZZER == *"@"* ]]; then
FUZZER=(${FUZZER//@/ }[0])
Expand Down
1 change: 1 addition & 0 deletions infra/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
'go',
'javascript',
'jvm',
'lua',
'python',
'rust',
'swift',
Expand Down
35 changes: 35 additions & 0 deletions projects/lua-example/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Copyright 2023-2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
################################################################################

FROM gcr.io/oss-fuzz-base/base-builder

RUN apt-get update && apt-get install -y build-essential make

COPY build.sh $SRC/
COPY compile_lua_fuzzer $SRC/
COPY luzer-scm-1.rockspec $SRC/

# For real projects, you would clone your repo in the next step.
RUN mkdir -p $SRC/example

# Ideally, you have already configured fuzz tests in your repo so
# that they run (in luzer regression mode) as part of unit testing.
# Keeping the fuzz tests in sync with the source code ensures that
# they are adjusted continue to work after code changes. Here,
# we copy them into the example project directory.
COPY example_basic.lua $SRC/example/fuzz_basic.lua

WORKDIR $SRC/example
36 changes: 36 additions & 0 deletions projects/lua-example/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#!/bin/bash -eu
# Copyright 2023-2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
################################################################################

# Install dependencies.
apt install -y liblua5.1-0 liblua5.1-0-dev lua5.1 cmake luarocks \
clang-18 libclang-rt-18-dev libc++-18-dev

# Install Lua dependencies.
# luarocks install --tree=lua_modules --server=https://luarocks.org/dev luzer
# TODO: A custom rockspec is used because custom branch is required,
# see https://github.com/ligurio/luzer/issues/63.
luarocks install --tree=lua_modules $SRC/luzer-scm-1.rockspec
zip -r lua_modules.zip lua_modules

LUA_RUNTIME_NAME=lua

# Build fuzzers.
$SRC/compile_lua_fuzzer lua-example $LUA_RUNTIME_NAME fuzz_basic.lua

cp fuzz_basic.lua "$OUT/"
cp /usr/bin/lua5.1 "$OUT/$LUA_RUNTIME_NAME"
cp lua_modules.zip "$OUT/"
38 changes: 38 additions & 0 deletions projects/lua-example/compile_lua_fuzzer
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/bin/bash -eu
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
################################################################################

project=$1
# Lua runtime name.
lua_runtime=$2
# Path the fuzz target source file relative to the project's root.
fuzz_target=$3

fuzzer_basename=$(basename -s .lua "$fuzz_target")

# Create an execution wrapper that executes luzer with the correct
# arguments.
echo "#!/bin/bash

# LLVMFuzzerTestOneInput so that the wrapper script is recognized
# as a fuzz target for 'check_build'.
project_dir=\$(dirname \"\$0\")
eval \$(luarocks --tree lua_modules path)
LD_PRELOAD=\$project_dir/lua_modules/lib/lua/5.1/libfuzzer_with_asan.so \
ASAN_OPTIONS=\$ASAN_OPTIONS:symbolize=1:external_symbolizer_path=\$project_dir/llvm-symbolizer:detect_leaks=0 \
\$project_dir/$lua_runtime \$project_dir/$fuzz_target \$@" > "$OUT/$fuzzer_basename"

chmod +x "$OUT/$fuzzer_basename"
23 changes: 23 additions & 0 deletions projects/lua-example/example_basic.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
local luzer = require("luzer")

local function TestOneInput(buf)
local fdp = luzer.FuzzedDataProvider(buf)
local str = fdp:consume_string(4)

local b = {}
str:gsub(".", function(c) table.insert(b, c) end)
local count = 0
if b[1] == "o" then count = count + 1 end
if b[2] == "o" then count = count + 1 end
if b[3] == "p" then count = count + 1 end
if b[4] == "s" then count = count + 1 end

if count == 4 then assert(nil) end
end

local args = {
only_ascii = 1,
print_pcs = 1,
}

luzer.Fuzz(TestOneInput, nil, args)
35 changes: 35 additions & 0 deletions projects/lua-example/luzer-scm-1.rockspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package = "luzer"
version = "scm-1"
source = {
url = "git+https://github.com/ligurio/luzer",
branch = "ligurio/oss-fuzz",
}

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 <[email protected]>",
license = "ISC",
}

dependencies = {
"lua >= 5.1",
}

build = {
type = "cmake",
-- luacheck: push no max_comment_line_length
-- https://github.com/luarocks/luarocks/blob/7ed653f010671b3a7245be9adcc70068c049ef68/docs/config_file_format.md#config-file-format
-- luacheck: pop
variables = {
CMAKE_LUADIR = "$(LUADIR)",
CMAKE_LIBDIR = "$(LIBDIR)",
CMAKE_BUILD_TYPE = "RelWithDebInfo",
CMAKE_C_COMPILER = "clang-18",
CMAKE_CXX_COMPILER = "clang++-18",
},
}
10 changes: 10 additions & 0 deletions projects/lua-example/project.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
homepage: https://github.com/ligurio/luzer
language: lua
main_repo: https://github.com/ligurio/luzer
fuzzing_engines:
- afl
- libfuzzer
sanitizers:
- none
vendor_ccs:
- [email protected]
Loading