summaryrefslogtreecommitdiff
path: root/examples/redis-unstable/modules/vector-sets/fastjson_test.c
diff options
context:
space:
mode:
authorMitja Felicijan <mitja.felicijan@gmail.com>2026-01-21 22:40:55 +0100
committerMitja Felicijan <mitja.felicijan@gmail.com>2026-01-21 22:40:55 +0100
commit5d8dfe892a2ea89f706ee140c3bdcfd89fe03fda (patch)
tree1acdfa5220cd13b7be43a2a01368e80d306473ca /examples/redis-unstable/modules/vector-sets/fastjson_test.c
parentc7ab12bba64d9c20ccd79b132dac475f7bc3923e (diff)
downloadcrep-5d8dfe892a2ea89f706ee140c3bdcfd89fe03fda.tar.gz
Add Redis source code for testing
Diffstat (limited to 'examples/redis-unstable/modules/vector-sets/fastjson_test.c')
-rw-r--r--examples/redis-unstable/modules/vector-sets/fastjson_test.c406
1 files changed, 406 insertions, 0 deletions
diff --git a/examples/redis-unstable/modules/vector-sets/fastjson_test.c b/examples/redis-unstable/modules/vector-sets/fastjson_test.c
new file mode 100644
index 0000000..1ea76a9
--- /dev/null
+++ b/examples/redis-unstable/modules/vector-sets/fastjson_test.c
@@ -0,0 +1,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();
+}