Skip to content
Closed
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Integration with libFuzzer's `FuzzedDataProvider`.
- Examples with tests.
- Documentation with usecases, API etc.
- Support for command line arguments for libfuzzer.
- Environment variable to disable parsing of command line arguments for libfuzzer - `LUZER_NOT_USE_CLI_ARGS`.
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,7 +15,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - Examples with tests.
 - Documentation with usecases, API etc.
 - Support for command line arguments for libfuzzer.
-- Environment variable to disable parsing of command line arguments for libfuzzer - `LUZER_NOT_USE_CLI_ARGS`.
+- Environment variable to disable parsing of command line arguments
+  for libfuzzer - `LUZER_NOT_USE_CLI_ARGS`.
 
 ### Changed
 


### Changed

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ local function TestOneInput(buf)
end
end

luzer.Fuzz(TestOneInput)
luzer.Fuzz(TestOneInput, nil, {})
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What for this change?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (lua_istable(L, -1) == 0) {

At the moment, it is checked that the last argument is a table and if not, fuzzing ends with an error. Therefore, it is necessary to explicitly specify arguments 2 and 3.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not clear why support of CLI arguments makes custom mutator and table with options mandatory for Fuzz function.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is that the code currently shown in the readme will not work at all. The fuzzing process will start only if all 3 arguments are passed to the function input. In this PR, the process of parsing input arguments has been changed, I decided to fix this too.

```

3. Start the fuzzer using the fuzz target
Expand Down
1 change: 1 addition & 0 deletions luzer/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ set(LUZER_SOURCES luzer.c
fuzzed_data_provider.cc
tracer.c
counters.c
luzer_args.c
${CMAKE_CURRENT_BINARY_DIR}/version.c)

add_library(${CMAKE_PROJECT_NAME} SHARED ${LUZER_SOURCES})
Expand Down
54 changes: 4 additions & 50 deletions luzer/luzer.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include "macros.h"
#include "tracer.h"
#include "version.h"
#include "luzer_args.h"
#include "luzer.h"

#define TEST_ONE_INPUT_FUNC "luzer_test_one_input"
Expand Down Expand Up @@ -327,57 +328,10 @@ load_custom_mutator_lib(void) {
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);
char **argv = NULL;
int argc = 0;

/* 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 */
luaL_get_fuzz_args(L, &argv, &argc);

/* Processing a function with custom mutator. */
if (!lua_isnil(L, -1) && (lua_isfunction(L, -1) == 1)) {
Expand Down
284 changes: 284 additions & 0 deletions luzer/luzer_args.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,284 @@
/*
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

Please fix indentation in a whole patch.

* SPDX-License-Identifier: ISC
*
* Copyright 2022-2023, Sergey Bronnikov
*/

#include <lauxlib.h>
#include <stdlib.h>
#include <string.h>

#include "luzer_args.h"
#include "macros.h"

#define ENV_NOT_USE_CLI_ARGS "LUZER_NOT_USE_CLI_ARGS"
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would rename ENV_NOT_USE_CLI_ARGS to IGNORE_CLI_ARGS and the same for env variable name: LUZER_NOT_USE_CLI_ARGS -> LUZER_IGNORE_CLI_ARGS.


#define SEED_CORPUS_PATH_FLAG "-corpus"

#define FLAG_SCANF_FORMAT_KEY "-%[^=]"
#define FLAG_PATTERN_KEY "-%s="
#define FLAG_PATTERN_KEY_VALUE "-%s=%s"
#define FLAG_PATTERN_OVERHEAD 2
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Used only once in a line below, I would drop it.

#define FLAG_FULL_SIZE(flag) (strlen(flag) + FLAG_PATTERN_OVERHEAD)

/* Structure for convenient argument parsing. */
typedef struct {
char **argv;
int argc;
} luzer_args;

NO_SANITIZE static bool
is_flag_in_args(luzer_args *f_args, const char *key) {
if (!f_args || !f_args->argv || f_args->argc <= 1) {
return false;
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why functions below returns int and here you are using bool?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean to change return type to int? With return 0/1?

}

size_t flag_size = FLAG_FULL_SIZE(key);
/* +1 for null terminated */
char flag[flag_size + 1];
snprintf(flag, flag_size + 1, FLAG_PATTERN_KEY, key);
for (int i = 0; i < f_args->argc; i++) {
if (strncmp(f_args->argv[i], flag, flag_size) == 0) {
return true;
}
}
return false;
}

NO_SANITIZE static int
add_arg_with_mem_allocation(luzer_args *l_args, char *arg) {
char **argvp = realloc(l_args->argv, sizeof(char*) * (l_args->argc + 1));
if (argvp == NULL) {
return -1;
}
l_args->argv = argvp;
l_args->argv[l_args->argc] = arg;
return 0;
}

NO_SANITIZE static int
luaL_get_args_from_cli(lua_State *L, luzer_args *cli_args) {
lua_getglobal(L, "arg");

cli_args->argv = malloc(1 * sizeof(char*));
if (!cli_args->argv) {
return -1;
}

lua_pushnil(L);

bool use_cli_args = !getenv(ENV_NOT_USE_CLI_ARGS);
/* Zero arg is reserved for program name. */
cli_args->argc = 1;
while (lua_next(L, -2) != 0) {
const char *value = lua_tostring(L, -1);
const int key = lua_tointeger(L, -2);
lua_pop(L, 1);

if (key < 0) {
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add comment here with explanation

continue;
}

const char *arg = strdup(value);
if (!arg) {
return -1;
}

if (key == 0) {
cli_args->argv[0] = (char*)arg;
continue;
}

if (use_cli_args) {
if (add_arg_with_mem_allocation(cli_args, (char*)arg)) {
return -1;
}
cli_args->argc++;
}
}
lua_pop(L, 1);
return 0;
}

NO_SANITIZE static int
luaL_get_args_from_table(lua_State *L, luzer_args *table_args) {
if (lua_istable(L, -1) == 0) {
return -2;
}

lua_pushnil(L);

/* Processing a table with options. */
table_args->argc = 0;
while (lua_next(L, -2) != 0) {
const char *key = lua_tostring(L, -2);
const char *value = lua_tostring(L, -1);
lua_pop(L, 1);

/* +1 for null terminated */
size_t arg_len = FLAG_FULL_SIZE(key) + strlen(value) + 1;
char *arg = calloc(arg_len, sizeof(char));
if (!arg) {
return -1;
}
snprintf(arg, arg_len, FLAG_PATTERN_KEY_VALUE, key, value);

if (table_args->argc > 0) {
if (add_arg_with_mem_allocation(table_args, arg)) {
return -1;
}
} else {
table_args->argv = malloc(1 * sizeof(char*));
if (!table_args->argv) {
return -1;
}
table_args->argv[table_args->argc] = arg;
}
table_args->argc++;
}
lua_pop(L, 1);
return 0;
}

NO_SANITIZE static int
merge_args(luzer_args *cli_args, luzer_args *table_args, luzer_args *total_args) {
/* Program name is first argument. */
total_args->argc = 1;
total_args->argv = malloc(sizeof(char*));
if (!cli_args->argv) {
return -1;
}

/* Program name on zero index. */
total_args->argv[0] = cli_args->argv[0];

char *corpus_path = NULL;
for (int i = 0; i < table_args->argc; i++) {
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably it is better to put table_args->argv[i] to a separate variable (for example cur_arg) at the beginning of loop iteration. This change will reduce length of lines a bit. Currently, it is difficult to read lines in a loop body.

if (strncmp(table_args->argv[i], SEED_CORPUS_PATH_FLAG, strlen(SEED_CORPUS_PATH_FLAG)) == 0) {
int corpus_path_len = strlen(table_args->argv[i]) - strlen(SEED_CORPUS_PATH_FLAG);
corpus_path = malloc(corpus_path_len * sizeof(char*));
if (!corpus_path) {
return -1;
}
memcpy(corpus_path, &table_args->argv[i][strlen(SEED_CORPUS_PATH_FLAG) + 1], corpus_path_len);
free(table_args->argv[i]);
table_args->argv[i] = NULL;
} else {
char key[strlen(table_args->argv[i]) + 1];
if (sscanf(table_args->argv[i], FLAG_SCANF_FORMAT_KEY, key) == 0) {
#ifdef DEBUG
DEBUG_PRINT("error get libfuzzer flag in string: %s\n", table_args->argv[i]);
#endif /* DEBUG */
continue;
}

if (is_flag_in_args(cli_args, key)) {
continue;
}

if (add_arg_with_mem_allocation(total_args, table_args->argv[i])) {
return -1;
}
total_args->argc++;
}
}

for (int i = 1; i < cli_args->argc; i++) {
if (add_arg_with_mem_allocation(total_args, cli_args->argv[i])) {
return -1;
}
total_args->argc++;
}
if (corpus_path) {
if (add_arg_with_mem_allocation(total_args, corpus_path)) {
return -1;
}
total_args->argc++;
}

if (add_arg_with_mem_allocation(total_args, NULL)) {
return -1;
}

if (table_args->argv) {
free(table_args->argv);
}

if (cli_args->argv) {
free(cli_args->argv);
}

return 0;
}

NO_SANITIZE static void
free_args(luzer_args args) {
if (!args.argv) {
return;
}
for (int i = 0; i < args.argc; i++) {
if (args.argv[i]) {
free(args.argv[i]);
}
}
free(args.argv);
}

#ifdef DEBUG
NO_SANITIZE static void
print_args_with_prefix(luzer_args args, const char* prefix) {
for (int i = 0; i < args.argc; i++) {
DEBUG_PRINT("libFuzzer %s arg - '%s'\n", prefix, args.argv[i]);
}
}
#endif /* DEBUG */

NO_SANITIZE int
luaL_get_fuzz_args(lua_State *L, char ***argv, int *argc) {
luzer_args total_args = { .argv = NULL, .argc = 0};
luzer_args cli_args = { .argv = NULL, .argc = 0 };
luzer_args table_args = { .argv = NULL, .argc = 0 };

int result = -1;
result = luaL_get_args_from_cli(L, &cli_args);
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not a self-explained variable name. I propose to rename to something like retcode, rc or something else.

if (result != 0) {
free_args(cli_args);
luaL_error(L, "failed parsing fuzz args. not enough memory");
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

replace string with "failed to parse arguments: not enough memory"

}

#ifdef DEBUG
print_args_with_prefix(cli_args, "from cli");
#endif /* DEBUG */

/* If flag in cli and lua is duplicated, then flag from lua is ignored. */
result = luaL_get_args_from_table(L, &table_args);
if (result != 0) {
free_args(table_args);
free_args(cli_args);
if (result == -2) {
luaL_error(L, "failed parsing fuzz args. last argument is not a table");
}
luaL_error(L, "failed parsing fuzz args. not enough memory");
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

replace string with "failed to parse arguments: not enough memory"

}

#ifdef DEBUG
print_args_with_prefix(table_args, "from table");
#endif /* DEBUG */

result = merge_args(&cli_args, &table_args, &total_args);
if (result != 0) {
free_args(table_args);
free_args(cli_args);
free_args(total_args);
luaL_error(L, "failed parsing fuzz args. not enough memory");
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

replace string with "failed to parse arguments: not enough memory"

}

#ifdef DEBUG
print_args_with_prefix(total_args, "total");
#endif /* DEBUG */

*argv = total_args.argv;
*argc = total_args.argc;

return 0;
}
6 changes: 6 additions & 0 deletions luzer/luzer_args.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#ifndef LUZER_ARGS_H_
#define LUZER_ARGS_H_

int luaL_get_fuzz_args(lua_State *L, char ***argv, int *argc);

#endif // LUZER_ARGS_H_
Loading