Skip to content

Commit 27cbbac

Browse files
committed
Support integration with luzer
The patch enables using luzer for fuzzing Lua projects in OSS-Fuzz. sudo docker build infra/base-images/base-runner sudo docker tag 12a7301c37b0 gcr.io/oss-fuzz-base/base-runner:latest Usage: sudo python infra/helper.py build_fuzzers lua-example sudo python infra/helper.py check_build lua-example fuzz_basic sudo python infra/helper.py run_fuzzer lua-example fuzz_basic TODO - Fix compile_lua_fuzzer-related text in documentation - Describe using luzer for testing builtin Lua C modules - Move projects/lua-example/compile_lua_fuzzer to infra? - Remove projects/lua-example/luzer-scm-1.rockspec Closes #13782
1 parent 783f398 commit 27cbbac

File tree

11 files changed

+363
-0
lines changed

11 files changed

+363
-0
lines changed
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
---
2+
layout: default
3+
title: Integrating a Lua project
4+
parent: Setting up a new project
5+
grand_parent: Getting started
6+
nav_order: 4
7+
permalink: /getting-started/new-project-guide/lua-lang/
8+
---
9+
10+
# Integrating a Lua project
11+
{: .no_toc}
12+
13+
- TOC
14+
{:toc}
15+
---
16+
17+
The process of integrating a project written in Lua with OSS-Fuzz
18+
is similar to the general [Setting up a new project]({{ site.baseurl
19+
}}/getting-started/new-project-guide/) process. The key specifics of
20+
integrating a Lua project are outlined below.
21+
22+
## luzer
23+
24+
Lua fuzzing in OSS-Fuzz is powered by
25+
[luzer](https://github.com/ligurio/luzer), which is installed during the build
26+
step. As luzer operates directly on the Lua source code level, it can be
27+
applied to any project written in a language that can be transpiled into Lua
28+
such as [MoonScript](https://moonscript.org/),
29+
[TypeScriptToLua](https://typescripttolua.github.io/),
30+
[Fennel](https://fennel-lang.org/), and [Urn](https://urn-lang.com/). More
31+
information on how luzer fuzz targets looks like can be found in its [README's
32+
QuickStart section](https://github.com/ligurio/luzer#quickstart).
33+
34+
## Project files
35+
36+
### Example project
37+
38+
We recommend viewing
39+
[lua-example](https://github.com/google/oss-fuzz/tree/master/projects/lua-example)
40+
as an example of a simple Lua fuzzing project. This example also demonstrates
41+
how to use luzer's fuzzed data provider.
42+
43+
### project.yaml
44+
45+
The `language` attribute must be specified as follows:
46+
47+
```yaml
48+
language: lua
49+
```
50+
51+
The only supported fuzzing engine is libFuzzer (`libfuzzer`). The supported
52+
sanitizers are AddressSanitizer (`address`) and
53+
UndefinedBehaviorSanitizer (`undefined`). These must be explicitly specified.
54+
(`none`) is the default sanitizer for Lua projects, so setting it up in
55+
`project.yaml` is optional.
56+
57+
```yaml
58+
fuzzing_engines:
59+
- libfuzzer
60+
sanitizers:
61+
- none
62+
```
63+
64+
### Dockerfile
65+
66+
The Dockerfile should start by `FROM gcr.io/oss-fuzz-base/base-builder`.
67+
68+
The OSS-Fuzz base Docker images come without any pre-installed components
69+
required for Lua fuzzing. Apart from that, you should usually need to build
70+
or install a Lua runtime, luzer module, clone the project, set a `WORKDIR`, and copy any
71+
necessary files, or install any project-specific dependencies here as you normally would.
72+
73+
### Fuzzers
74+
75+
In the simplest case, every fuzzer consists of a single Lua file that defines
76+
a function `TestOneInput` and executes a function named `luzer.Fuzz()`.
77+
An example fuzz target could thus be a file `fuzz_basic.lua` with contents:
78+
79+
```lua
80+
local parser = require("src.luacheck.parser")
81+
local decoder = require("luacheck.decoder")
82+
local luzer = require("luzer")
83+
84+
local function TestOneInput(buf)
85+
parser.parse(decoder.decode(buf))
86+
end
87+
88+
local args = {
89+
print_final_stats = 1,
90+
}
91+
luzer.Fuzz(TestOneInput, nil, args)
92+
```
93+
94+
### build.sh
95+
96+
The OSS-Fuzz base docker image for Lua comes with the [`compile_lua_fuzzer`
97+
script](https://github.com/google/oss-fuzz/blob/master/infra/base-images/base-builder/compile_lua_fuzzer)
98+
preinstalled. In `build.sh`, you should install dependencies for your project,
99+
and if necessary compile the code into Lua. Then, you can use the script to
100+
build the fuzzers. The script ensures that
101+
[luzer](https://luarocks.org/modules/ligurio/luzer) is installed so that it
102+
can be used to execute your fuzz tests. It also generates a wrapper script
103+
that can be used as a drop-in replacement for libFuzzer. This means that the
104+
generated script accepts the same command line flags for libFuzzer. Under the
105+
hood these flags are simply forwarded to the libFuzzer native addon used by
106+
luzer.
107+
108+
A usage example from the lua-example project is
109+
110+
```shell
111+
compile_lua_fuzzer lua fuzz_basic.lua
112+
```
113+
114+
Arguments are:
115+
116+
* a Lua runtime name
117+
* a path to the fuzz test inside the OSS Fuzz project directory
118+
119+
The [lua-example](https://github.com/google/oss-fuzz/blob/master/projects/lua-example/build.sh)
120+
project contains an example of a `build.sh` for a Lua projects.
121+
122+
## FuzzedDataProvider
123+
124+
luzer provides a `FuzzedDataProvider` that can simplify the task of creating a
125+
fuzz target by translating the raw input bytes received from the fuzzer into
126+
useful primitive Lua types. Its functionality is similar to
127+
`FuzzedDataProviders` available in other languages, such as
128+
[Java](https://codeintelligencetesting.github.io/jazzer-docs/jazzer-api/com/code_intelligence/jazzer/api/FuzzedDataProvider.html) and
129+
[C++](https://github.com/google/fuzzing/blob/master/docs/split-inputs.md).
130+
131+
A fuzz target using the `FuzzedDataProvider` would look as follows:
132+
133+
```lua
134+
local luzer = require("luzer")
135+
136+
local function TestOneInput(buf)
137+
local fdp = luzer.FuzzedDataProvider(buf)
138+
local str = fdp:consume_string(4)
139+
140+
local b = {}
141+
str:gsub(".", function(c) table.insert(b, c) end)
142+
local count = 0
143+
if b[1] == "o" then count = count + 1 end
144+
if b[2] == "o" then count = count + 1 end
145+
if b[3] == "p" then count = count + 1 end
146+
if b[4] == "s" then count = count + 1 end
147+
148+
if count == 4 then assert(nil) end
149+
end
150+
151+
local args = {
152+
only_ascii = 1,
153+
print_pcs = 1,
154+
}
155+
156+
luzer.Fuzz(TestOneInput, nil, args)
157+
```

docs/getting-started/new_project_guide.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ Programming language the project is written in. Values you can specify include:
102102
* [`jvm` (Java, Kotlin, Scala and other JVM-based languages)]({{ site.baseurl }}//getting-started/new-project-guide/jvm-lang/)
103103
* [`swift`]({{ site.baseurl }}//getting-started/new-project-guide/swift-lang/)
104104
* [`javascript`]({{ site.baseurl }}//getting-started/new-project-guide/javascript-lang/)
105+
* [`lua`]({{ site.baseurl }}//getting-started/new-project-guide/lua-lang/)
105106

106107
### primary_contact, auto_ccs {#primary}
107108
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

infra/base-images/base-runner/Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ COPY --from=base-ruby /usr/local/bin/gem /usr/local/bin/gem
117117
COPY --from=base-ruby /usr/local/lib/ruby /usr/local/lib/ruby
118118
COPY --from=base-ruby /usr/local/include/ruby-3.3.0 /usr/local/include/ruby-3.3.0
119119

120+
RUN apt-get update && apt-get install -y luarocks
120121

121122
# Do this last to make developing these files easier/faster due to caching.
122123
COPY bad_build_check \

infra/base-images/base-runner/bad_build_check

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,11 @@ function check_mixed_sanitizers {
330330
return 0
331331
fi
332332

333+
if [ "${FUZZING_LANGUAGE:-}" = "lua" ]; then
334+
# Sanitizer runtime is loaded via LD_PRELOAD, so this check does not apply.
335+
return 0
336+
fi
337+
333338
# For fuzztest fuzzers point to the binary instead of launcher script.
334339
if [[ $FUZZER == *"@"* ]]; then
335340
FUZZER=(${FUZZER//@/ }[0])
@@ -426,6 +431,10 @@ function check_architecture {
426431
FUZZER=${FUZZER}.pkg
427432
fi
428433

434+
if [ "${FUZZING_LANGUAGE:-}" = "lua" ]; then
435+
return 0;
436+
fi
437+
429438
# For fuzztest fuzzers point to the binary instead of launcher script.
430439
if [[ $FUZZER == *"@"* ]]; then
431440
FUZZER=(${FUZZER//@/ }[0])

infra/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
'go',
2828
'javascript',
2929
'jvm',
30+
'lua',
3031
'python',
3132
'rust',
3233
'swift',

projects/lua-example/Dockerfile

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Copyright 2023-2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
#
15+
################################################################################
16+
17+
FROM gcr.io/oss-fuzz-base/base-builder
18+
19+
RUN apt-get update && apt-get install -y build-essential make
20+
21+
COPY build.sh $SRC/
22+
COPY compile_lua_fuzzer $SRC/
23+
COPY luzer-scm-1.rockspec $SRC/
24+
25+
# For real projects, you would clone your repo in the next step.
26+
RUN mkdir -p $SRC/example
27+
28+
# Ideally, you have already configured fuzz tests in your repo so
29+
# that they run (in luzer regression mode) as part of unit testing.
30+
# Keeping the fuzz tests in sync with the source code ensures that
31+
# they are adjusted continue to work after code changes. Here,
32+
# we copy them into the example project directory.
33+
COPY example_basic.lua $SRC/example/fuzz_basic.lua
34+
35+
WORKDIR $SRC/example

projects/lua-example/build.sh

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#!/bin/bash -eu
2+
3+
# Copyright 2025 Google LLC
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
#
17+
################################################################################
18+
19+
# Install dependencies:
20+
# 1. The libraries are required for building luzer module.
21+
# 2. PUC Rio Lua executable, required for running tests itself.
22+
# 3. The Lua package manager.
23+
apt install -y cmake liblua5.1-0 liblua5.1-0-dev lua5.1 luarocks
24+
25+
# Install Lua libraries.
26+
# luarocks install --tree=lua_modules --server=https://luarocks.org/dev luzer
27+
# XXX: A custom rockspec is used because custom branch is required,
28+
# see https://github.com/ligurio/luzer/issues/63.
29+
export OSS_FUZZ=1
30+
luarocks install --tree=lua_modules $SRC/luzer-scm-1.rockspec
31+
32+
LUA_RUNTIME_NAME=lua
33+
34+
# Build fuzzers.
35+
$SRC/compile_lua_fuzzer $LUA_RUNTIME_NAME fuzz_basic.lua
36+
37+
cp fuzz_basic.lua "$OUT/"
38+
cp /usr/bin/lua5.1 "$OUT/$LUA_RUNTIME_NAME"
39+
cp -R lua_modules "$OUT/"
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#!/bin/bash -eu
2+
# Copyright 2025 Google LLC
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
#
16+
################################################################################
17+
18+
# The Lua runtime name.
19+
lua_runtime=$1
20+
# Path to the fuzz target source file relative to the project's root.
21+
fuzz_target=$2
22+
23+
fuzzer_basename=$(basename -s .lua "$fuzz_target")
24+
25+
# Create an execution wrapper that executes luzer with the correct
26+
# arguments.
27+
echo "#!/bin/bash
28+
29+
# LLVMFuzzerTestOneInput so that the wrapper script is recognized
30+
# as a fuzz target for 'check_build'.
31+
project_dir=\$(dirname \"\$0\")
32+
eval \$(luarocks --tree lua_modules path)
33+
LD_PRELOAD=\$project_dir/lua_modules/lib/lua/5.1/libfuzzer_with_asan.so \
34+
ASAN_OPTIONS=\$ASAN_OPTIONS:symbolize=1:external_symbolizer_path=\$project_dir/llvm-symbolizer:detect_leaks=0 \
35+
\$project_dir/$lua_runtime \$project_dir/$fuzz_target \$@" > "$OUT/$fuzzer_basename"
36+
37+
chmod +x "$OUT/$fuzzer_basename"
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
-- Copyright 2023-2025 Google LLC
2+
3+
-- Licensed under the Apache License, Version 2.0 (the "License");
4+
-- you may not use this file except in compliance with the
5+
-- License.
6+
-- You may obtain a copy of the License at
7+
8+
-- http://www.apache.org/licenses/LICENSE-2.0
9+
10+
-- Unless required by applicable law or agreed to in writing,
11+
-- software distributed under the License is distributed on an
12+
-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
13+
-- either express or implied.
14+
-- See the License for the specific language governing permissions
15+
-- and limitations under the License.
16+
17+
local luzer = require("luzer")
18+
19+
local function TestOneInput(buf)
20+
local fdp = luzer.FuzzedDataProvider(buf)
21+
local str = fdp:consume_string(4)
22+
23+
local b = {}
24+
str:gsub(".", function(c) table.insert(b, c) end)
25+
local count = 0
26+
if b[1] == "o" then count = count + 1 end
27+
if b[2] == "o" then count = count + 1 end
28+
if b[3] == "p" then count = count + 1 end
29+
if b[4] == "s" then count = count + 1 end
30+
31+
if count == 4 then assert(nil) end
32+
end
33+
34+
local args = {
35+
only_ascii = 1,
36+
print_pcs = 1,
37+
}
38+
39+
luzer.Fuzz(TestOneInput, nil, args)
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package = "luzer"
2+
version = "scm-1"
3+
source = {
4+
url = "git+https://github.com/ligurio/luzer",
5+
branch = "ligurio/oss-fuzz",
6+
}
7+
8+
description = {
9+
summary = "A coverage-guided, native Lua fuzzer",
10+
detailed = [[ luzer is a coverage-guided Lua fuzzing engine. It supports
11+
fuzzing of Lua code, but also C extensions written for Lua. Luzer is based off
12+
of libFuzzer. When fuzzing native code, luzer can be used in combination with
13+
Address Sanitizer or Undefined Behavior Sanitizer to catch extra bugs. ]],
14+
homepage = "https://github.com/ligurio/luzer",
15+
maintainer = "Sergey Bronnikov <[email protected]>",
16+
license = "ISC",
17+
}
18+
19+
dependencies = {
20+
"lua >= 5.1",
21+
}
22+
23+
build = {
24+
type = "cmake",
25+
-- luacheck: push no max_comment_line_length
26+
-- https://github.com/luarocks/luarocks/blob/7ed653f010671b3a7245be9adcc70068c049ef68/docs/config_file_format.md#config-file-format
27+
-- luacheck: pop
28+
variables = {
29+
CMAKE_LUADIR = "$(LUADIR)",
30+
CMAKE_LIBDIR = "$(LIBDIR)",
31+
CMAKE_BUILD_TYPE = "RelWithDebInfo",
32+
CMAKE_C_COMPILER = "clang",
33+
CMAKE_CXX_COMPILER = "clang++",
34+
},
35+
}

0 commit comments

Comments
 (0)