diff --git a/Makefile b/Makefile index 01df3e8..9845b33 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -all: fuzzer-binascii fuzzer-html fuzzer-email fuzzer-httpclient fuzzer-json fuzzer-difflib fuzzer-csv fuzzer-decode fuzzer-ast fuzzer-tarfile fuzzer-tarfile-hypothesis fuzzer-zipfile fuzzer-zipfile-hypothesis fuzzer-re fuzzer-configparser fuzzer-tomllib fuzzer-plistlib fuzzer-xml fuzzer-zoneinfo +all: fuzzer-binascii fuzzer-html fuzzer-email fuzzer-httpclient fuzzer-json fuzzer-difflib fuzzer-csv fuzzer-decode fuzzer-ast fuzzer-tarfile fuzzer-tarfile-hypothesis fuzzer-zipfile fuzzer-zipfile-hypothesis fuzzer-re fuzzer-configparser fuzzer-tomllib fuzzer-plistlib fuzzer-xml fuzzer-zoneinfo fuzzer-marshal fuzzer-pickle PYTHON_CONFIG_PATH=$(CPYTHON_INSTALL_PATH)/bin/python3-config CXXFLAGS += $(shell $(PYTHON_CONFIG_PATH) --cflags) @@ -42,3 +42,8 @@ fuzzer-xml: clang++ $(CXXFLAGS) $(LIB_FUZZING_ENGINE) -std=c++17 fuzzer.cpp -DPYTHON_HARNESS_PATH="\"xml.py\"" -ldl $(LDFLAGS) -o fuzzer-xml fuzzer-zoneinfo: clang++ $(CXXFLAGS) $(LIB_FUZZING_ENGINE) -std=c++17 fuzzer.cpp -DPYTHON_HARNESS_PATH="\"zoneinfo.py\"" -ldl $(LDFLAGS) -o fuzzer-zoneinfo + +fuzzer-marshal: + clang++ $(CXXFLAGS) $(LIB_FUZZING_ENGINE) -std=c++17 fuzzer.cpp -DPYTHON_HARNESS_PATH="\"marshal.py\"" -ldl $(LDFLAGS) -o fuzzer-marshal +fuzzer-pickle: + clang++ $(CXXFLAGS) $(LIB_FUZZING_ENGINE) -std=c++17 fuzzer.cpp -DPYTHON_HARNESS_PATH="\"pickle.py\"" -ldl $(LDFLAGS) -o fuzzer-pickle diff --git a/corp-marshal/big_tuple_v4.marshal b/corp-marshal/big_tuple_v4.marshal new file mode 100644 index 0000000..90dd1b2 Binary files /dev/null and b/corp-marshal/big_tuple_v4.marshal differ diff --git a/corp-marshal/code_nested_v4.marshal b/corp-marshal/code_nested_v4.marshal new file mode 100644 index 0000000..a6e0889 Binary files /dev/null and b/corp-marshal/code_nested_v4.marshal differ diff --git a/corp-marshal/code_object_v4.marshal b/corp-marshal/code_object_v4.marshal new file mode 100644 index 0000000..c3e16fc Binary files /dev/null and b/corp-marshal/code_object_v4.marshal differ diff --git a/corp-marshal/containers_v2.marshal b/corp-marshal/containers_v2.marshal new file mode 100644 index 0000000..a30df32 Binary files /dev/null and b/corp-marshal/containers_v2.marshal differ diff --git a/corp-marshal/containers_v3.marshal b/corp-marshal/containers_v3.marshal new file mode 100644 index 0000000..30eff82 Binary files /dev/null and b/corp-marshal/containers_v3.marshal differ diff --git a/corp-marshal/containers_v4.marshal b/corp-marshal/containers_v4.marshal new file mode 100644 index 0000000..1839a3c Binary files /dev/null and b/corp-marshal/containers_v4.marshal differ diff --git a/corp-marshal/float_variants_v4.marshal b/corp-marshal/float_variants_v4.marshal new file mode 100644 index 0000000..d430ae2 Binary files /dev/null and b/corp-marshal/float_variants_v4.marshal differ diff --git a/corp-marshal/negative_and_huge_ints_v4.marshal b/corp-marshal/negative_and_huge_ints_v4.marshal new file mode 100644 index 0000000..198f649 Binary files /dev/null and b/corp-marshal/negative_and_huge_ints_v4.marshal differ diff --git a/corp-marshal/recursive_list_v4.marshal b/corp-marshal/recursive_list_v4.marshal new file mode 100644 index 0000000..b4f6d3c Binary files /dev/null and b/corp-marshal/recursive_list_v4.marshal differ diff --git a/corp-marshal/strings_v2.marshal b/corp-marshal/strings_v2.marshal new file mode 100644 index 0000000..5bd92b2 Binary files /dev/null and b/corp-marshal/strings_v2.marshal differ diff --git a/corp-marshal/strings_v3.marshal b/corp-marshal/strings_v3.marshal new file mode 100644 index 0000000..168cd57 Binary files /dev/null and b/corp-marshal/strings_v3.marshal differ diff --git a/corp-marshal/strings_v4.marshal b/corp-marshal/strings_v4.marshal new file mode 100644 index 0000000..6a0efd9 Binary files /dev/null and b/corp-marshal/strings_v4.marshal differ diff --git a/corp-marshal/types_v2.marshal b/corp-marshal/types_v2.marshal new file mode 100644 index 0000000..f26caf7 Binary files /dev/null and b/corp-marshal/types_v2.marshal differ diff --git a/corp-marshal/types_v3.marshal b/corp-marshal/types_v3.marshal new file mode 100644 index 0000000..0c289c6 Binary files /dev/null and b/corp-marshal/types_v3.marshal differ diff --git a/corp-marshal/types_v4.marshal b/corp-marshal/types_v4.marshal new file mode 100644 index 0000000..fdab933 Binary files /dev/null and b/corp-marshal/types_v4.marshal differ diff --git a/corp-pickle/basic_dict_p0.pickle b/corp-pickle/basic_dict_p0.pickle new file mode 100644 index 0000000..70f4a59 --- /dev/null +++ b/corp-pickle/basic_dict_p0.pickle @@ -0,0 +1,27 @@ +(dp0 +Valpha +p1 +I1 +sVbeta +p2 +(lp3 +F2.5 +aVx +p4 +ac_codecs +encode +p5 +(Vy +p6 +Vlatin1 +p7 +tp8 +Rp9 +aNaI01 +aI00 +asVgamma +p10 +(I1 +I2 +tp11 +s. \ No newline at end of file diff --git a/corp-pickle/basic_dict_p1.pickle b/corp-pickle/basic_dict_p1.pickle new file mode 100644 index 0000000..4e0833c Binary files /dev/null and b/corp-pickle/basic_dict_p1.pickle differ diff --git a/corp-pickle/basic_dict_p2.pickle b/corp-pickle/basic_dict_p2.pickle new file mode 100644 index 0000000..8aa587d Binary files /dev/null and b/corp-pickle/basic_dict_p2.pickle differ diff --git a/corp-pickle/basic_dict_p3.pickle b/corp-pickle/basic_dict_p3.pickle new file mode 100644 index 0000000..138916f Binary files /dev/null and b/corp-pickle/basic_dict_p3.pickle differ diff --git a/corp-pickle/basic_dict_p4.pickle b/corp-pickle/basic_dict_p4.pickle new file mode 100644 index 0000000..514bbed Binary files /dev/null and b/corp-pickle/basic_dict_p4.pickle differ diff --git a/corp-pickle/basic_dict_p5.pickle b/corp-pickle/basic_dict_p5.pickle new file mode 100644 index 0000000..980e794 Binary files /dev/null and b/corp-pickle/basic_dict_p5.pickle differ diff --git a/corp-pickle/bytes_and_bytearray.pickle b/corp-pickle/bytes_and_bytearray.pickle new file mode 100644 index 0000000..23f30ad Binary files /dev/null and b/corp-pickle/bytes_and_bytearray.pickle differ diff --git a/corp-pickle/complex_and_ellipsis.pickle b/corp-pickle/complex_and_ellipsis.pickle new file mode 100644 index 0000000..9245e3b Binary files /dev/null and b/corp-pickle/complex_and_ellipsis.pickle differ diff --git a/corp-pickle/empty_stream_variants.pickle b/corp-pickle/empty_stream_variants.pickle new file mode 100644 index 0000000..ebcd9b5 Binary files /dev/null and b/corp-pickle/empty_stream_variants.pickle differ diff --git a/corp-pickle/float_values.pickle b/corp-pickle/float_values.pickle new file mode 100644 index 0000000..3cd750d Binary files /dev/null and b/corp-pickle/float_values.pickle differ diff --git a/corp-pickle/framed_proto4.pickle b/corp-pickle/framed_proto4.pickle new file mode 100644 index 0000000..42a395e Binary files /dev/null and b/corp-pickle/framed_proto4.pickle differ diff --git a/corp-pickle/global_ref_type.pickle b/corp-pickle/global_ref_type.pickle new file mode 100644 index 0000000..f0abfbc Binary files /dev/null and b/corp-pickle/global_ref_type.pickle differ diff --git a/corp-pickle/long_int.pickle b/corp-pickle/long_int.pickle new file mode 100644 index 0000000..b9048d7 Binary files /dev/null and b/corp-pickle/long_int.pickle differ diff --git a/corp-pickle/memo_reuse.pickle b/corp-pickle/memo_reuse.pickle new file mode 100644 index 0000000..59918eb Binary files /dev/null and b/corp-pickle/memo_reuse.pickle differ diff --git a/corp-pickle/neg_int_p2.pickle b/corp-pickle/neg_int_p2.pickle new file mode 100644 index 0000000..7c0dd06 --- /dev/null +++ b/corp-pickle/neg_int_p2.pickle @@ -0,0 +1 @@ +€Jë2¤ø. \ No newline at end of file diff --git a/corp-pickle/nested_p0.pickle b/corp-pickle/nested_p0.pickle new file mode 100644 index 0000000..bf547ba --- /dev/null +++ b/corp-pickle/nested_p0.pickle @@ -0,0 +1,41 @@ +(dp0 +Vlist +p1 +(lp2 +I1 +a(lp3 +I2 +a(lp4 +I3 +a(dp5 +Vdeep +p6 +(I4 +I5 +tp7 +saaasVset +p8 +c__builtin__ +set +p9 +((lp10 +I1 +aI2 +aI3 +atp11 +Rp12 +sVfrozenset +p13 +c__builtin__ +frozenset +p14 +((lp15 +Va +p16 +aVb +p17 +aVc +p18 +atp19 +Rp20 +s. \ No newline at end of file diff --git a/corp-pickle/nested_p1.pickle b/corp-pickle/nested_p1.pickle new file mode 100644 index 0000000..b09ac0c Binary files /dev/null and b/corp-pickle/nested_p1.pickle differ diff --git a/corp-pickle/nested_p2.pickle b/corp-pickle/nested_p2.pickle new file mode 100644 index 0000000..ff66986 Binary files /dev/null and b/corp-pickle/nested_p2.pickle differ diff --git a/corp-pickle/nested_p3.pickle b/corp-pickle/nested_p3.pickle new file mode 100644 index 0000000..3c66a18 Binary files /dev/null and b/corp-pickle/nested_p3.pickle differ diff --git a/corp-pickle/nested_p4.pickle b/corp-pickle/nested_p4.pickle new file mode 100644 index 0000000..1bededb Binary files /dev/null and b/corp-pickle/nested_p4.pickle differ diff --git a/corp-pickle/nested_p5.pickle b/corp-pickle/nested_p5.pickle new file mode 100644 index 0000000..8283c4c Binary files /dev/null and b/corp-pickle/nested_p5.pickle differ diff --git a/corp-pickle/proto0_text.pickle b/corp-pickle/proto0_text.pickle new file mode 100644 index 0000000..794fd76 --- /dev/null +++ b/corp-pickle/proto0_text.pickle @@ -0,0 +1,12 @@ +(dp0 +Vkey +p1 +Vvalue +p2 +sVn +p3 +(lp4 +I1 +aI2 +aI3 +as. \ No newline at end of file diff --git a/corp-pickle/recursive_list.pickle b/corp-pickle/recursive_list.pickle new file mode 100644 index 0000000..aad58cf Binary files /dev/null and b/corp-pickle/recursive_list.pickle differ diff --git a/corp-pickle/stopiteration_and_range.pickle b/corp-pickle/stopiteration_and_range.pickle new file mode 100644 index 0000000..c2b9ccb Binary files /dev/null and b/corp-pickle/stopiteration_and_range.pickle differ diff --git a/corp-pickle/unicode_strs.pickle b/corp-pickle/unicode_strs.pickle new file mode 100644 index 0000000..58312d5 Binary files /dev/null and b/corp-pickle/unicode_strs.pickle differ diff --git a/fuzz_targets.txt b/fuzz_targets.txt index b016889..98cd45c 100644 --- a/fuzz_targets.txt +++ b/fuzz_targets.txt @@ -8,6 +8,8 @@ email email.py html html.py httpclient httpclient.py json json.py +marshal marshal.py +pickle pickle.py plistlib plist.py re re.py tarfile tarfile.py diff --git a/fuzzer-marshal.dict b/fuzzer-marshal.dict new file mode 100644 index 0000000..3ac1e4e --- /dev/null +++ b/fuzzer-marshal.dict @@ -0,0 +1,53 @@ +# marshal type codes (with and without the 0x80 REF flag) and structure tokens +"N" +"F" +"T" +"S" +"i" +"I" +"f" +"g" +"x" +"y" +"A" +"s" +"t" +"u" +"r" +"e" +"c" +"(" +"[" +"{" +"<" +">" +")" +"0" +"C" +"w" +"\x80" +"\x81" +"\x84" +"\x88" +"\x89" +"\x8a" +"\x8d" +"\x8e" +"\x91" +"\xa0" +"\xa1" +"\xa2" +"\xa3" +"\xa4" +"\xa5" +"\xa6" +"\xa7" +"\xa8" +"\xa9" +"\xc0" +"\xc1" +"\xe0" +"\xe9" +"ab" +"aaa" +"\x00\x01" diff --git a/fuzzer-pickle.dict b/fuzzer-pickle.dict new file mode 100644 index 0000000..2e705ef --- /dev/null +++ b/fuzzer-pickle.dict @@ -0,0 +1,71 @@ +# pickle opcode bytes (protocol 0-5) and typical atoms +"\x80" +"\x80\x02" +"\x80\x04" +"\x80\x05" +"(" +")" +"]" +"}" +"." +"t" +"G" +"X" +"U" +"T" +"V" +"S" +"C" +"B" +"L" +"K" +"M" +"J" +"F" +"I" +"o" +"i" +"R" +"P" +"Q" +"N" +"e" +"a" +"s" +"u" +"r" +"p" +"q" +"h" +"j" +"0" +"1" +"g" +"f" +"\x8a" +"\x8b" +"\x8c" +"\x8d" +"\x8e" +"\x8f" +"\x90" +"\x91" +"\x92" +"\x93" +"\x94" +"\x95" +"\x96" +"\x97" +"\x98" +"builtins" +"__main__" +"copyreg" +"_reconstructor" +"collections" +"OrderedDict" +"defaultdict" +"encode" +"loads" +"\x8cbuiltins" +"\x8cdict" +"\x94" diff --git a/marshal.py b/marshal.py new file mode 100644 index 0000000..b0d9f22 --- /dev/null +++ b/marshal.py @@ -0,0 +1,30 @@ +"""Fuzz marshal.loads() over untrusted bytes. + +marshal is the deserialiser behind .pyc files and marshal.dumps()/loads(); +unmarshalling attacker-supplied bytes must stay memory-safe: invalid type +tags, negative or oversized length fields, dangling ref tags and malformed +code objects are all attacker-controlled. Input is capped at 1 MiB; the +deserialisation itself is expected to reject malformed streams with +EOFError/ValueError/TypeError/RecursionError/MemoryError -- anything else +propagates and aborts the fuzzer as a finding. +""" +import marshal + +MAX_MARSHAL_TEST_SIZE = 0x100000 # 1 MiB, matching the other size-capped targets + + +def FuzzerRunOne(FuzzerInput): + if len(FuzzerInput) > MAX_MARSHAL_TEST_SIZE: + return + try: + marshal.loads(FuzzerInput) + except EOFError: + pass + except ValueError: + pass + except TypeError: + pass + except RecursionError: + pass + except MemoryError: + pass diff --git a/pickle.py b/pickle.py new file mode 100644 index 0000000..a46bb01 --- /dev/null +++ b/pickle.py @@ -0,0 +1,31 @@ +"""Fuzz the C unpickler over untrusted bytes. + +pickle is the classic "never unpickle untrusted data" surface: the load +loop lives in the C accelerator _pickle.Unpickler. We subclass it only to +disable find_class, so mutated payloads cannot import arbitrary modules or +execute code inside the fuzzer process while every opcode-handling code +path (stack/memo manipulation, string and framing parsing, object +reconstruction of built-in types) stays covered. Invalid streams raise +UnpicklingError/EOFError, and object reconstruction (REDUCE, SETSTATE, +APPEND on built-in containers) can raise arbitrary exceptions -- those are +expected for attacker bytes; memory-safety bugs in the opcode machinery +still crash the sanitiser. +""" +import _pickle +import io + +MAX_PICKLE_TEST_SIZE = 0x100000 # 1 MiB + + +class FuzzerUnpickler(_pickle.Unpickler): + def find_class(self, module, name): + raise _pickle.UnpicklingError("find_class is disabled") + + +def FuzzerRunOne(FuzzerInput): + if len(FuzzerInput) > MAX_PICKLE_TEST_SIZE: + return + try: + FuzzerUnpickler(io.BytesIO(FuzzerInput)).load() + except Exception: + pass