From 1e3b123bcfd56df93b767f5c322871cd0ca2e75f Mon Sep 17 00:00:00 2001 From: "Chris (ChrisJr404)" <11917633+ChrisJr404@users.noreply.github.com> Date: Mon, 17 Aug 2026 21:20:40 -0400 Subject: [PATCH] Expose RegisterPostProcessor in the libprotobuf-mutator wrapper --- .../atheris_libprotobuf_mutator/__init__.py | 1 + .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 193 bytes .../__pycache__/helpers.cpython-313.pyc | Bin 0 -> 4266 bytes .../proto_fuzz_test.cpython-313.pyc | Bin 0 -> 2450 bytes .../atheris_libprotobuf_mutator/helpers.py | 27 ++++++++++++++++++ .../atheris_libprotobuf_mutator/mutator.cc | 15 ++++++++++ .../proto_fuzz_test.py | 22 ++++++++++++++ 7 files changed, 65 insertions(+) create mode 100644 contrib/libprotobuf_mutator/atheris_libprotobuf_mutator/__pycache__/__init__.cpython-313.pyc create mode 100644 contrib/libprotobuf_mutator/atheris_libprotobuf_mutator/__pycache__/helpers.cpython-313.pyc create mode 100644 contrib/libprotobuf_mutator/atheris_libprotobuf_mutator/__pycache__/proto_fuzz_test.cpython-313.pyc diff --git a/contrib/libprotobuf_mutator/atheris_libprotobuf_mutator/__init__.py b/contrib/libprotobuf_mutator/atheris_libprotobuf_mutator/__init__.py index 05698233..62f7fc95 100644 --- a/contrib/libprotobuf_mutator/atheris_libprotobuf_mutator/__init__.py +++ b/contrib/libprotobuf_mutator/atheris_libprotobuf_mutator/__init__.py @@ -1 +1,2 @@ +from .helpers import RegisterPostProcessor from .helpers import Setup diff --git a/contrib/libprotobuf_mutator/atheris_libprotobuf_mutator/__pycache__/__init__.cpython-313.pyc b/contrib/libprotobuf_mutator/atheris_libprotobuf_mutator/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..edfd02d5c1d8ee5fb5c78400308b3bad9bff357a GIT binary patch literal 193 zcmey&%ge<81nQfcv)q96V-N=hn4pZ$GC;;uhG2$ZMsEf$#v(=q5St0eW-4M*U`S`y zWO>O5RHDgvOEf4oJ+rtZwJ0FJxFnz`KRLCyIKK!e!5W-eQd;1r$$X1FBQ>WWwWzp= z8K|X*1xT!9_zWZ&ZgI!QXXa&=#K-FuRNmsS$<0qG%}KQ@;snZpj4$Q}5+9fu85!>~ MXx(FwDq;un0WX&;`v3p{ literal 0 HcmV?d00001 diff --git a/contrib/libprotobuf_mutator/atheris_libprotobuf_mutator/__pycache__/helpers.cpython-313.pyc b/contrib/libprotobuf_mutator/atheris_libprotobuf_mutator/__pycache__/helpers.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1e0afcd7b29a241d00085c48918a360340f533d3 GIT binary patch literal 4266 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 literal 0 HcmV?d00001 diff --git a/contrib/libprotobuf_mutator/atheris_libprotobuf_mutator/__pycache__/proto_fuzz_test.cpython-313.pyc b/contrib/libprotobuf_mutator/atheris_libprotobuf_mutator/__pycache__/proto_fuzz_test.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3ca47f9a02d8311657315536988d04a76fd034c9 GIT binary patch literal 2450 zcmcIl&u^L?FX$*-eiE$eVtHPwHNhm@{l=ebe7kNnq3esrpY|OUyt~mI0MSE7+BY*hZ{B^xmNR8xA(W1d}a-h$8R3s*#0H&S;)3^qe(ScTfG^fTRF?20uCeB;|u*LvT_fTJB&=+>? z=Aq8&teAX8i{&Ddnbq*tEgsl@z#C2#f23~+tJSiF&s)_~H8wB;QtjZs^RKqVPFNGxQt2lpN*iIeQ8YMk0^~dBL~YU<5p4nqd{MQWvxG3 zMi$1cLN;D#CR>j9O21#mu32U(rfO;*BL5Hsy)5-8-Y!{H-5_MmW*f(2tEwER(7Y3~`kO@Kz$Hj!8Q^~Xl-3TY*R0M@QV#O&UhV^sA zn{kq)Zju*4r;1+f9~*L=IN8WTVup+HWOO*2CM67k`}F6Q#|>tQIpa_2!b z?9ZZDhb`@3Tbp{QO>HHox3aVC?EGeSzH{j4FWE*~)aRsXa%Yn<@9N#5G==B&QhKQi4_b@cdwY`*~qI3km?)t#J`sVH{yY#~Cy|$#z z^*rQZkENH;uK-ur2g)ogDIc?W_AV5a{lSr$xA5zZqu7A+%w$W@=(z!6q*4|XL6HeE zUsMFqnK)pt__RBpk4Z4R>WP}oVmQ^vlYAU_<=3<`HP4xj-rBi8%0r* z8s4Kdd4LGXJ=f-rOV`MV@0XZCC7&)s9$Sy~uZ&ch|HJX8DT31|*5SFLByu}RNEF(} z!b4*Lt7kh^*i04fE$*l+@%~?#by3CiUlIS4luYw;e6eP|z~JnhS@XquMea$Zc!Wav5p z)XB?eQYSk~>h_86PyBcipJ^=@W$0f7jXdE1 literal 0 HcmV?d00001 diff --git a/contrib/libprotobuf_mutator/atheris_libprotobuf_mutator/helpers.py b/contrib/libprotobuf_mutator/atheris_libprotobuf_mutator/helpers.py index 8abc08f9..e8dbab72 100644 --- a/contrib/libprotobuf_mutator/atheris_libprotobuf_mutator/helpers.py +++ b/contrib/libprotobuf_mutator/atheris_libprotobuf_mutator/helpers.py @@ -74,3 +74,30 @@ def TestOneProtoInputImpl(data: bytes): custom_mutator=_CustomMutator, custom_crossover=_CustomCrossOver, **kwargs) + + +def RegisterPostProcessor(proto: Callable[..., Any], + callback: Callable[[Any, int], Any]): + """Register a post-processor that runs after every mutation of `proto`. + + libprotobuf-mutator mutates fields blindly, so it can produce messages that + break invariants the mutator knows nothing about (a length field that has to + match a repeated field, a checksum, an enum that's really a bitmask, ...). + A post-processor gets a chance to patch those up before the message reaches + your test function. + + The callback receives the freshly mutated message and the mutation seed. Edit + the message in place, or return a new one of the same type. Returning None + keeps whatever edits you made in place. Registration is global per message + type, so call this once before atheris.Fuzz(). + + Args: + proto: the protobuf message type to post-process. + callback: called as callback(message, seed) after each mutation. + """ + + def _wrapped(message: Any, seed: int): + result = callback(message, seed) + return message if result is None else result + + _mutator.RegisterPostProcessor(proto(), _wrapped) diff --git a/contrib/libprotobuf_mutator/atheris_libprotobuf_mutator/mutator.cc b/contrib/libprotobuf_mutator/atheris_libprotobuf_mutator/mutator.cc index 36b68c19..58529b3e 100644 --- a/contrib/libprotobuf_mutator/atheris_libprotobuf_mutator/mutator.cc +++ b/contrib/libprotobuf_mutator/atheris_libprotobuf_mutator/mutator.cc @@ -95,6 +95,21 @@ PYBIND11_MODULE(_mutator, m) { } return std::nullopt; }); + m.def("RegisterPostProcessor", + [](std::unique_ptr prototype, py::function callback) { + const protobuf::Descriptor* descriptor = prototype->GetDescriptor(); + libfuzzer::RegisterPostProcessor( + descriptor, + [callback](protobuf::Message* message, unsigned int seed) { + py::gil_scoped_acquire gil; + py::object result = callback(message, seed); + if (!result.is_none()) { + auto updated = + result.cast>(); + message->CopyFrom(*updated); + } + }); + }); } } // namespace protobuf_mutator diff --git a/contrib/libprotobuf_mutator/atheris_libprotobuf_mutator/proto_fuzz_test.py b/contrib/libprotobuf_mutator/atheris_libprotobuf_mutator/proto_fuzz_test.py index 1a43a46c..5c69138c 100644 --- a/contrib/libprotobuf_mutator/atheris_libprotobuf_mutator/proto_fuzz_test.py +++ b/contrib/libprotobuf_mutator/atheris_libprotobuf_mutator/proto_fuzz_test.py @@ -22,6 +22,28 @@ def testSimpleProtoComparison(self): expected_output=b"Solved", timeout=60) + def testPostProcessor(self): + # The post-processor rewrites every mutated message to the solving value, so + # the comparison fires on the first input rather than waiting for the + # mutator to stumble onto "abc" on its own. If the post-processor's edits + # didn't make it back into the message the fuzzer feeds the harness, this + # would time out instead. + def setup_with_post_processor(argv, test_one_input, **kwargs): + def force_value(msg, seed): + del seed + msg.value = "abc" + + atheris_libprotobuf_mutator.RegisterPostProcessor( + wrappers_pb2.StringValue, force_value) + return atheris_libprotobuf_mutator.Setup(argv, test_one_input, **kwargs) + + fuzz_test_lib.run_fuzztest( + simple_proto_comparison, + custom_setup=setup_with_post_processor, + setup_kwargs={"proto": wrappers_pb2.StringValue}, + expected_output=b"Solved", + timeout=60) + if __name__ == "__main__": unittest.main()