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
157 changes: 157 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,157 @@
---
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 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 build
or install a 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 it
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 fuzz_basic.lua
```

Arguments are:

* a Lua runtime name
* a path to the fuzz test inside the OSS Fuzz project directory

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
1 change: 1 addition & 0 deletions infra/base-images/base-runner/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ COPY --from=base-ruby /usr/local/bin/gem /usr/local/bin/gem
COPY --from=base-ruby /usr/local/lib/ruby /usr/local/lib/ruby
COPY --from=base-ruby /usr/local/include/ruby-3.3.0 /usr/local/include/ruby-3.3.0

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 \
Expand Down
12 changes: 12 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 All @@ -349,6 +354,9 @@ function check_mixed_sanitizers {
echo "UNSUPPORTED ARCHITECTURE"
exit 1
fi

egrep luarocks $FUZZER && return 0;

local ASAN_CALLS=$(objdump -dC $FUZZER | egrep "${CALL_INSN}__asan" -c)
local DFSAN_CALLS=$(objdump -dC $FUZZER | egrep "${CALL_INSN}__dfsan" -c)
local MSAN_CALLS=$(objdump -dC $FUZZER | egrep "${CALL_INSN}__msan" -c)
Expand Down Expand Up @@ -426,6 +434,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
2 changes: 2 additions & 0 deletions infra/base-images/base-runner/ubuntu-20-04.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@ COPY --from=base-ruby /usr/local/bin/gem /usr/local/bin/gem
COPY --from=base-ruby /usr/local/lib/ruby /usr/local/lib/ruby
COPY --from=base-ruby /usr/local/include/ruby-3.3.0 /usr/local/include/ruby-3.3.0

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
2 changes: 2 additions & 0 deletions infra/base-images/base-runner/ubuntu-24-04.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@ COPY --from=base-ruby /usr/local/bin/gem /usr/local/bin/gem
COPY --from=base-ruby /usr/local/lib/ruby /usr/local/lib/ruby
COPY --from=base-ruby /usr/local/include/ruby-3.3.0 /usr/local/include/ruby-3.3.0

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
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
39 changes: 39 additions & 0 deletions projects/lua-example/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/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.
#
################################################################################

# Install dependencies:
# 1. The libraries are required for building luzer module.
# 2. PUC Rio Lua executable, required for running tests itself.
# 3. The Lua package manager.
apt install -y cmake liblua5.1-0 liblua5.1-0-dev lua5.1 luarocks

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

LUA_RUNTIME_NAME=lua

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

cp fuzz_basic.lua "$OUT/"
cp /usr/bin/lua5.1 "$OUT/$LUA_RUNTIME_NAME"
cp -R lua_modules "$OUT/"
37 changes: 37 additions & 0 deletions projects/lua-example/compile_lua_fuzzer
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#!/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.
#
################################################################################

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

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"
39 changes: 39 additions & 0 deletions projects/lua-example/example_basic.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
-- 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.

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)
Loading
Loading