Date Mar 31, 2026

Meta Bug Bounty — Fuzzing "netconsd" for Fun and Profit — Part 2

Understanding your target is the key to finding bugs. While the fuzzer ran in the background, deeper research into netconsole's fragmented message format — specifically the ncfrag header field — revealed the critical flaw in the original harness and led directly to a heap overflow vulnerability.

netconsd fuzzing part 2

Researching the Message Format

netconsd stands for "The Netconsole Daemon" — it parses kernel messages transmitted over UDP. The Linux kernel documentation reveals an extended console format where messages can contain a sequence number and be split into fragments:

# Extended netconsole format: <level>,<sequnum>,<timestamp>,<contflag>;<message text> # If message exceeds ~1000 bytes, it is fragmented with ncfrag: 6,416,1758426,-,ncfrag=0/31;the first chunk, 6,416,1758426,-,ncfrag=16/31; the 2nd chunk.

The Problem: Single-Message Harness

The original harness creates a new ncrx instance, calls ncrx_process() once, then destroys it. This means the fuzzer never exercises the fragment reassembly code — the exact code that contains the heap overflow vulnerability:

Key Insight: The ncfrag handling code in ncrx_process() maintains state across multiple messages, tracking byte offsets and total lengths. By destroying the ncrx instance after every single message, the fragmentation code path was never reached.

Looking at the ncfrag parsing logic in the source shows the dangerous operations:

if (!strcmp(key, "ncfrag")) { unsigned nf_off, nf_len; if (sscanf(val, "%u/%u", &nf_off, &nf_len) != 2) goto einval; if (!msg->text_len || nf_len >= NCRX_LINE_MAX || nf_off >= nf_len || nf_off + msg->text_len > nf_len) goto einval; msg->ncfrag_off = nf_off; msg->ncfrag_len = msg->text_len; msg->ncfrag_left = nf_len - msg->ncfrag_len; // ← tracked across calls msg->text_len = nf_len; }

The Improved Harness

The fix was straightforward: instead of calling ncrx_process() once per AFL loop iteration, split the input on newlines and call it once per line — simulating a real stream of kernel log messages with potential fragmentation:

#include "ncrx.h" #include "string.h" #include <time.h> __AFL_FUZZ_INIT(); int myProcess(const char *payload, uint64_t now_mono, uint64_t now_real, struct ncrx *ncrx, unsigned int length) { char *string = payload; string[length-1] = '\0'; // AFL input may not be null-terminated char *token = strtok(string, "\n"); while (token != NULL) { ncrx_process(token, now_mono, now_real, ncrx); token = strtok(NULL, " "); // continue tokenizing } } int main(int argc, char **argv) { unsigned char *buf = __AFL_FUZZ_TESTCASE_BUF; while (__AFL_LOOP(10000)) { int len = __AFL_FUZZ_TESTCASE_LEN; struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); uint64_t now = ts.tv_sec * 1000 + ts.tv_nsec / 1000000; struct ncrx *ncrx = ncrx_create(NULL); myProcess(buf, now, 0, ncrx, len); // ← now processes MULTIPLE messages ncrx_destroy(ncrx); } }

Result: Heap Overflow Found!

With the updated harness processing multiple messages per ncrx lifecycle, AFL++ quickly discovered crash-inducing inputs that triggered a heap overflow in netconsd's fragment reassembly code. The crash was reproducible against the actual netconsd daemon — confirming a real, exploitable vulnerability in a Meta production system.

The fuzz harness source code is publicly available: github.com/fadyosman/netconsd_ncrx_fuzz

Continue Reading

Part 3 covers corpus generation techniques — using /dev/kmsg and symbolic execution with Klee — plus the full fuzzing setup with multiple AFL++ instances and custom mutators.

https://ZINAD.net/support-page.html

Written by Fady Othman, Co-founder and Director of R&D at ZINAD.