From ce3121716f864818929734594c50d656bedcc70f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jiri=20Dan=C4=9Bk?= Date: Tue, 18 Aug 2026 11:27:55 +0200 Subject: [PATCH] PROTON-2957: fuzz-message-decode: exercise the codec on decoded content pn_message_decode() only scans the wire-level section framing (header, properties, annotations, application-properties, body) and stores each section's raw, undecoded bytes on the pn_message_t. It never invokes the generic AMQP codec (codec.c, decoder.c) on any of that content -- that only happens lazily, the first time an application calls one of the pn_message_{instructions,annotations,properties,body}() accessors. The fuzz-message-decode harness only ever called pn_message_decode() and discarded the result, so none of those accessors were ever invoked and the fuzzer's input never actually reached the codec. Call the four accessors and force a full read-side traversal of each resulting pn_data_t via pn_data_format(), then round-trip the message back to bytes via pn_message_encode2() to also exercise the encoder on the same content. This implements the harness's own long-standing "FUTURE: do something like encode msg and compare again with Data" comment. --- c/tests/fuzz/fuzz-message-decode.c | 47 +++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/c/tests/fuzz/fuzz-message-decode.c b/c/tests/fuzz/fuzz-message-decode.c index 30b9076e37..da285183ce 100644 --- a/c/tests/fuzz/fuzz-message-decode.c +++ b/c/tests/fuzz/fuzz-message-decode.c @@ -20,11 +20,46 @@ */ #include +#include #include "proton/message.h" #include "libFuzzingEngine.h" +/* + * pn_message_decode() (c/src/core/message.c) only scans the wire-level + * section framing (header / properties / delivery-annotations / + * message-annotations / application-properties / body) and stashes each + * section's *raw*, undecoded bytes on the pn_message_t. It never calls into + * the generic AMQP codec (c/src/core/codec.c, decoder.c) on any of those + * byte ranges. That only happens lazily -- the first time something calls + * one of the pn_message_{instructions,annotations,properties,body}() + * accessors, which route through pni_switch_to_data() (c/src/core/util.h) + * -> pn_data_decode() -> the real recursive decoder in decoder.c. + * + * Previously this harness only ever called pn_message_decode() and threw + * the result away, so none of those accessors were ever invoked and the + * fuzzer's input bytes never actually reached codec.c/decoder.c/encoder.c. + * + * Force that decode here so the fuzzer's own input bytes actually drive the + * codec, then force a full read-side traversal of each resulting pn_data_t + * via pn_data_format() -- which recursively walks the decoded tree with + * pn_data_next()/pn_data_enter()/pn_data_exit() and the type-specific + * pn_data_get_*() accessors, so nested lists/maps/arrays/described values + * are actually visited and not just the outermost node. Finally re-encode + * the message (pn_message_encode2()) to drive the corresponding encoder.c + * paths on the same decoded content -- this is the harness's own + * long-standing "FUTURE" comment, now implemented. + */ +static void pni_force_data_traversal(pn_data_t *data) { + if (!data) return; + pn_data_rewind(data); + char buf[4096]; + size_t size = sizeof(buf); + pn_data_format(data, buf, &size); + pn_data_rewind(data); +} + int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { if (Size < 1) { // pn_message_decode would die on assert @@ -33,7 +68,17 @@ int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { pn_message_t *msg = pn_message(); int ret = pn_message_decode(msg, (const char *)Data, Size); if (ret == 0) { - // FUTURE: do something like encode msg and compare again with Data + // Force real decode + traversal of each lazily-decoded section. + pni_force_data_traversal(pn_message_instructions(msg)); + pni_force_data_traversal(pn_message_annotations(msg)); + pni_force_data_traversal(pn_message_properties(msg)); + pni_force_data_traversal(pn_message_body(msg)); + + // Round-trip the decoded message back to bytes: exercises encoder.c on + // the same fuzzer-controlled content. + pn_rwbytes_t buf = {0, NULL}; + pn_message_encode2(msg, &buf); + free(buf.start); } if (msg != NULL) { pn_message_free(msg);