test(fuzz): add config-frame receive and identity parser fuzz targets
Add two libFuzzer harnesses (GLOBbed from tests/fuzz/*.c) and deterministic P8 config-frame receive tests: - fuzz_config_receive.c drives config_receive() from arbitrary bytes. It captures one canonical valid frame with the production sender and feeds the receiver four shapes: raw bytes, valid-version-prefix + fuzz bytes, valid frame minus the P8 tail (super_mode + copy-as) + fuzz bytes, and valid frame minus the usermap count + fuzz bytes. This reaches the --super/--copy-as and huge/negative map-count paths that random bytes cannot get through the preceding wire-bool gate. - fuzz_identity_parse.c fuzzes identity_parse_copy_as/map/chown plus the identity_wire_valid/identity_ownership_requested predicates on a fresh config per input. - test_fuzz_smoke.c gains deterministic malformed-frame cases: out-of-range super_mode, negative/extreme copy-as ids, non-bool copy-as presence, tail truncation, huge/negative usermap counts, version mismatch and a wrong-order field after the version gate. Unit build (STRICT_WARNINGS) and the fuzz build are clean; both targets run 3000+ iterations with no crash. No production code changed.
This commit is contained in:
@@ -0,0 +1,241 @@
|
||||
/*
|
||||
* Fuzz the binary config-frame receive path: Config* config_receive(int fd).
|
||||
*
|
||||
* The frame is a length-prefixed stream of strings/ints/bools, so the receiver
|
||||
* stops at the first malformed field. Feeding raw fuzz bytes alone therefore
|
||||
* almost never reaches the deep P8 trailing blocks (--super / --copy-as) or the
|
||||
* identity-map block, because every preceding wire bool must be exactly 0 or 1.
|
||||
*
|
||||
* To exercise those paths we first build one canonical, fully-valid frame with
|
||||
* the production sender and then feed the receiver four shapes:
|
||||
*
|
||||
* 1. raw : the raw fuzz bytes as the whole frame (version gate included).
|
||||
* 2. general : the valid version-string prefix + the raw fuzz bytes, so the
|
||||
* fuzzer can walk the early/core/selection blocks from arbitrary
|
||||
* input while staying past the version gate.
|
||||
* 3. tail : the valid frame up to its last P8_TAIL_BYTES (super_mode +
|
||||
* copy-as presence/uid/gid) + the raw fuzz bytes, so the fuzzer
|
||||
* directly mutates super_mode and the copy-as ids and truncates
|
||||
* the tail at any byte.
|
||||
* 4. map : the valid frame up to the --usermap count + the raw fuzz bytes,
|
||||
* so the fuzzer directly drives the map count (huge/extreme) and
|
||||
* the map entries.
|
||||
*
|
||||
* The canonical frame is captured by running config_send once, writing the
|
||||
* frame into a pipe whose read end is drained afterwards; the STATUS_OK ack is
|
||||
* pre-loaded into a second pipe so a single thread suffices.
|
||||
*/
|
||||
#include "config.h"
|
||||
#include "protocol.h"
|
||||
#include "utils.h"
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/* super_mode (4) + copy-as presence (4) + uid (4) + gid (4) = the P8 tail. */
|
||||
#define P8_TAIL_BYTES 16
|
||||
|
||||
/* Distinctive --usermap entry used to locate the map-count field in the
|
||||
* canonical frame without duplicating the wire layout here. */
|
||||
#define MAP_FROM 0x11223344
|
||||
#define MAP_TO 0x55667788
|
||||
|
||||
static unsigned char* g_frame;
|
||||
static size_t g_frame_len;
|
||||
static size_t g_version_len; /* length of the leading version-string frame */
|
||||
static size_t g_usermap_count_off; /* offset of the usermap count int, 0 = unknown */
|
||||
static bool g_frame_ready;
|
||||
|
||||
/* Read the canonical frame from the send peer. The producer shuts down its
|
||||
* write half first, so a blocking read drains the frame and then sees EOF. */
|
||||
static unsigned char* drain_frame(int fd, size_t* out_len) {
|
||||
size_t cap = 4096;
|
||||
size_t len = 0;
|
||||
unsigned char* buf = malloc(cap);
|
||||
if (!buf)
|
||||
return NULL;
|
||||
for (;;) {
|
||||
if (len == cap) {
|
||||
size_t grown = cap * 2;
|
||||
unsigned char* bigger = realloc(buf, grown);
|
||||
if (!bigger) {
|
||||
free(buf);
|
||||
return NULL;
|
||||
}
|
||||
buf = bigger;
|
||||
cap = grown;
|
||||
}
|
||||
ssize_t n = read(fd, buf + len, cap - len);
|
||||
if (n > 0) {
|
||||
len += (size_t)n;
|
||||
continue;
|
||||
}
|
||||
if (n < 0 && errno == EINTR)
|
||||
continue;
|
||||
break; /* 0 (EOF) or error */
|
||||
}
|
||||
*out_len = len;
|
||||
return buf;
|
||||
}
|
||||
|
||||
/* Serialize a valid Config with the real sender. The frame is written into a
|
||||
* pipe (64 KiB kernel buffer, far larger than one config frame) whose read end
|
||||
* is drained afterwards; the STATUS_OK ack is pre-loaded into a second pipe so
|
||||
* a single thread suffices (config_send writes the whole frame before it reads
|
||||
* the ack). */
|
||||
static void build_canonical_frame(void) {
|
||||
g_frame_ready = true;
|
||||
|
||||
Config* cfg = config_create();
|
||||
if (!cfg)
|
||||
return;
|
||||
cfg->send_directory = str_dup("/src");
|
||||
cfg->receive_root_directory = str_dup("/dst");
|
||||
/* Force the three P8 tail fields to be present (copy-as requires metadata). */
|
||||
cfg->copy_as_set = true;
|
||||
cfg->copy_as_uid = 0;
|
||||
cfg->copy_as_gid = 0;
|
||||
cfg->use_metadata = true;
|
||||
/* Force one usermap entry with a locatable sentinel. */
|
||||
cfg->usermap = malloc(sizeof(IdentityMap));
|
||||
if (cfg->usermap) {
|
||||
cfg->usermap_count = 1;
|
||||
cfg->usermap[0].from = MAP_FROM;
|
||||
cfg->usermap[0].to = MAP_TO;
|
||||
}
|
||||
if (!cfg->send_directory || !cfg->receive_root_directory || !cfg->usermap) {
|
||||
config_delete(cfg);
|
||||
return;
|
||||
}
|
||||
|
||||
int frame_pipe[2] = {-1, -1};
|
||||
int status_pipe[2] = {-1, -1};
|
||||
if (pipe(frame_pipe) != 0 || pipe(status_pipe) != 0)
|
||||
goto out;
|
||||
|
||||
int ack = STATUS_OK;
|
||||
if (write(status_pipe[1], &ack, sizeof(ack)) != (ssize_t)sizeof(ack))
|
||||
goto out;
|
||||
|
||||
io_set_fds(status_pipe[0], frame_pipe[1]);
|
||||
io_set_bwlimit(0);
|
||||
bool sent = config_send(frame_pipe[1], cfg);
|
||||
close(frame_pipe[1]);
|
||||
frame_pipe[1] = -1;
|
||||
close(status_pipe[0]);
|
||||
status_pipe[0] = -1;
|
||||
close(status_pipe[1]);
|
||||
status_pipe[1] = -1;
|
||||
|
||||
if (sent)
|
||||
g_frame = drain_frame(frame_pipe[0], &g_frame_len);
|
||||
|
||||
out:
|
||||
if (frame_pipe[0] != -1)
|
||||
close(frame_pipe[0]);
|
||||
if (frame_pipe[1] != -1)
|
||||
close(frame_pipe[1]);
|
||||
if (status_pipe[0] != -1)
|
||||
close(status_pipe[0]);
|
||||
if (status_pipe[1] != -1)
|
||||
close(status_pipe[1]);
|
||||
config_delete(cfg);
|
||||
if (!g_frame || g_frame_len == 0) {
|
||||
free(g_frame);
|
||||
g_frame = NULL;
|
||||
g_frame_len = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
g_version_len = sizeof(size_t) + strlen(PROTOCOL_VERSION);
|
||||
if (g_version_len > g_frame_len)
|
||||
g_version_len = g_frame_len;
|
||||
|
||||
/* Locate the usermap entry sentinel; its count int sits 4 bytes before it. */
|
||||
int32_t from = MAP_FROM;
|
||||
int32_t to = MAP_TO;
|
||||
unsigned char pattern[8];
|
||||
memcpy(pattern, &from, sizeof(from));
|
||||
memcpy(pattern + sizeof(from), &to, sizeof(to));
|
||||
if (g_frame_len >= sizeof(pattern)) {
|
||||
for (size_t i = 4; i + sizeof(pattern) <= g_frame_len; i++) {
|
||||
if (memcmp(g_frame + i, pattern, sizeof(pattern)) == 0) {
|
||||
g_usermap_count_off = i - sizeof(int32_t);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Best-effort non-blocking write: an oversized fuzz input is truncated rather
|
||||
* than stalling the harness. */
|
||||
static void write_best_effort(int fd, const void* data, size_t size) {
|
||||
const unsigned char* p = data;
|
||||
size_t off = 0;
|
||||
while (off < size) {
|
||||
ssize_t n = write(fd, p + off, size - off);
|
||||
if (n > 0) {
|
||||
off += (size_t)n;
|
||||
continue;
|
||||
}
|
||||
if (n < 0 && errno == EINTR)
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Build prefix ++ data as a stream and drive config_receive over it. */
|
||||
static void receive_stream(const unsigned char* prefix, size_t prefix_len, const uint8_t* data,
|
||||
size_t size) {
|
||||
int sv[2];
|
||||
if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) != 0)
|
||||
return;
|
||||
|
||||
int flags = fcntl(sv[0], F_GETFL, 0);
|
||||
if (flags != -1)
|
||||
(void)fcntl(sv[0], F_SETFL, flags | O_NONBLOCK);
|
||||
|
||||
if (prefix_len > 0)
|
||||
write_best_effort(sv[0], prefix, prefix_len);
|
||||
if (size > 0)
|
||||
write_best_effort(sv[0], data, size);
|
||||
/* Signal EOF without closing the read half, so the receiver's STATUS_ERROR
|
||||
* replies do not hit EPIPE. */
|
||||
shutdown(sv[0], SHUT_WR);
|
||||
|
||||
io_set_fds(sv[1], sv[1]);
|
||||
io_set_bwlimit(0);
|
||||
Config* cfg = config_receive(sv[1]);
|
||||
config_delete(cfg);
|
||||
|
||||
close(sv[0]);
|
||||
close(sv[1]);
|
||||
}
|
||||
|
||||
int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
|
||||
if (!g_frame_ready)
|
||||
build_canonical_frame();
|
||||
|
||||
/* Raw bytes as the whole frame (version gate and all). */
|
||||
receive_stream(NULL, 0, data, size);
|
||||
|
||||
if (g_frame) {
|
||||
/* Keep the valid version prefix, fuzz everything after it. */
|
||||
receive_stream(g_frame, g_version_len, data, size);
|
||||
|
||||
/* Keep the valid frame up to the P8 tail, fuzz super_mode + copy-as. */
|
||||
if (g_frame_len > P8_TAIL_BYTES)
|
||||
receive_stream(g_frame, g_frame_len - P8_TAIL_BYTES, data, size);
|
||||
|
||||
/* Keep the valid frame up to the usermap count, fuzz the count + entries. */
|
||||
if (g_usermap_count_off > 0)
|
||||
receive_stream(g_frame, g_usermap_count_off, data, size);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Fuzz the CLI-time identity parsers (identity.h):
|
||||
* - identity_parse_copy_as
|
||||
* - identity_parse_map (user and group variants)
|
||||
* - identity_parse_chown
|
||||
*
|
||||
* Each parser mutates a Config, so every input gets a fresh config_create()
|
||||
* (freed afterwards). After a successful parse the shared wire validator and
|
||||
* the ownership predicate are also exercised on the mutated config. The input
|
||||
* is NUL-terminated; embedded NULs simply shorten the effective spec, which is
|
||||
* fine for a parser fuzzer.
|
||||
*/
|
||||
#include "config.h"
|
||||
#include "identity.h"
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
static void exercise(Config* c, const char* spec, int which) {
|
||||
if (!c)
|
||||
return;
|
||||
switch (which) {
|
||||
case 0:
|
||||
(void)identity_parse_copy_as(c, spec);
|
||||
break;
|
||||
case 1:
|
||||
(void)identity_parse_map(c, spec, false);
|
||||
break;
|
||||
case 2:
|
||||
(void)identity_parse_map(c, spec, true);
|
||||
break;
|
||||
default:
|
||||
(void)identity_parse_chown(c, spec);
|
||||
break;
|
||||
}
|
||||
(void)identity_wire_valid(c);
|
||||
(void)identity_ownership_requested(c);
|
||||
config_delete(c);
|
||||
}
|
||||
|
||||
int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
|
||||
if (size == 0)
|
||||
return 0;
|
||||
|
||||
char* spec = malloc(size + 1);
|
||||
if (!spec)
|
||||
return 0;
|
||||
memcpy(spec, data, size);
|
||||
spec[size] = '\0';
|
||||
|
||||
exercise(config_create(), spec, 0);
|
||||
exercise(config_create(), spec, 1);
|
||||
exercise(config_create(), spec, 2);
|
||||
exercise(config_create(), spec, 3);
|
||||
|
||||
free(spec);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,16 +1,24 @@
|
||||
#include "test_fuzz_smoke.h"
|
||||
#include "chunk.h"
|
||||
#include "compression.h"
|
||||
#include "config.h"
|
||||
#include "data.h"
|
||||
#include "delta.h"
|
||||
#include "metadata.h"
|
||||
#include "protocol.h"
|
||||
#include "test_utils.h"
|
||||
#include "utils.h"
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/* P8 config-frame tail: super_mode (4) + copy-as presence (4) + uid (4) + gid (4). */
|
||||
#define P8_TAIL_BYTES 16
|
||||
|
||||
/* Smoke test for chunk_deserialize fuzz target */
|
||||
static void test_fuzz_chunk_deserialize() {
|
||||
/* Create a minimal valid chunk to serialize and deserialize */
|
||||
@@ -170,6 +178,272 @@ static void test_fuzz_glob_match() {
|
||||
EXPECT_FALSE(glob_match("*.md", "readme.txt"));
|
||||
}
|
||||
|
||||
/* ---- Deterministic config-frame receive hardening (P8) ----
|
||||
*
|
||||
* The P8 tail (--super / --copy-as) and the identity-map count only parse after
|
||||
* the entire preceding frame validates, which random bytes almost never reach.
|
||||
* These tests capture one valid frame with the production sender and then
|
||||
* mutate/truncate the exact tail bytes. */
|
||||
|
||||
/* Serialize cfg with the production sender into a heap buffer. The frame is
|
||||
* written into a pipe (64 KiB kernel buffer, far larger than one config frame)
|
||||
* whose read end is drained afterwards; the required STATUS_OK ack is
|
||||
* pre-loaded into a second pipe, so a single thread suffices. */
|
||||
static bool capture_config_frame(const Config* cfg, unsigned char** out, size_t* out_len) {
|
||||
*out = NULL;
|
||||
*out_len = 0;
|
||||
|
||||
int frame_pipe[2];
|
||||
int status_pipe[2];
|
||||
if (pipe(frame_pipe) != 0)
|
||||
return false;
|
||||
if (pipe(status_pipe) != 0) {
|
||||
close(frame_pipe[0]);
|
||||
close(frame_pipe[1]);
|
||||
return false;
|
||||
}
|
||||
|
||||
int ack = STATUS_OK;
|
||||
bool ok = write(status_pipe[1], &ack, sizeof(ack)) == (ssize_t)sizeof(ack);
|
||||
if (ok) {
|
||||
io_set_fds(status_pipe[0], frame_pipe[1]);
|
||||
io_set_bwlimit(0);
|
||||
ok = config_send(frame_pipe[1], cfg);
|
||||
}
|
||||
close(frame_pipe[1]);
|
||||
close(status_pipe[0]);
|
||||
close(status_pipe[1]);
|
||||
|
||||
unsigned char* buf = NULL;
|
||||
if (ok) {
|
||||
size_t cap = 4096;
|
||||
size_t len = 0;
|
||||
buf = malloc(cap);
|
||||
if (!buf) {
|
||||
ok = false;
|
||||
}
|
||||
while (ok) {
|
||||
if (len == cap) {
|
||||
size_t grown = cap * 2;
|
||||
unsigned char* bigger = realloc(buf, grown);
|
||||
if (!bigger) {
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
buf = bigger;
|
||||
cap = grown;
|
||||
}
|
||||
ssize_t n = read(frame_pipe[0], buf + len, cap - len);
|
||||
if (n > 0) {
|
||||
len += (size_t)n;
|
||||
continue;
|
||||
}
|
||||
if (n < 0 && errno == EINTR)
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
if (ok && len > 0) {
|
||||
*out = buf;
|
||||
*out_len = len;
|
||||
buf = NULL;
|
||||
}
|
||||
}
|
||||
close(frame_pipe[0]);
|
||||
free(buf);
|
||||
return *out != NULL;
|
||||
}
|
||||
|
||||
/* Feed a raw config frame to config_receive over a socketpair. The write half
|
||||
* is shut down (not closed) after the data so the receiver sees EOF but its
|
||||
* STATUS_ERROR replies do not hit a closed peer. */
|
||||
static bool receive_config_frame(const unsigned char* buf, size_t len) {
|
||||
int sv[2];
|
||||
if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) != 0)
|
||||
return false;
|
||||
|
||||
size_t off = 0;
|
||||
while (off < len) {
|
||||
ssize_t n = write(sv[0], buf + off, len - off);
|
||||
if (n > 0) {
|
||||
off += (size_t)n;
|
||||
continue;
|
||||
}
|
||||
if (n < 0 && errno == EINTR)
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
shutdown(sv[0], SHUT_WR);
|
||||
io_set_fds(sv[1], sv[1]);
|
||||
io_set_bwlimit(0);
|
||||
Config* cfg = config_receive(sv[1]);
|
||||
bool accepted = cfg != NULL;
|
||||
config_delete(cfg);
|
||||
close(sv[0]);
|
||||
close(sv[1]);
|
||||
return accepted;
|
||||
}
|
||||
|
||||
static void put_i32(unsigned char* buf, size_t off, int32_t value) {
|
||||
memcpy(buf + off, &value, sizeof(value));
|
||||
}
|
||||
|
||||
static size_t find_bytes(const unsigned char* haystack, size_t haystack_len,
|
||||
const unsigned char* needle, size_t needle_len) {
|
||||
if (needle_len == 0 || haystack_len < needle_len)
|
||||
return SIZE_MAX;
|
||||
for (size_t i = 0; i + needle_len <= haystack_len; i++) {
|
||||
if (memcmp(haystack + i, needle, needle_len) == 0)
|
||||
return i;
|
||||
}
|
||||
return SIZE_MAX;
|
||||
}
|
||||
|
||||
static Config* make_copy_as_config(void) {
|
||||
Config* c = config_create();
|
||||
if (!c)
|
||||
return NULL;
|
||||
c->send_directory = str_dup("/src");
|
||||
c->receive_root_directory = str_dup("/dst");
|
||||
c->copy_as_set = true;
|
||||
c->copy_as_uid = 0;
|
||||
c->copy_as_gid = 0;
|
||||
c->use_metadata = true; /* --copy-as requires the metadata path */
|
||||
return c;
|
||||
}
|
||||
|
||||
/* The P8 tail must reject an out-of-range super_mode, a negative copy-as id and
|
||||
* any truncation inside the tail, while the untouched frame is accepted. */
|
||||
static void test_fuzz_config_receive_p8_tail() {
|
||||
Config* c = make_copy_as_config();
|
||||
EXPECT_NOT_NULL(c);
|
||||
|
||||
unsigned char* frame = NULL;
|
||||
size_t len = 0;
|
||||
bool captured = capture_config_frame(c, &frame, &len);
|
||||
config_delete(c);
|
||||
if (!captured || len <= P8_TAIL_BYTES) {
|
||||
free(frame);
|
||||
EXPECT_TRUE(false);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Baseline: the untouched frame is accepted. */
|
||||
EXPECT_TRUE(receive_config_frame(frame, len));
|
||||
|
||||
unsigned char* mut = malloc(len);
|
||||
EXPECT_NOT_NULL(mut);
|
||||
|
||||
/* super_mode outside the 0..2 tri-state is refused. */
|
||||
memcpy(mut, frame, len);
|
||||
put_i32(mut, len - P8_TAIL_BYTES, 99);
|
||||
EXPECT_FALSE(receive_config_frame(mut, len));
|
||||
put_i32(mut, len - P8_TAIL_BYTES, -1);
|
||||
EXPECT_FALSE(receive_config_frame(mut, len));
|
||||
|
||||
/* A negative (sentinel) and an extreme copy-as uid/gid are refused. */
|
||||
memcpy(mut, frame, len);
|
||||
put_i32(mut, len - P8_TAIL_BYTES, SUPER_MODE_AUTO);
|
||||
put_i32(mut, len - P8_TAIL_BYTES + 4, 1);
|
||||
put_i32(mut, len - P8_TAIL_BYTES + 8, -1);
|
||||
put_i32(mut, len - P8_TAIL_BYTES + 12, 0);
|
||||
EXPECT_FALSE(receive_config_frame(mut, len));
|
||||
put_i32(mut, len - P8_TAIL_BYTES + 8, 0);
|
||||
put_i32(mut, len - P8_TAIL_BYTES + 12, INT32_MIN);
|
||||
EXPECT_FALSE(receive_config_frame(mut, len));
|
||||
|
||||
/* A presence int that is not a wire bool is refused. */
|
||||
memcpy(mut, frame, len);
|
||||
put_i32(mut, len - P8_TAIL_BYTES, SUPER_MODE_AUTO);
|
||||
put_i32(mut, len - P8_TAIL_BYTES + 4, 2);
|
||||
EXPECT_FALSE(receive_config_frame(mut, len));
|
||||
|
||||
/* Truncating anywhere inside the P8 tail is refused. */
|
||||
EXPECT_FALSE(receive_config_frame(frame, len - 2));
|
||||
EXPECT_FALSE(receive_config_frame(frame, len - P8_TAIL_BYTES));
|
||||
|
||||
free(mut);
|
||||
free(frame);
|
||||
}
|
||||
|
||||
/* A huge or negative --usermap count must be refused up front, never driving a
|
||||
* giant allocation. The count is located by searching for a sentinel entry. */
|
||||
static void test_fuzz_config_receive_huge_map_count() {
|
||||
Config* c = make_copy_as_config();
|
||||
EXPECT_NOT_NULL(c);
|
||||
int32_t sentinel_from = 0x11223344;
|
||||
int32_t sentinel_to = 0x55667788;
|
||||
c->usermap = malloc(sizeof(IdentityMap));
|
||||
if (!c->usermap) {
|
||||
config_delete(c);
|
||||
EXPECT_TRUE(false);
|
||||
return;
|
||||
}
|
||||
c->usermap_count = 1;
|
||||
c->usermap[0].from = sentinel_from;
|
||||
c->usermap[0].to = sentinel_to;
|
||||
|
||||
unsigned char* frame = NULL;
|
||||
size_t len = 0;
|
||||
bool captured = capture_config_frame(c, &frame, &len);
|
||||
config_delete(c);
|
||||
if (!captured) {
|
||||
EXPECT_TRUE(false);
|
||||
return;
|
||||
}
|
||||
|
||||
unsigned char pattern[8];
|
||||
memcpy(pattern, &sentinel_from, sizeof(sentinel_from));
|
||||
memcpy(pattern + sizeof(sentinel_from), &sentinel_to, sizeof(sentinel_to));
|
||||
size_t entry_off = find_bytes(frame, len, pattern, sizeof(pattern));
|
||||
if (entry_off == SIZE_MAX || entry_off < sizeof(int32_t)) {
|
||||
free(frame);
|
||||
EXPECT_TRUE(false);
|
||||
return;
|
||||
}
|
||||
size_t count_off = entry_off - sizeof(int32_t);
|
||||
|
||||
/* Baseline accepted. */
|
||||
EXPECT_TRUE(receive_config_frame(frame, len));
|
||||
|
||||
unsigned char* mut = malloc(len);
|
||||
EXPECT_NOT_NULL(mut);
|
||||
memcpy(mut, frame, len);
|
||||
put_i32(mut, count_off, INT_MAX);
|
||||
EXPECT_FALSE(receive_config_frame(mut, len));
|
||||
put_i32(mut, count_off, -1);
|
||||
EXPECT_FALSE(receive_config_frame(mut, len));
|
||||
put_i32(mut, count_off, MAX_IDENTITY_MAP + 1);
|
||||
EXPECT_FALSE(receive_config_frame(mut, len));
|
||||
|
||||
free(mut);
|
||||
free(frame);
|
||||
}
|
||||
|
||||
/* A mismatched version and a matching version followed by a wrong-order field
|
||||
* (an int that is not a wire bool) are both refused at/just after the gate. */
|
||||
static void test_fuzz_config_receive_version_gate() {
|
||||
unsigned char buf[64];
|
||||
|
||||
size_t off = 0;
|
||||
const char* bad_version = "1.2.3";
|
||||
size_t bad_len = strlen(bad_version);
|
||||
memcpy(buf + off, &bad_len, sizeof(bad_len));
|
||||
off += sizeof(bad_len);
|
||||
memcpy(buf + off, bad_version, bad_len);
|
||||
off += bad_len;
|
||||
EXPECT_FALSE(receive_config_frame(buf, off));
|
||||
|
||||
off = 0;
|
||||
size_t good_len = strlen(PROTOCOL_VERSION);
|
||||
memcpy(buf + off, &good_len, sizeof(good_len));
|
||||
off += sizeof(good_len);
|
||||
memcpy(buf + off, PROTOCOL_VERSION, good_len);
|
||||
off += good_len;
|
||||
put_i32(buf, off, -1);
|
||||
off += sizeof(int32_t);
|
||||
EXPECT_FALSE(receive_config_frame(buf, off));
|
||||
}
|
||||
|
||||
void test_fuzz_smoke() {
|
||||
test_fuzz_chunk_deserialize();
|
||||
test_fuzz_compress_decompress();
|
||||
@@ -177,4 +451,7 @@ void test_fuzz_smoke() {
|
||||
test_fuzz_metadata_from_buf();
|
||||
test_fuzz_delta_signature_deserialize();
|
||||
test_fuzz_glob_match();
|
||||
test_fuzz_config_receive_p8_tail();
|
||||
test_fuzz_config_receive_huge_map_count();
|
||||
test_fuzz_config_receive_version_gate();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user