From 8059dbc6250d42b05766625f001a32dcc3f338fd Mon Sep 17 00:00:00 2001 From: Adam Korczynski Date: Fri, 10 Apr 2026 18:35:28 +0100 Subject: [PATCH] Add fuzzer for mmap module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fuzzes the CPython mmap C module (Modules/mmapmodule.c) by creating a temporary file seeded with fuzzed bytes and memory-mapping it read/write. Drives a sequence of up to ten operations — find, rfind, read, readline, seek, getitem, write, setitem, move, and flush — with fuzzed offsets, lengths, needles, and byte values to exercise bounds handling and buffer management in the native implementation. --- Makefile | 5 ++- fuzz_targets.txt | 1 + mmap.py | 100 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 mmap.py diff --git a/Makefile b/Makefile index 7bbbca4..391826d 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -all : 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-binascii +all : 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-binascii fuzzer-mmap PYTHON_CONFIG_PATH=$(CPYTHON_INSTALL_PATH)/bin/python3-config CXXFLAGS += $(shell $(PYTHON_CONFIG_PATH) --cflags) @@ -43,3 +43,6 @@ fuzzer-zoneinfo: fuzzer-binascii: clang++ $(CXXFLAGS) $(LIB_FUZZING_ENGINE) -std=c++17 fuzzer.cpp -DPYTHON_HARNESS_PATH="\"binascii.py\"" -ldl $(LDFLAGS) -o fuzzer-binascii + +fuzzer-mmap: + clang++ $(CXXFLAGS) $(LIB_FUZZING_ENGINE) -std=c++17 fuzzer.cpp -DPYTHON_HARNESS_PATH="\"mmap.py\"" -ldl $(LDFLAGS) -o fuzzer-mmap diff --git a/fuzz_targets.txt b/fuzz_targets.txt index b016889..8c47064 100644 --- a/fuzz_targets.txt +++ b/fuzz_targets.txt @@ -8,6 +8,7 @@ email email.py html html.py httpclient httpclient.py json json.py +mmap mmap.py plistlib plist.py re re.py tarfile tarfile.py diff --git a/mmap.py b/mmap.py new file mode 100644 index 0000000..e9a2b23 --- /dev/null +++ b/mmap.py @@ -0,0 +1,100 @@ +from fuzzeddataprovider import FuzzedDataProvider +import os +import mmap +import tempfile + +OP_FIND = 0 +OP_RFIND = 1 +OP_READ = 2 +OP_READLINE = 3 +OP_SEEK = 4 +OP_GETITEM = 5 +OP_WRITE = 6 +OP_SETITEM = 7 +OP_MOVE = 8 +OP_FLUSH = 9 + +_OP_MAX = OP_FLUSH + + +# Fuzzes the mmap C module (Modules/mmapmodule.c). Creates a temporary +# file-backed mmap and exercises find, rfind, seek, read, readline, +# getitem, write, setitem, move, and flush operations with fuzzed +# offsets, sizes, and byte content. +def FuzzerRunOne(FuzzerInput): + if len(FuzzerInput) < 1 or len(FuzzerInput) > 0x10000: + return + fdp = FuzzedDataProvider(FuzzerInput) + init_size = ( + fdp.ConsumeIntInRange(1, min(fdp.remaining_bytes(), 4096)) + if fdp.remaining_bytes() > 0 + else 0 + ) + if init_size == 0: + return + init_data = fdp.ConsumeBytes(init_size) + tmpname = None + try: + with tempfile.NamedTemporaryFile(delete=False) as tmp: + tmpname = tmp.name + tmp.write(init_data) + tmp.flush() + + with open(tmpname, "r+b") as f: + mm = mmap.mmap(f.fileno(), 0) + num_ops = fdp.ConsumeIntInRange(1, 10) + for _ in range(num_ops): + if fdp.remaining_bytes() == 0: + break + op = fdp.ConsumeIntInRange(0, _OP_MAX) + if op == OP_FIND: + needle = fdp.ConsumeBytes( + fdp.ConsumeIntInRange(1, min(fdp.remaining_bytes(), 20)) + ) + mm.find(needle) + elif op == OP_RFIND: + needle = fdp.ConsumeBytes( + fdp.ConsumeIntInRange(1, min(fdp.remaining_bytes(), 20)) + ) + mm.rfind(needle) + elif op == OP_READ: + mm.seek(0) + mm.read(fdp.ConsumeIntInRange(0, len(mm))) + elif op == OP_READLINE: + mm.seek(0) + mm.readline() + elif op == OP_SEEK: + pos = fdp.ConsumeIntInRange(0, max(0, len(mm) - 1)) + mm.seek(pos) + elif op == OP_GETITEM: + if len(mm) > 0: + idx = fdp.ConsumeIntInRange(0, len(mm) - 1) + _ = mm[idx] + elif op == OP_WRITE: + data = fdp.ConsumeBytes( + fdp.ConsumeIntInRange(1, min(fdp.remaining_bytes(), 50)) + ) + pos = fdp.ConsumeIntInRange(0, max(0, len(mm) - len(data))) + mm.seek(pos) + mm.write(data) + elif op == OP_SETITEM: + if len(mm) > 0: + idx = fdp.ConsumeIntInRange(0, len(mm) - 1) + mm[idx] = fdp.ConsumeInt(1) + elif op == OP_MOVE: + if len(mm) > 1: + count = fdp.ConsumeIntInRange(1, len(mm) // 2) + src = fdp.ConsumeIntInRange(0, len(mm) - count) + dest = fdp.ConsumeIntInRange(0, len(mm) - count) + mm.move(dest, src, count) + elif op == OP_FLUSH: + mm.flush() + mm.close() + except Exception: + pass + finally: + if tmpname: + try: + os.unlink(tmpname) + except Exception: + pass