-
Notifications
You must be signed in to change notification settings - Fork 225
PROTON-2957: fuzz-message-decode: exercise the codec on decoded content #450
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,11 +20,46 @@ | |
| */ | ||
|
|
||
| #include <stdint.h> | ||
| #include <stdlib.h> | ||
|
|
||
| #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); | ||
| } | ||
|
Comment on lines
+54
to
+61
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Both Not a functional problem today — both calls are harmless no-ops here — but the extra defensive rewinds make it harder for a future reader to tell whether the pre/post position is actually load-bearing. If this helper is ever reused in a context or call order where the rewind does matter, the redundant pair could mask that. Worth dropping unless there's a reason to keep them explicit. |
||
|
|
||
| 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); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The return value of |
||
| free(buf.start); | ||
|
Comment on lines
+77
to
+81
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This encodes the decoded message but discards the result ( Might be worth a follow-up that decodes |
||
| } | ||
| if (msg != NULL) { | ||
| pn_message_free(msg); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit:
pni_is used throughoutc/src/coreas the naming convention for that library's own private/internal symbols (e.g.pni_switch_to_data,pni_switch_to_raw_bytes, both referenced in the comment above). Reusing it for astatichelper local to this fuzz harness is a little confusing when grepping for internal core symbols. A different prefix (or none, since it's alreadystatic) would avoid the naming collision in spirit.