File: fastjson_test.c

package info (click to toggle)
redis 5%3A8.0.2-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 22,304 kB
  • sloc: ansic: 216,903; tcl: 51,562; sh: 4,625; perl: 4,214; cpp: 3,568; python: 2,954; makefile: 2,055; ruby: 639; javascript: 30; csh: 7
file content (406 lines) | stat: -rw-r--r-- 13,493 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
/* fastjson_test.c - Stress test for fastjson.c
 *
 * This performs boundary and corruption tests to ensure
 * the JSON parser handles edge cases without accessing
 * memory outside the bounds of the input.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <time.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>
#include <errno.h>
#include <setjmp.h>

/* Page size constant - typically 4096 or 16k bytes (Apple Silicon).
 * We use 16k so that it will work on both, but not with Linux huge pages. */
#define PAGE_SIZE 4096*4
#define MAX_JSON_SIZE (PAGE_SIZE - 128)  /* Keep some margin */
#define MAX_FIELD_SIZE 64
#define NUM_TEST_ITERATIONS 100000
#define NUM_CORRUPTION_TESTS 10000
#define NUM_BOUNDARY_TESTS 10000

/* Test state tracking */
static char *safe_page = NULL;       /* Start of readable/writable page */
static char *unsafe_page = NULL;     /* Start of inaccessible guard page */
static int boundary_violation = 0;   /* Flag for boundary violations */
static jmp_buf jmpbuf;               /* For signal handling */
static int tests_passed = 0;
static int tests_failed = 0;
static int corruptions_passed = 0;
static int boundary_tests_passed = 0;

/* Test metadata for tracking */
typedef struct {
    char *json;
    size_t json_len;
    char field[MAX_FIELD_SIZE];
    size_t field_len;
    int expected_result;
} test_case_t;

/* Forward declarations for test JSON generation */
char *generate_random_json(size_t *len, char *field, size_t *field_len, int *has_field);
void corrupt_json(char *json, size_t len);
void setup_test_memory(void);
void cleanup_test_memory(void);
void run_normal_tests(void);
void run_corruption_tests(void);
void run_boundary_tests(void);
void print_test_summary(void);

/* Signal handler for segmentation violations */
static void sigsegv_handler(int sig) {
    boundary_violation = 1;
    printf("Boundary violation detected! Caught signal %d\n", sig);
    longjmp(jmpbuf, 1);
}

/* Wrapper for jsonExtractField to check for boundary violations */
exprtoken *safe_extract_field(const char *json, size_t json_len,
                             const char *field, size_t field_len) {
    boundary_violation = 0;

    if (setjmp(jmpbuf) == 0) {
        return jsonExtractField(json, json_len, field, field_len);
    } else {
        return NULL; /* Return NULL if boundary violation occurred */
    }
}

/* Setup two adjacent memory pages - one readable/writable, one inaccessible */
void setup_test_memory(void) {
    /* Request a page of memory, with specific alignment. We rely on the
     * fact that hopefully the page after that will cause a segfault if
     * accessed. */
    void *region = mmap(NULL, PAGE_SIZE,
                       PROT_READ | PROT_WRITE,
                       MAP_PRIVATE | MAP_ANONYMOUS,
                       -1, 0);

    if (region == MAP_FAILED) {
        perror("mmap failed");
        exit(EXIT_FAILURE);
    }

    safe_page = (char*)region;
    unsafe_page = safe_page + PAGE_SIZE;
    // Uncomment to make sure it crashes :D
    // printf("%d\n", unsafe_page[5]);

    /* Set up signal handlers for memory access violations */
    struct sigaction sa;
    sa.sa_handler = sigsegv_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;

    sigaction(SIGSEGV, &sa, NULL);
    sigaction(SIGBUS, &sa, NULL);
}

void cleanup_test_memory(void) {
    if (safe_page != NULL) {
        munmap(safe_page, PAGE_SIZE);
        safe_page = NULL;
        unsafe_page = NULL;
    }
}

/* Generate random strings with proper escaping for JSON */
void generate_random_string(char *buffer, size_t max_len) {
    static const char charset[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    size_t len = 1 + rand() % (max_len - 2); /* Ensure at least 1 char */

    for (size_t i = 0; i < len; i++) {
        buffer[i] = charset[rand() % (sizeof(charset) - 1)];
    }
    buffer[len] = '\0';
}

/* Generate random numbers as strings */
void generate_random_number(char *buffer, size_t max_len) {
    double num = (double)rand() / RAND_MAX * 1000.0;

    /* Occasionally make it negative or add decimal places */
    if (rand() % 5 == 0) num = -num;
    if (rand() % 3 != 0) num += (double)(rand() % 100) / 100.0;

    snprintf(buffer, max_len, "%.6g", num);
}

/* Generate a random field name */
void generate_random_field(char *field, size_t *field_len) {
    generate_random_string(field, MAX_FIELD_SIZE / 2);
    *field_len = strlen(field);
}

/* Generate a random JSON object with fields */
char *generate_random_json(size_t *len, char *field, size_t *field_len, int *has_field) {
    char *json = malloc(MAX_JSON_SIZE);
    if (json == NULL) {
        perror("malloc");
        exit(EXIT_FAILURE);
    }

    char buffer[MAX_JSON_SIZE / 4]; /* Buffer for generating values */
    int pos = 0;
    int num_fields = 1 + rand() % 10; /* Random number of fields */
    int target_field_index = rand() % num_fields; /* Which field to return */

    /* Start the JSON object */
    pos += snprintf(json + pos, MAX_JSON_SIZE - pos, "{");

    /* Generate random field/value pairs */
    for (int i = 0; i < num_fields; i++) {
        /* Add a comma if not the first field */
        if (i > 0) {
            pos += snprintf(json + pos, MAX_JSON_SIZE - pos, ", ");
        }

        /* Generate a field name */
        if (i == target_field_index) {
            /* This is our target field - save it for the caller */
            generate_random_field(field, field_len);
            pos += snprintf(json + pos, MAX_JSON_SIZE - pos, "\"%s\": ", field);
            *has_field = 1;
            /* Sometimes change the last char so that it will not match. */
            if (rand() % 2) {
                *has_field = 0;
                field[*field_len-1] = '!';
            }
        } else {
            generate_random_string(buffer, MAX_FIELD_SIZE / 4);
            pos += snprintf(json + pos, MAX_JSON_SIZE - pos, "\"%s\": ", buffer);
        }

        /* Generate a random value type */
        int value_type = rand() % 5;
        switch (value_type) {
            case 0: /* String */
                generate_random_string(buffer, MAX_JSON_SIZE / 8);
                pos += snprintf(json + pos, MAX_JSON_SIZE - pos, "\"%s\"", buffer);
                break;

            case 1: /* Number */
                generate_random_number(buffer, MAX_JSON_SIZE / 8);
                pos += snprintf(json + pos, MAX_JSON_SIZE - pos, "%s", buffer);
                break;

            case 2: /* Boolean: true */
                pos += snprintf(json + pos, MAX_JSON_SIZE - pos, "true");
                break;

            case 3: /* Boolean: false */
                pos += snprintf(json + pos, MAX_JSON_SIZE - pos, "false");
                break;

            case 4: /* Null */
                pos += snprintf(json + pos, MAX_JSON_SIZE - pos, "null");
                break;

            case 5: /* Array (simple) */
                pos += snprintf(json + pos, MAX_JSON_SIZE - pos, "[");
                int array_items = 1 + rand() % 5;
                for (int j = 0; j < array_items; j++) {
                    if (j > 0) pos += snprintf(json + pos, MAX_JSON_SIZE - pos, ", ");

                    /* Array items - either number or string */
                    if (rand() % 2) {
                        generate_random_number(buffer, MAX_JSON_SIZE / 16);
                        pos += snprintf(json + pos, MAX_JSON_SIZE - pos, "%s", buffer);
                    } else {
                        generate_random_string(buffer, MAX_JSON_SIZE / 16);
                        pos += snprintf(json + pos, MAX_JSON_SIZE - pos, "\"%s\"", buffer);
                    }
                }
                pos += snprintf(json + pos, MAX_JSON_SIZE - pos, "]");
                break;
        }
    }

    /* Close the JSON object */
    pos += snprintf(json + pos, MAX_JSON_SIZE - pos, "}");
    *len = pos;

    return json;
}

/* Corrupt JSON by replacing random characters */
void corrupt_json(char *json, size_t len) {
    if (len < 2) return;  /* Too short to corrupt safely */

    /* Corrupt 1-3 characters */
    int num_corruptions = 1 + rand() % 3;
    for (int i = 0; i < num_corruptions; i++) {
        size_t pos = rand() % len;
        char corruption = " \t\n{}[]\":,0123456789abcdefXYZ"[rand() % 30];
        json[pos] = corruption;
    }
}

/* Run standard parser tests with generated valid JSON */
void run_normal_tests(void) {
    printf("Running normal JSON extraction tests...\n");

    for (int i = 0; i < NUM_TEST_ITERATIONS; i++) {
        char field[MAX_FIELD_SIZE] = {0};
        size_t field_len = 0;
        size_t json_len = 0;
        int has_field = 0;

        /* Generate random JSON */
        char *json = generate_random_json(&json_len, field, &field_len, &has_field);

        /* Use valid field to test parser */
        exprtoken *token = safe_extract_field(json, json_len, field, field_len);

        /* Check if we got a token as expected */
        if (has_field && token != NULL) {
            exprTokenRelease(token);
            tests_passed++;
        } else if (!has_field && token == NULL) {
            tests_passed++;
        } else {
            tests_failed++;
        }

        /* Test with a non-existent field */
        char nonexistent_field[MAX_FIELD_SIZE] = "nonexistent_field";
        token = safe_extract_field(json, json_len, nonexistent_field, strlen(nonexistent_field));

        if (token == NULL) {
            tests_passed++;
        } else {
            exprTokenRelease(token);
            tests_failed++;
        }

        free(json);
    }
}

/* Run tests with corrupted JSON */
void run_corruption_tests(void) {
    printf("Running JSON corruption tests...\n");

    for (int i = 0; i < NUM_CORRUPTION_TESTS; i++) {
        char field[MAX_FIELD_SIZE] = {0};
        size_t field_len = 0;
        size_t json_len = 0;
        int has_field = 0;

        /* Generate random JSON */
        char *json = generate_random_json(&json_len, field, &field_len, &has_field);

        /* Make a copy and corrupt it */
        char *corrupted = malloc(json_len + 1);
        if (!corrupted) {
            perror("malloc");
            free(json);
            exit(EXIT_FAILURE);
        }

        memcpy(corrupted, json, json_len + 1);
        corrupt_json(corrupted, json_len);

        /* Test with corrupted JSON */
        exprtoken *token = safe_extract_field(corrupted, json_len, field, field_len);

        /* We're just testing that it doesn't crash or access invalid memory */
        if (boundary_violation) {
            printf("Boundary violation with corrupted JSON!\n");
            tests_failed++;
        } else {
            if (token != NULL) {
                exprTokenRelease(token);
            }
            corruptions_passed++;
        }

        free(corrupted);
        free(json);
    }
}

/* Run tests at memory boundaries */
void run_boundary_tests(void) {
    printf("Running memory boundary tests...\n");

    for (int i = 0; i < NUM_BOUNDARY_TESTS; i++) {
        char field[MAX_FIELD_SIZE] = {0};
        size_t field_len = 0;
        size_t json_len = 0;
        int has_field = 0;

        /* Generate random JSON */
        char *temp_json = generate_random_json(&json_len, field, &field_len, &has_field);

        /* Truncate the JSON to a random length */
        size_t truncated_len = 1 + rand() % json_len;

        /* Place at the edge of the safe page */
        size_t offset = PAGE_SIZE - truncated_len;
        memcpy(safe_page + offset, temp_json, truncated_len);

        /* Test parsing with non-existent field (forcing it to scan to end) */
        char nonexistent_field[MAX_FIELD_SIZE] = "nonexistent_field";
        exprtoken *token = safe_extract_field(safe_page + offset, truncated_len,
                                             nonexistent_field, strlen(nonexistent_field));

        /* We're just testing that it doesn't access memory beyond the boundary */
        if (boundary_violation) {
            printf("Boundary violation at edge of memory page!\n");
            tests_failed++;
        } else {
            if (token != NULL) {
                exprTokenRelease(token);
            }
            boundary_tests_passed++;
        }

        free(temp_json);
    }
}

/* Print summary of test results */
void print_test_summary(void) {
    printf("\n===== FASTJSON PARSER TEST SUMMARY =====\n");
    printf("Normal tests passed: %d/%d\n", tests_passed, NUM_TEST_ITERATIONS * 2);
    printf("Corruption tests passed: %d/%d\n", corruptions_passed, NUM_CORRUPTION_TESTS);
    printf("Boundary tests passed: %d/%d\n", boundary_tests_passed, NUM_BOUNDARY_TESTS);
    printf("Failed tests: %d\n", tests_failed);

    if (tests_failed == 0) {
        printf("\nALL TESTS PASSED! The JSON parser appears to be robust.\n");
    } else {
        printf("\nSome tests FAILED. The JSON parser may be vulnerable.\n");
    }
}

/* Entry point for fastjson parser test */
void run_fastjson_test(void) {
    printf("Starting fastjson parser stress test...\n");

    /* Seed the random number generator */
    srand(time(NULL));

    /* Setup test memory environment */
    setup_test_memory();

    /* Run the various test phases */
    run_normal_tests();
    run_corruption_tests();
    run_boundary_tests();

    /* Print summary */
    print_test_summary();

    /* Cleanup */
    cleanup_test_memory();
}