Skip to content

Commit f8d02e0

Browse files
committed
Improve fuzzing coverage of yajl-ruby for OSS-Fuzz
1 parent e8de283 commit f8d02e0

10 files changed

+644
-0
lines changed

fuzz/error_string_fuzzer.c

+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*
2+
# Copyright 2024 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+
19+
#include <stddef.h>
20+
#include <stdint.h>
21+
#include <stdlib.h>
22+
#include <string.h>
23+
24+
#include "yajl_lex.h"
25+
#include "yajl_parser.h"
26+
#include "yajl_encode.h"
27+
#include "yajl_bytestack.h"
28+
#include "api/yajl_parse.h"
29+
30+
// Helper to create parse error
31+
static void create_parse_error(yajl_handle hand) {
32+
yajl_bs_push(hand->stateStack, yajl_state_parse_error);
33+
hand->parseError = "test parse error";
34+
}
35+
36+
// Helper to create lexical error
37+
static void create_lexical_error(yajl_handle hand) {
38+
yajl_bs_push(hand->stateStack, yajl_state_lexical_error);
39+
}
40+
41+
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
42+
if (size < 2) {
43+
return 0;
44+
}
45+
46+
// Initialize parser
47+
yajl_parser_config cfg = { 1, 1 }; // allowComments=1, checkUTF8=1
48+
yajl_handle hand = yajl_alloc(NULL, &cfg, NULL, NULL);
49+
if (!hand) {
50+
return 0;
51+
}
52+
53+
// Use first byte to determine error type and verbosity
54+
unsigned int error_type = data[0] % 3; // 0=parse, 1=lexical, 2=other
55+
int verbose = data[1] & 1;
56+
57+
// Set bytesConsumed to some position in the input
58+
hand->bytesConsumed = (size > 2) ? (data[2] % (size - 2)) : 0;
59+
60+
// Create error state based on type
61+
switch (error_type) {
62+
case 0:
63+
create_parse_error(hand);
64+
break;
65+
case 1:
66+
create_lexical_error(hand);
67+
break;
68+
default:
69+
// Leave in unknown state
70+
break;
71+
}
72+
73+
// Get error string
74+
unsigned char *error = yajl_render_error_string(hand,
75+
data + 2,
76+
size > 2 ? size - 2 : 0,
77+
verbose);
78+
79+
// Free error string if allocated
80+
if (error) {
81+
yajl_free_error(hand, error);
82+
}
83+
84+
yajl_free(hand);
85+
return 0;
86+
}

fuzz/error_string_fuzzer.dict

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# JSON fragments that might trigger errors
2+
"{"
3+
"}"
4+
"["
5+
"]"
6+
","
7+
":"
8+
"null"
9+
"true"
10+
"false"
11+
"123"
12+
"-123"
13+
"1.23"
14+
"\\"string\\""
15+
"\\"
16+
"\\n"
17+
"\\r"
18+
"\\t"
19+
"\\u0000"
20+
21+
# Special characters that might affect error display
22+
"\\n"
23+
"\\r"
24+
"\\t"
25+
" "
26+
"#"
27+
"/*"
28+
"//"
29+
30+
# Common error locations
31+
"{"
32+
"{\\"key\\":"
33+
"[1,2,"
34+
"[\\"incomplete"
35+
"{\\"key\\":123,"

fuzz/json_fuzzer.c

+104
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/*
2+
# Copyright 2018 Google Inc.
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+
19+
#include <assert.h>
20+
#include <stdbool.h>
21+
#include <stddef.h>
22+
#include <stdint.h>
23+
24+
#include "api/yajl_parse.h"
25+
26+
typedef struct {
27+
int arrayLevel;
28+
int objectLevel;
29+
} context;
30+
31+
static int yajl_found_null(void* ctx) {
32+
return 1;
33+
}
34+
35+
static int yajl_found_boolean(void* ctx, int boolean) {
36+
return 1;
37+
};
38+
39+
static int yajl_found_number(void* ctx, const char* v, unsigned int l) {
40+
assert(l > 0);
41+
return 1;
42+
}
43+
44+
static int yajl_found_string(void* ctx, const unsigned char* s, unsigned int l) {
45+
return 1;
46+
}
47+
48+
static int yajl_found_object_key(void* ctx, const unsigned char* v, unsigned int l) {
49+
assert(((context*)ctx)->objectLevel > 0);
50+
return 1;
51+
}
52+
53+
static int yajl_found_start_object(void* ctx) {
54+
((context*)ctx)->objectLevel++;
55+
return 1;
56+
}
57+
58+
static int yajl_found_end_object(void* ctx) {
59+
assert(((context*)ctx)->objectLevel > 0);
60+
((context*)ctx)->objectLevel--;
61+
return 1;
62+
}
63+
64+
static int yajl_found_start_array(void* ctx) {
65+
((context*)ctx)->arrayLevel++;
66+
return 1;
67+
}
68+
69+
static int yajl_found_end_array(void* ctx) {
70+
assert(((context*)ctx)->arrayLevel > 0);
71+
((context*)ctx)->arrayLevel--;
72+
return 1;
73+
}
74+
75+
static yajl_callbacks callbacks = {
76+
yajl_found_null,
77+
yajl_found_boolean,
78+
NULL,
79+
NULL,
80+
yajl_found_number,
81+
yajl_found_string,
82+
yajl_found_start_object,
83+
yajl_found_object_key,
84+
yajl_found_end_object,
85+
yajl_found_start_array,
86+
yajl_found_end_array
87+
};
88+
89+
int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
90+
context ctx = {
91+
.arrayLevel = 0,
92+
.objectLevel = 0,
93+
};
94+
yajl_parser_config cfg = {
95+
.allowComments = 1,
96+
.checkUTF8 = 1,
97+
};
98+
yajl_handle parser = yajl_alloc(&callbacks, &cfg, NULL, (void*)&ctx);
99+
100+
(void)yajl_parse(parser, data, size);
101+
yajl_free(parser);
102+
103+
return 0;
104+
}

fuzz/json_fuzzer.dict

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
"{"
2+
"}"
3+
","
4+
"["
5+
"]"
6+
","
7+
":"
8+
"e"
9+
"e+"
10+
"e-"
11+
"E"
12+
"E+"
13+
"E-"
14+
"\""
15+
"\\"
16+
" "
17+
"null"
18+
"1"
19+
"1.234"
20+
"3e4"

fuzz/lex_peek_fuzzer.c

+97
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*
2+
# Copyright 2024 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+
19+
#include <stddef.h>
20+
#include <stdint.h>
21+
#include <stdlib.h>
22+
#include <string.h>
23+
24+
#include "yajl_lex.h"
25+
#include "yajl_alloc.h"
26+
#include "api/yajl_common.h"
27+
28+
// Default allocation functions
29+
static void * malloc_wrapper(void *ctx, unsigned int sz) {
30+
return malloc(sz);
31+
}
32+
33+
static void * realloc_wrapper(void *ctx, void *ptr, unsigned int sz) {
34+
return realloc(ptr, sz);
35+
}
36+
37+
static void free_wrapper(void *ctx, void *ptr) {
38+
free(ptr);
39+
}
40+
41+
static yajl_alloc_funcs allocFuncs = {
42+
malloc_wrapper,
43+
realloc_wrapper,
44+
free_wrapper,
45+
NULL
46+
};
47+
48+
// Test that peek doesn't affect subsequent lexing
49+
static void test_peek_and_lex(yajl_lexer lexer, const unsigned char* json_text,
50+
size_t json_len, unsigned int offset) {
51+
const unsigned char *outBuf1, *outBuf2;
52+
unsigned int outLen1, outLen2;
53+
unsigned int testOffset = offset;
54+
55+
// First peek at token
56+
yajl_tok peek_tok = yajl_lex_peek(lexer, json_text, json_len, offset);
57+
58+
// Now actually lex the token
59+
yajl_tok lex_tok = yajl_lex_lex(lexer, json_text, json_len, &testOffset,
60+
&outBuf1, &outLen1);
61+
62+
// Verify that peek and actual lex return same token type
63+
if (peek_tok != lex_tok && peek_tok != yajl_tok_eof) {
64+
abort(); // Keep the abort to detect inconsistencies
65+
}
66+
}
67+
68+
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
69+
if (size == 0) {
70+
return 0;
71+
}
72+
73+
// Create lexer with different comment/UTF8 validation combinations
74+
unsigned int allowComments = data[0] & 1;
75+
unsigned int validateUTF8 = data[0] & 2;
76+
77+
// Use explicit allocation functions instead of NULL
78+
yajl_lexer lexer = yajl_lex_alloc(&allocFuncs, allowComments, validateUTF8);
79+
if (!lexer) {
80+
return 0;
81+
}
82+
83+
const unsigned char *json_text = data + 1;
84+
size_t json_len = size - 1;
85+
86+
// Test peeking at different offsets through the input
87+
for (unsigned int offset = 0; offset < json_len; offset++) {
88+
test_peek_and_lex(lexer, json_text, json_len, offset);
89+
90+
yajl_lexer new_lexer = yajl_lex_realloc(lexer);
91+
if (!new_lexer) break;
92+
lexer = new_lexer;
93+
}
94+
95+
yajl_lex_free(lexer);
96+
return 0;
97+
}

fuzz/lex_peek_fuzzer.dict

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Tokens
2+
"{"
3+
"}"
4+
"["
5+
"]"
6+
","
7+
":"
8+
"true"
9+
"false"
10+
"null"
11+
"\\""
12+
"\\n"
13+
"\\r"
14+
"\\t"
15+
"\\u"
16+
17+
# Numbers
18+
"123"
19+
"-123"
20+
"0.123"
21+
"1e10"
22+
"1e-10"
23+
24+
# Special sequences
25+
"/*"
26+
"//"
27+
"\\u0000"
28+
"\\u001F"
29+
"\\uD800"
30+
"\\uDBFF"
31+
"\\uDC00"
32+
"\\uDFFF"
33+
34+
# Common fragments
35+
"string"
36+
"number"
37+
"object"
38+
"array"

0 commit comments

Comments
 (0)