From aa65fbf040499da8515da6e2a0ecebe5082f4e93 Mon Sep 17 00:00:00 2001 From: David Korczynski Date: Mon, 27 Nov 2023 16:19:56 +0000 Subject: [PATCH 1/3] Extend fuzzing set up Adds a parse function ins `jv_parse.c` that enables parsing using custom flags for the parser. This is then used by two fuzzers added as well. This is to make sure fuzzing hits various code parts currently not fuzzed, e.g. `stream_token`: https://storage.googleapis.com/oss-fuzz-coverage/jq/reports/20231125/linux/src/jq/src/jv_parse.c.html#L241 Signed-off-by: David Korczynski --- src/jv.h | 1 + src/jv_parse.c | 43 ++++++++++++++++++++++++++++++++++ tests/jq_fuzz_parse_extended.c | 29 +++++++++++++++++++++++ tests/jq_fuzz_parse_stream.c | 21 +++++++++++++++++ 4 files changed, 94 insertions(+) create mode 100644 tests/jq_fuzz_parse_extended.c create mode 100644 tests/jq_fuzz_parse_stream.c diff --git a/src/jv.h b/src/jv.h index eb313f8a97..2cc63c79fb 100644 --- a/src/jv.h +++ b/src/jv.h @@ -234,6 +234,7 @@ enum { jv jv_parse(const char* string); jv jv_parse_sized(const char* string, int length); +jv jv_parse_custom_flags(const char* string, int flags); typedef void (*jv_nomem_handler_f)(void *); void jv_nomem_handler(jv_nomem_handler_f, void *); diff --git a/src/jv_parse.c b/src/jv_parse.c index 3be9360833..0c427eaed6 100644 --- a/src/jv_parse.c +++ b/src/jv_parse.c @@ -901,3 +901,46 @@ jv jv_parse_sized(const char* string, int length) { jv jv_parse(const char* string) { return jv_parse_sized(string, strlen(string)); } + +jv jv_parse_sized_custom_flags(const char* string, int length, int flags) { + struct jv_parser parser; + parser_init(&parser, flags); + jv_parser_set_buf(&parser, string, length, 0); + jv value = jv_parser_next(&parser); + if (jv_is_valid(value)) { + jv next = jv_parser_next(&parser); + if (jv_is_valid(next)) { + // multiple JSON values, we only wanted one + jv_free(value); + jv_free(next); + value = jv_invalid_with_msg(jv_string("Unexpected extra JSON values")); + } else if (jv_invalid_has_msg(jv_copy(next))) { + // parser error after the first JSON value + jv_free(value); + value = next; + } else { + // a single valid JSON value + jv_free(next); + } + } else if (jv_invalid_has_msg(jv_copy(value))) { + // parse error, we'll return it + } else { + // no value at all + jv_free(value); + value = jv_invalid_with_msg(jv_string("Expected JSON value")); + } + parser_free(&parser); + + if (!jv_is_valid(value) && jv_invalid_has_msg(jv_copy(value))) { + jv msg = jv_invalid_get_msg(value); + value = jv_invalid_with_msg(jv_string_fmt("%s (while parsing '%s')", + jv_string_value(msg), + string)); + jv_free(msg); + } + return value; +} + +jv jv_parse_custom_flags(const char* string, int flags) { + return jv_parse_sized_custom_flags(string, strlen(string), flags); +} diff --git a/tests/jq_fuzz_parse_extended.c b/tests/jq_fuzz_parse_extended.c new file mode 100644 index 0000000000..af6ceccc8d --- /dev/null +++ b/tests/jq_fuzz_parse_extended.c @@ -0,0 +1,29 @@ +#include +#include +#include + +#include "jv.h" + +int LLVMFuzzerTestOneInput(uint8_t *data, size_t size) { + if (size < 4) { + return 0; + } + + int fuzz_flags = *(int*)data; + data += 4; + size -= 4; + + // Creat null-terminated string + char *null_terminated = (char *)malloc(size + 1); + memcpy(null_terminated, (char *)data, size); + null_terminated[size] = '\0'; + + // Fuzzer entrypoint + jv res = jv_parse_custom_flags(null_terminated, fuzz_flags); + jv_free(res); + + // Free the null-terminated string + free(null_terminated); + + return 0; +} diff --git a/tests/jq_fuzz_parse_stream.c b/tests/jq_fuzz_parse_stream.c new file mode 100644 index 0000000000..5b51c599f7 --- /dev/null +++ b/tests/jq_fuzz_parse_stream.c @@ -0,0 +1,21 @@ +#include +#include +#include + +#include "jv.h" + +int LLVMFuzzerTestOneInput(uint8_t *data, size_t size) { + // Creat null-terminated string + char *null_terminated = (char *)malloc(size + 1); + memcpy(null_terminated, (char *)data, size); + null_terminated[size] = '\0'; + + // Fuzzer entrypoint + jv res = jv_parse_custom_flags(null_terminated, JV_PARSE_STREAMING); + jv_free(res); + + // Free the null-terminated string + free(null_terminated); + + return 0; +} From aa07438941530b5cfda7f3da797bb5e44794216b Mon Sep 17 00:00:00 2001 From: David Korczynski Date: Tue, 28 Nov 2023 18:17:27 +0000 Subject: [PATCH 2/3] test: add jv_dump to extended fuzzer Signed-off-by: David Korczynski --- tests/jq_fuzz_parse_extended.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/jq_fuzz_parse_extended.c b/tests/jq_fuzz_parse_extended.c index af6ceccc8d..7f212fcee0 100644 --- a/tests/jq_fuzz_parse_extended.c +++ b/tests/jq_fuzz_parse_extended.c @@ -5,13 +5,16 @@ #include "jv.h" int LLVMFuzzerTestOneInput(uint8_t *data, size_t size) { - if (size < 4) { + if (size < 8) { return 0; } int fuzz_flags = *(int*)data; data += 4; size -= 4; + int dump_flags = *(int*)data; + data += 4; + size -= 4; // Creat null-terminated string char *null_terminated = (char *)malloc(size + 1); @@ -20,6 +23,9 @@ int LLVMFuzzerTestOneInput(uint8_t *data, size_t size) { // Fuzzer entrypoint jv res = jv_parse_custom_flags(null_terminated, fuzz_flags); + if (jv_is_valid(res)) { + jv_dump(res, dump_flags); + } jv_free(res); // Free the null-terminated string From 9fc928457697b366a92b87a2b2537fd6b6e7766b Mon Sep 17 00:00:00 2001 From: David Korczynski Date: Tue, 28 Nov 2023 18:18:10 +0000 Subject: [PATCH 3/3] jv_parse: refactor jv_parse_sized Signed-off-by: David Korczynski --- src/jv_parse.c | 47 ++++++----------------------------------------- 1 file changed, 6 insertions(+), 41 deletions(-) diff --git a/src/jv_parse.c b/src/jv_parse.c index 0c427eaed6..110483a4af 100644 --- a/src/jv_parse.c +++ b/src/jv_parse.c @@ -859,9 +859,9 @@ jv jv_parser_next(struct jv_parser* p) { } } -jv jv_parse_sized(const char* string, int length) { +jv jv_parse_sized_custom_flags(const char* string, int length, int flags) { struct jv_parser parser; - parser_init(&parser, 0); + parser_init(&parser, flags); jv_parser_set_buf(&parser, string, length, 0); jv value = jv_parser_next(&parser); if (jv_is_valid(value)) { @@ -898,47 +898,12 @@ jv jv_parse_sized(const char* string, int length) { return value; } -jv jv_parse(const char* string) { - return jv_parse_sized(string, strlen(string)); +jv jv_parse_sized(const char* string, int length) { + return jv_parse_sized_custom_flags(string, length, 0); } -jv jv_parse_sized_custom_flags(const char* string, int length, int flags) { - struct jv_parser parser; - parser_init(&parser, flags); - jv_parser_set_buf(&parser, string, length, 0); - jv value = jv_parser_next(&parser); - if (jv_is_valid(value)) { - jv next = jv_parser_next(&parser); - if (jv_is_valid(next)) { - // multiple JSON values, we only wanted one - jv_free(value); - jv_free(next); - value = jv_invalid_with_msg(jv_string("Unexpected extra JSON values")); - } else if (jv_invalid_has_msg(jv_copy(next))) { - // parser error after the first JSON value - jv_free(value); - value = next; - } else { - // a single valid JSON value - jv_free(next); - } - } else if (jv_invalid_has_msg(jv_copy(value))) { - // parse error, we'll return it - } else { - // no value at all - jv_free(value); - value = jv_invalid_with_msg(jv_string("Expected JSON value")); - } - parser_free(&parser); - - if (!jv_is_valid(value) && jv_invalid_has_msg(jv_copy(value))) { - jv msg = jv_invalid_get_msg(value); - value = jv_invalid_with_msg(jv_string_fmt("%s (while parsing '%s')", - jv_string_value(msg), - string)); - jv_free(msg); - } - return value; +jv jv_parse(const char* string) { + return jv_parse_sized(string, strlen(string)); } jv jv_parse_custom_flags(const char* string, int flags) {