|
| 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 is very |
| 18 | +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 install |
| 70 | +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 its |
| 102 | +CLI 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 lua-example fuzz_basic.lua |
| 112 | +``` |
| 113 | + |
| 114 | +Arguments are: |
| 115 | + |
| 116 | +* a Lua runtime name |
| 117 | +* relative path of the project in the $SRC directory |
| 118 | +* relative path to the fuzz test inside the project |
| 119 | + |
| 120 | +The [lua-example](https://github.com/google/oss-fuzz/blob/master/projects/lua-example/build.sh) |
| 121 | +project contains an example of a `build.sh` for a Lua projects. |
| 122 | + |
| 123 | +## FuzzedDataProvider |
| 124 | + |
| 125 | +luzer provides a `FuzzedDataProvider` that can simplify the task of creating a |
| 126 | +fuzz target by translating the raw input bytes received from the fuzzer into |
| 127 | +useful primitive Lua types. Its functionality is similar to |
| 128 | +`FuzzedDataProviders` available in other languages, such as |
| 129 | +[Java](https://codeintelligencetesting.github.io/jazzer-docs/jazzer-api/com/code_intelligence/jazzer/api/FuzzedDataProvider.html) and |
| 130 | +[C++](https://github.com/google/fuzzing/blob/master/docs/split-inputs.md). |
| 131 | + |
| 132 | +A fuzz target using the `FuzzedDataProvider` would look as follows: |
| 133 | + |
| 134 | +```lua |
| 135 | +local luzer = require("luzer") |
| 136 | +
|
| 137 | +local function TestOneInput(buf) |
| 138 | + local fdp = luzer.FuzzedDataProvider(buf) |
| 139 | + local str = fdp:consume_string(4) |
| 140 | +
|
| 141 | + local b = {} |
| 142 | + str:gsub(".", function(c) table.insert(b, c) end) |
| 143 | + local count = 0 |
| 144 | + if b[1] == "o" then count = count + 1 end |
| 145 | + if b[2] == "o" then count = count + 1 end |
| 146 | + if b[3] == "p" then count = count + 1 end |
| 147 | + if b[4] == "s" then count = count + 1 end |
| 148 | +
|
| 149 | + if count == 4 then assert(nil) end |
| 150 | +end |
| 151 | +
|
| 152 | +local args = { |
| 153 | + only_ascii = 1, |
| 154 | + print_pcs = 1, |
| 155 | +} |
| 156 | +
|
| 157 | +luzer.Fuzz(TestOneInput, nil, args) |
| 158 | +``` |
0 commit comments