diff --git a/fuzz/Makefile b/fuzz/Makefile new file mode 100644 index 0000000000..0b7d3c3b2b --- /dev/null +++ b/fuzz/Makefile @@ -0,0 +1,53 @@ +# Copyright (C) 2025 Artifex Software, Inc. +# SPDX-License-Identifier: AGPL-3.0-or-later +# +# Makefile for building MuPDF fuzzers locally +# +# Usage: +# make # Build all fuzzers +# make fuzz_image # Build specific fuzzer +# make run_image # Run fuzzer with corpus +# make clean # Remove built fuzzers +# +# Prerequisites: +# - clang with fuzzer support +# - MuPDF libraries built: make -C .. libs HAVE_X11=no HAVE_GLUT=no + +MUPDF_ROOT = .. +CC = clang +CFLAGS = -g -O1 -fno-omit-frame-pointer \ + -fsanitize=fuzzer,address,undefined \ + -I$(MUPDF_ROOT)/include +LDFLAGS = -fsanitize=fuzzer,address,undefined +LIBS = $(MUPDF_ROOT)/build/release/libmupdf.a \ + $(MUPDF_ROOT)/build/release/libmupdf-third.a \ + -lm -lpthread + +FUZZERS = fuzz_archive fuzz_cbz fuzz_cmap fuzz_colorspace fuzz_epub \ + fuzz_filter fuzz_font fuzz_html fuzz_html5 fuzz_image \ + fuzz_json fuzz_path fuzz_pdf_lexer fuzz_pdf_object \ + fuzz_pdf_stream fuzz_stext fuzz_svg fuzz_xml fuzz_xps + +.PHONY: all clean run_% libs + +all: libs $(FUZZERS) + +libs: + $(MAKE) -C $(MUPDF_ROOT) libs HAVE_X11=no HAVE_GLUT=no + +fuzz_%: fuzz_%.c $(MUPDF_ROOT)/build/release/libmupdf.a + $(CC) $(CFLAGS) $< -o $@ $(LDFLAGS) $(LIBS) + +# Run a fuzzer with its corpus (e.g., make run_image) +run_%: fuzz_% + @mkdir -p corpus/$* + ./fuzz_$* corpus/$* $(if $(wildcard corpus/$*/*),,-max_total_time=60) + +clean: + rm -f $(FUZZERS) + rm -rf corpus/*/crash-* corpus/*/oom-* + +# Coverage build (for measuring code coverage) +coverage: CFLAGS = -g -O1 -fprofile-instr-generate -fcoverage-mapping -I$(MUPDF_ROOT)/include +coverage: LDFLAGS = -fprofile-instr-generate -fcoverage-mapping +coverage: clean all diff --git a/fuzz/build.sh b/fuzz/build.sh new file mode 100755 index 0000000000..87bef9d897 --- /dev/null +++ b/fuzz/build.sh @@ -0,0 +1,37 @@ +#!/bin/bash -eu +# Copyright (C) 2025 Artifex Software, Inc. +# SPDX-License-Identifier: AGPL-3.0-or-later +# +# OSS-Fuzz build script for MuPDF fuzzers + +# Build MuPDF static libraries +make -j$(nproc) libs HAVE_X11=no HAVE_GLUT=no + +# List of all 19 fuzzers +FUZZERS="archive cbz cmap colorspace epub filter font html html5 image json path pdf_lexer pdf_object pdf_stream stext svg xml xps" + +# Build each fuzzer +for f in $FUZZERS; do + $CC $CFLAGS -Iinclude \ + fuzz/fuzz_$f.c -o $OUT/fuzz_$f \ + $LIB_FUZZING_ENGINE build/release/libmupdf.a build/release/libmupdf-third.a -lm -lpthread +done + +# Copy dictionaries +if [ -d "fuzz/dictionaries" ]; then + for dict in fuzz/dictionaries/*.dict; do + if [ -f "$dict" ]; then + base=$(basename "$dict" .dict) + cp "$dict" "$OUT/fuzz_${base}.dict" 2>/dev/null || true + fi + done +fi + +# Create seed corpora +for f in $FUZZERS; do + if [ -d "fuzz/corpus/$f" ] && [ "$(ls -A fuzz/corpus/$f 2>/dev/null)" ]; then + zip -jr "$OUT/fuzz_${f}_seed_corpus.zip" "fuzz/corpus/$f/" 2>/dev/null || true + fi +done + +echo "Build complete: $(ls -1 $OUT/fuzz_* | wc -l) artifacts created" diff --git a/fuzz/corpus/archive/minimal.tar b/fuzz/corpus/archive/minimal.tar new file mode 100644 index 0000000000..5aa1f6e7ae Binary files /dev/null and b/fuzz/corpus/archive/minimal.tar differ diff --git a/fuzz/corpus/archive/minimal.zip b/fuzz/corpus/archive/minimal.zip new file mode 100644 index 0000000000..2c8be909ae Binary files /dev/null and b/fuzz/corpus/archive/minimal.zip differ diff --git a/fuzz/corpus/cbz/minimal.cbz b/fuzz/corpus/cbz/minimal.cbz new file mode 100644 index 0000000000..2821dc4b48 Binary files /dev/null and b/fuzz/corpus/cbz/minimal.cbz differ diff --git a/fuzz/corpus/cmap/identity.cmap b/fuzz/corpus/cmap/identity.cmap new file mode 100644 index 0000000000..49bc43eee8 --- /dev/null +++ b/fuzz/corpus/cmap/identity.cmap @@ -0,0 +1,16 @@ +/CIDInit /ProcSet findresource begin +12 dict begin +begincmap +/CIDSystemInfo << /Registry (Adobe) /Ordering (Identity) /Supplement 0 >> def +/CMapName /Identity-H def +/CMapType 1 def +1 begincodespacerange +<0000> +endcodespacerange +1 begincidrange +<0000> 0 +endcidrange +endcmap +CMapName currentdict /CMap defineresource pop +end +end diff --git a/fuzz/corpus/colorspace/cmyk.icc b/fuzz/corpus/colorspace/cmyk.icc new file mode 100644 index 0000000000..fce61bf2bc Binary files /dev/null and b/fuzz/corpus/colorspace/cmyk.icc differ diff --git a/fuzz/corpus/colorspace/gray.icc b/fuzz/corpus/colorspace/gray.icc new file mode 100644 index 0000000000..e9cd5b1229 Binary files /dev/null and b/fuzz/corpus/colorspace/gray.icc differ diff --git a/fuzz/corpus/colorspace/lab.icc b/fuzz/corpus/colorspace/lab.icc new file mode 100644 index 0000000000..df5c56c127 Binary files /dev/null and b/fuzz/corpus/colorspace/lab.icc differ diff --git a/fuzz/corpus/colorspace/rgb.icc b/fuzz/corpus/colorspace/rgb.icc new file mode 100644 index 0000000000..055997234c Binary files /dev/null and b/fuzz/corpus/colorspace/rgb.icc differ diff --git a/fuzz/corpus/epub/minimal.epub b/fuzz/corpus/epub/minimal.epub new file mode 100644 index 0000000000..8dcd00b97a Binary files /dev/null and b/fuzz/corpus/epub/minimal.epub differ diff --git a/fuzz/corpus/filter/flate.bin b/fuzz/corpus/filter/flate.bin new file mode 100644 index 0000000000..c1b0730e01 --- /dev/null +++ b/fuzz/corpus/filter/flate.bin @@ -0,0 +1 @@ +x \ No newline at end of file diff --git a/fuzz/corpus/filter/gzip.gz b/fuzz/corpus/filter/gzip.gz new file mode 100644 index 0000000000..b9d794e16e Binary files /dev/null and b/fuzz/corpus/filter/gzip.gz differ diff --git a/fuzz/corpus/font/droid.ttf b/fuzz/corpus/font/droid.ttf new file mode 100644 index 0000000000..cc7d41d348 Binary files /dev/null and b/fuzz/corpus/font/droid.ttf differ diff --git a/fuzz/corpus/font/noto.otf b/fuzz/corpus/font/noto.otf new file mode 100644 index 0000000000..e7837b14fd Binary files /dev/null and b/fuzz/corpus/font/noto.otf differ diff --git a/fuzz/corpus/html/minimal.html b/fuzz/corpus/html/minimal.html new file mode 100644 index 0000000000..ca38bddbdc --- /dev/null +++ b/fuzz/corpus/html/minimal.html @@ -0,0 +1,5 @@ + + +Test +

Hello

+ diff --git a/fuzz/corpus/html5/minimal.html b/fuzz/corpus/html5/minimal.html new file mode 100644 index 0000000000..ca38bddbdc --- /dev/null +++ b/fuzz/corpus/html5/minimal.html @@ -0,0 +1,5 @@ + + +Test +

Hello

+ diff --git a/fuzz/corpus/image/minimal.jpg b/fuzz/corpus/image/minimal.jpg new file mode 100644 index 0000000000..6f47c3c779 Binary files /dev/null and b/fuzz/corpus/image/minimal.jpg differ diff --git a/fuzz/corpus/image/minimal.png b/fuzz/corpus/image/minimal.png new file mode 100644 index 0000000000..08cd6f2bfd Binary files /dev/null and b/fuzz/corpus/image/minimal.png differ diff --git a/fuzz/corpus/json/complex.json b/fuzz/corpus/json/complex.json new file mode 100644 index 0000000000..42ab3f6f04 --- /dev/null +++ b/fuzz/corpus/json/complex.json @@ -0,0 +1 @@ +{"string":"hello","number":42,"float":3.14,"bool":true,"null":null,"array":[1,2,3],"object":{"nested":"value"}} \ No newline at end of file diff --git a/fuzz/corpus/json/minimal.json b/fuzz/corpus/json/minimal.json new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/fuzz/corpus/json/minimal.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/fuzz/corpus/path/simple.path b/fuzz/corpus/path/simple.path new file mode 100644 index 0000000000..cd1e69630d --- /dev/null +++ b/fuzz/corpus/path/simple.path @@ -0,0 +1 @@ +M 0 0 L 100 100 C 50 50 75 25 100 0 Z \ No newline at end of file diff --git a/fuzz/corpus/pdf/minimal.pdf b/fuzz/corpus/pdf/minimal.pdf new file mode 100644 index 0000000000..8168c6e6f9 Binary files /dev/null and b/fuzz/corpus/pdf/minimal.pdf differ diff --git a/fuzz/corpus/pdf_lexer/minimal.pdf b/fuzz/corpus/pdf_lexer/minimal.pdf new file mode 100644 index 0000000000..8168c6e6f9 Binary files /dev/null and b/fuzz/corpus/pdf_lexer/minimal.pdf differ diff --git a/fuzz/corpus/pdf_object/minimal.pdf b/fuzz/corpus/pdf_object/minimal.pdf new file mode 100644 index 0000000000..8168c6e6f9 Binary files /dev/null and b/fuzz/corpus/pdf_object/minimal.pdf differ diff --git a/fuzz/corpus/pdf_stream/content.stream b/fuzz/corpus/pdf_stream/content.stream new file mode 100644 index 0000000000..b027fcabe0 --- /dev/null +++ b/fuzz/corpus/pdf_stream/content.stream @@ -0,0 +1,5 @@ +BT +/F1 12 Tf +100 700 Td +(Hello World) Tj +ET diff --git a/fuzz/corpus/stext/minimal.pdf b/fuzz/corpus/stext/minimal.pdf new file mode 100644 index 0000000000..8168c6e6f9 Binary files /dev/null and b/fuzz/corpus/stext/minimal.pdf differ diff --git a/fuzz/corpus/svg/blendmodes.svg b/fuzz/corpus/svg/blendmodes.svg new file mode 100644 index 0000000000..00ee732eaf --- /dev/null +++ b/fuzz/corpus/svg/blendmodes.svg @@ -0,0 +1,832 @@ + + + +NormalMultiplyScreenOverlayDarkenLightenColorDodgeColorBurnHardLightSoftLightDifferenceExclusionHueSaturationColorLuminosityDuck in foreground,rainbow in backgroundNormalMultiplyScreenOverlayDarkenLightenColorDodgeColorBurnHardLightSoftLightDifferenceExclusionHueSaturationColorLuminosityRainbow in foreground,duck in background diff --git a/fuzz/corpus/svg/complex.svg b/fuzz/corpus/svg/complex.svg new file mode 100644 index 0000000000..c514efcd28 --- /dev/null +++ b/fuzz/corpus/svg/complex.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + Test + diff --git a/fuzz/corpus/svg/curveTo.svg b/fuzz/corpus/svg/curveTo.svg new file mode 100644 index 0000000000..2cf2bab895 --- /dev/null +++ b/fuzz/corpus/svg/curveTo.svg @@ -0,0 +1,144 @@ + + + + + + + + + + + + + + + Current point + (x1, y1) + (x2, y2) + (x3, y3) + + + + + diff --git a/fuzz/corpus/svg/curveToV.svg b/fuzz/corpus/svg/curveToV.svg new file mode 100644 index 0000000000..ba199fc7ca --- /dev/null +++ b/fuzz/corpus/svg/curveToV.svg @@ -0,0 +1,121 @@ + + + + + + + + + + + + + + Current point + (cx, cy) + (ex, ey) + + + + diff --git a/fuzz/corpus/svg/curveToY.svg b/fuzz/corpus/svg/curveToY.svg new file mode 100644 index 0000000000..16cc419308 --- /dev/null +++ b/fuzz/corpus/svg/curveToY.svg @@ -0,0 +1,121 @@ + + + + + + + + + + + + + + Current point + (cx, cy) + (ex, ey) + + + + diff --git a/fuzz/corpus/svg/discord-mark-blue.svg b/fuzz/corpus/svg/discord-mark-blue.svg new file mode 100644 index 0000000000..ca65400760 --- /dev/null +++ b/fuzz/corpus/svg/discord-mark-blue.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/fuzz/corpus/svg/knockout-isolated.svg b/fuzz/corpus/svg/knockout-isolated.svg new file mode 100644 index 0000000000..c120770007 --- /dev/null +++ b/fuzz/corpus/svg/knockout-isolated.svg @@ -0,0 +1,310 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Isolated + Non-isolated + Knockout + Non-knockout + + + diff --git a/fuzz/corpus/svg/line-caps.svg b/fuzz/corpus/svg/line-caps.svg new file mode 100644 index 0000000000..23d865aa0d --- /dev/null +++ b/fuzz/corpus/svg/line-caps.svg @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + Butt capRound capSquare cap + + + diff --git a/fuzz/corpus/svg/line-ending-styles.svg b/fuzz/corpus/svg/line-ending-styles.svg new file mode 100644 index 0000000000..eac59af959 --- /dev/null +++ b/fuzz/corpus/svg/line-ending-styles.svg @@ -0,0 +1,245 @@ + + + + + + + + + + + + + + + + + + + NoneSquareCircleDiamondButt + + + + OpenArrowClosedArrowROpenArrowRClosedArrowSlash + + + + + + + + + + + + + + + + + + diff --git a/fuzz/corpus/svg/line-joins.svg b/fuzz/corpus/svg/line-joins.svg new file mode 100644 index 0000000000..08eef6706a --- /dev/null +++ b/fuzz/corpus/svg/line-joins.svg @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + Miter joinRound joinBevel join + + + diff --git a/fuzz/corpus/svg/lineTo.svg b/fuzz/corpus/svg/lineTo.svg new file mode 100644 index 0000000000..0507f0068a --- /dev/null +++ b/fuzz/corpus/svg/lineTo.svg @@ -0,0 +1,101 @@ + + + + + + + + + + + + Current point + (x, y) + + + + diff --git a/fuzz/corpus/svg/minimal.svg b/fuzz/corpus/svg/minimal.svg new file mode 100644 index 0000000000..2e1cde54d3 --- /dev/null +++ b/fuzz/corpus/svg/minimal.svg @@ -0,0 +1,4 @@ + + + + diff --git a/fuzz/corpus/svg/miter-limit.svg b/fuzz/corpus/svg/miter-limit.svg new file mode 100644 index 0000000000..f4702988b4 --- /dev/null +++ b/fuzz/corpus/svg/miter-limit.svg @@ -0,0 +1,157 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Miter + + + + + + Limitedmiter + diff --git a/fuzz/corpus/svg/mupdf-icon.svg b/fuzz/corpus/svg/mupdf-icon.svg new file mode 100644 index 0000000000..b07bf4f2a9 --- /dev/null +++ b/fuzz/corpus/svg/mupdf-icon.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/fuzz/corpus/svg/rect.svg b/fuzz/corpus/svg/rect.svg new file mode 100644 index 0000000000..d173df59bb --- /dev/null +++ b/fuzz/corpus/svg/rect.svg @@ -0,0 +1,205 @@ + + + + + + + + + + + + + + (x1, y1) + (x2, y1) + (x2, y2) + (x1, y2) + + + + + + + + diff --git a/fuzz/corpus/xml/minimal.xml b/fuzz/corpus/xml/minimal.xml new file mode 100644 index 0000000000..37ae932348 --- /dev/null +++ b/fuzz/corpus/xml/minimal.xml @@ -0,0 +1,2 @@ + + diff --git a/fuzz/corpus/xml/with_attrs.xml b/fuzz/corpus/xml/with_attrs.xml new file mode 100644 index 0000000000..83ddb501c2 --- /dev/null +++ b/fuzz/corpus/xml/with_attrs.xml @@ -0,0 +1,5 @@ + + + text + + diff --git a/fuzz/corpus/xps/minimal.xps b/fuzz/corpus/xps/minimal.xps new file mode 100644 index 0000000000..3f1ed96bbf Binary files /dev/null and b/fuzz/corpus/xps/minimal.xps differ diff --git a/fuzz/dictionaries/archive.dict b/fuzz/dictionaries/archive.dict new file mode 100644 index 0000000000..b0852b2b06 --- /dev/null +++ b/fuzz/dictionaries/archive.dict @@ -0,0 +1,25 @@ +# Archive format magic bytes for fuzzing + +# ZIP +"PK\x03\x04" +"PK\x05\x06" +"PK\x01\x02" +"PK\x07\x08" + +# TAR +"ustar" +"ustar\x00" +"ustar \x00" + +# GZIP +"\x1f\x8b" + +# Common filenames in archives +"mimetype" +"META-INF/" +"container.xml" +"content.opf" +"toc.ncx" +"nav.xhtml" +"[Content_Types].xml" +"_rels/.rels" diff --git a/fuzz/dictionaries/cmap.dict b/fuzz/dictionaries/cmap.dict new file mode 100644 index 0000000000..2f54384d95 --- /dev/null +++ b/fuzz/dictionaries/cmap.dict @@ -0,0 +1,61 @@ +# CMap (Character Map) dictionary for fuzzing + +# CMap keywords +"/CIDInit" +"/ProcSet" +"findresource" +"begin" +"end" +"def" +"defineresource" +"pop" + +# CMap structure +"/CMapType" +"/CMapName" +"/CIDSystemInfo" +"/Registry" +"/Ordering" +"/Supplement" +"/WMode" +"/UIDOffset" +"/XUID" + +# CMap operators +"begincmap" +"endcmap" +"begincodespacerange" +"endcodespacerange" +"beginbfchar" +"endbfchar" +"beginbfrange" +"endbfrange" +"begincidchar" +"endcidchar" +"begincidrange" +"endcidrange" +"beginnotdefchar" +"endnotdefchar" +"beginnotdefrange" +"endnotdefrange" +"usecmap" +"usefont" + +# Common encodings +"(Adobe)" +"(Identity)" +"(Japan1)" +"(Korea1)" +"(CNS1)" +"(GB1)" +"(UCS)" + +# Hex values +"<0000>" +"" +"<00>" +"" +"<0020>" +"<007E>" +"<4E00>" +"<9FFF>" diff --git a/fuzz/dictionaries/epub.dict b/fuzz/dictionaries/epub.dict new file mode 100644 index 0000000000..149f56d790 --- /dev/null +++ b/fuzz/dictionaries/epub.dict @@ -0,0 +1,122 @@ +# EPUB dictionary for fuzzing + +# ZIP signatures (EPUB is a ZIP package) +"PK\x03\x04" +"PK\x01\x02" +"PK\x05\x06" + +# EPUB structure +"mimetype" +"application/epub+zip" +"META-INF/" +"META-INF/container.xml" +"container.xml" +"OEBPS/" +"content.opf" +"toc.ncx" +"nav.xhtml" + +# EPUB namespaces +"http://www.idpf.org/2007/opf" +"http://purl.org/dc/elements/1.1/" +"http://www.daisy.org/z3986/2005/ncx/" +"http://www.w3.org/1999/xhtml" +"http://www.idpf.org/2007/ops" + +# OPF elements +"" +"" +"" +"" +"" +"" +"" +"

" +"

" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"href=" +"src=" +"class=" +"id=" +"style=" +"type=" +"name=" +"value=" +"width=" +"height=" +"alt=" +"rel=" +"charset=" +"content=" +"http-equiv=" +" " +"<" +">" +"&" +""" +"&#" +"" diff --git a/fuzz/dictionaries/image.dict b/fuzz/dictionaries/image.dict new file mode 100644 index 0000000000..1adcb272fd --- /dev/null +++ b/fuzz/dictionaries/image.dict @@ -0,0 +1,69 @@ +# Image format magic bytes and keywords for fuzzing + +# PNG +"\x89PNG" +"\x0d\x0a\x1a\x0a" +"IHDR" +"PLTE" +"IDAT" +"IEND" +"tEXt" +"zTXt" +"iTXt" +"gAMA" +"cHRM" +"sRGB" +"iCCP" +"bKGD" +"pHYs" +"sBIT" +"tRNS" + +# JPEG +"\xff\xd8\xff" +"\xff\xe0" +"\xff\xe1" +"\xff\xdb" +"\xff\xc0" +"\xff\xc2" +"\xff\xc4" +"\xff\xda" +"\xff\xd9" +"JFIF" +"Exif" + +# GIF +"GIF87a" +"GIF89a" + +# BMP +"BM" + +# TIFF +"II\x2a\x00" +"MM\x00\x2a" + +# PSD +"8BPS" + +# JPEG 2000 +"\x00\x00\x00\x0c\x6a\x50" +"jp2 " +"jpx " +"jpm " + +# JPEG XR +"WMPHOTO" +"II\xbc\x01" + +# PNM +"P1" +"P2" +"P3" +"P4" +"P5" +"P6" +"P7" + +# JBIG2 +"\x97\x4a\x42\x32\x0d\x0a\x1a\x0a" diff --git a/fuzz/dictionaries/json.dict b/fuzz/dictionaries/json.dict new file mode 100644 index 0000000000..2e32e148d6 --- /dev/null +++ b/fuzz/dictionaries/json.dict @@ -0,0 +1,29 @@ +# JSON dictionary for fuzzing +"{" +"}" +"[" +"]" +":" +"," +"true" +"false" +"null" +"\x22" +"\x5c" +"\\n" +"\\t" +"\\r" +"\\b" +"\\f" +"\\/" +"\\u" +"\\u0000" +"\\u001f" +"\\uFFFF" +"0" +"-1" +"1e10" +"1E-10" +"0.0" +"-0" +"1.0e+1" diff --git a/fuzz/dictionaries/pdf.dict b/fuzz/dictionaries/pdf.dict new file mode 100644 index 0000000000..1e159481ba --- /dev/null +++ b/fuzz/dictionaries/pdf.dict @@ -0,0 +1,180 @@ +# PDF dictionary for fuzzing +"%PDF-1." +"%PDF-2." +"%%EOF" +"xref" +"trailer" +"startxref" +"obj" +"endobj" +"stream" +"endstream" +"<<" +">>" +"[" +"]" +"/" +"null" +"true" +"false" +"R" +"/Type" +"/Catalog" +"/Pages" +"/Page" +"/Kids" +"/Count" +"/Parent" +"/MediaBox" +"/CropBox" +"/BleedBox" +"/TrimBox" +"/ArtBox" +"/Resources" +"/Contents" +"/Font" +"/XObject" +"/ExtGState" +"/ColorSpace" +"/Pattern" +"/Shading" +"/ProcSet" +"/Subtype" +"/BaseFont" +"/Encoding" +"/ToUnicode" +"/FontDescriptor" +"/Widths" +"/FirstChar" +"/LastChar" +"/Image" +"/Width" +"/Height" +"/BitsPerComponent" +"/ColorSpace" +"/DeviceRGB" +"/DeviceCMYK" +"/DeviceGray" +"/ICCBased" +"/Indexed" +"/Separation" +"/DeviceN" +"/Filter" +"/FlateDecode" +"/DCTDecode" +"/CCITTFaxDecode" +"/JPXDecode" +"/JBIG2Decode" +"/LZWDecode" +"/ASCII85Decode" +"/ASCIIHexDecode" +"/RunLengthDecode" +"/Crypt" +"/DecodeParms" +"/Length" +"/N" +"/Predictor" +"/Columns" +"/Colors" +"/BPC" +"/K" +"/Rows" +"/BlackIs1" +"/Annots" +"/Outlines" +"/First" +"/Last" +"/Next" +"/Prev" +"/Dest" +"/Title" +"/A" +"/URI" +"/S" +"/GoTo" +"/JavaScript" +"/JS" +"/OpenAction" +"/AcroForm" +"/Fields" +"/SigFlags" +"/Encrypt" +"/ID" +"/Info" +"/Author" +"/Title" +"/Subject" +"/Keywords" +"/Creator" +"/Producer" +"/CreationDate" +"/ModDate" +"/Root" +"/Size" +"/Prev" +"BT" +"ET" +"Tf" +"Td" +"TD" +"Tm" +"T*" +"Tj" +"TJ" +"'" +"\x22" +"Tc" +"Tw" +"Tz" +"TL" +"Ts" +"Tr" +"q" +"Q" +"cm" +"m" +"l" +"c" +"v" +"y" +"h" +"re" +"S" +"s" +"f" +"F" +"f*" +"B" +"B*" +"b" +"b*" +"n" +"W" +"W*" +"cs" +"CS" +"sc" +"SC" +"scn" +"SCN" +"g" +"G" +"rg" +"RG" +"k" +"K" +"gs" +"Do" +"BI" +"ID" +"EI" +"sh" +"d0" +"d1" +"MP" +"DP" +"BMC" +"BDC" +"EMC" +"BX" +"EX" diff --git a/fuzz/dictionaries/svg.dict b/fuzz/dictionaries/svg.dict new file mode 100644 index 0000000000..9316c6aa65 --- /dev/null +++ b/fuzz/dictionaries/svg.dict @@ -0,0 +1,94 @@ +# SVG dictionary for fuzzing +"" +"xmlns" +"http://www.w3.org/2000/svg" +"viewBox" +"width" +"height" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"<" +"/" +"=" +"CDATA[" +"]]>" +"DOCTYPE" +"ENTITY" +"SYSTEM" +"PUBLIC" +"xmlns" +"xml:lang" +"encoding" +"version" +"standalone" +"<" +">" +"&" +"'" +""" +"&#" +"&#x" +"\x3c" +"\x3e" +"\x26" +"\x22" +"\x27" +"\x09" +"\x0a" +"\x0d" diff --git a/fuzz/dictionaries/xps.dict b/fuzz/dictionaries/xps.dict new file mode 100644 index 0000000000..8c3e88bebc --- /dev/null +++ b/fuzz/dictionaries/xps.dict @@ -0,0 +1,92 @@ +# XPS (Open XML Paper Specification) dictionary for fuzzing + +# ZIP signatures (XPS is a ZIP package) +"PK\x03\x04" +"PK\x01\x02" +"PK\x05\x06" + +# XPS document structure +"[Content_Types].xml" +"_rels/.rels" +"FixedDocumentSequence.fdseq" +"FixedDocument.fdoc" +"FixedPage" +".fpage" +".fdseq" +".fdoc" + +# XPS namespaces +"http://schemas.microsoft.com/xps/2005/06" +"http://schemas.openxmlformats.org/package/2006" + +# XPS elements +" +// +// Alternative licensing terms are available from the licensor. +// For commercial licensing, see or contact +// Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco, +// CA 94129, USA, for further information. + +/* + * Fuzzer for MuPDF archive handling (ZIP, TAR) + */ + +#include +#include +#include + +#include + +#define MAX_INPUT_SIZE (1 * 1024 * 1024) /* 1MB limit */ + +static fz_context *ctx = NULL; + +int LLVMFuzzerInitialize(int *argc, char ***argv) +{ + (void)argc; + (void)argv; + ctx = fz_new_context(NULL, NULL, FZ_STORE_DEFAULT); + return 0; +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + fz_stream *stream = NULL; + fz_archive *arch = NULL; + + if (size == 0 || size > MAX_INPUT_SIZE) + return 0; + + fz_var(stream); + fz_var(arch); + + fz_try(ctx) + { + stream = fz_open_memory(ctx, data, size); + arch = fz_open_archive_with_stream(ctx, stream); + + if (arch) + { + int count = fz_count_archive_entries(ctx, arch); + for (int i = 0; i < count && i < 10; i++) /* Limit entries */ + { + const char *name = fz_list_archive_entry(ctx, arch, i); + if (name) + { + fz_buffer *buf = fz_read_archive_entry(ctx, arch, name); + fz_drop_buffer(ctx, buf); + } + } + } + } + fz_always(ctx) + { + fz_drop_archive(ctx, arch); + fz_drop_stream(ctx, stream); + } + fz_catch(ctx) + { + /* Ignore errors - we're fuzzing */ + } + + return 0; +} diff --git a/fuzz/fuzz_cbz.c b/fuzz/fuzz_cbz.c new file mode 100644 index 0000000000..4a4aef0dce --- /dev/null +++ b/fuzz/fuzz_cbz.c @@ -0,0 +1,86 @@ +// Copyright (C) 2025 Artifex Software, Inc. +// +// This file is part of MuPDF. +// +// MuPDF is free software: you can redistribute it and/or modify it under the +// terms of the GNU Affero General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +// details. +// +// You should have received a copy of the GNU Affero General Public License +// along with MuPDF. If not, see +// +// Alternative licensing terms are available from the licensor. +// For commercial licensing, see or contact +// Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco, +// CA 94129, USA, for further information. + +/* + * Fuzzer for MuPDF CBZ (comic book archive) parsing + */ + +#include +#include +#include + +#include + +#define MAX_INPUT_SIZE (1 * 1024 * 1024) /* 1MB limit */ + +static fz_context *ctx = NULL; + +int LLVMFuzzerInitialize(int *argc, char ***argv) +{ + (void)argc; + (void)argv; + ctx = fz_new_context(NULL, NULL, FZ_STORE_DEFAULT); + if (ctx) + fz_register_document_handlers(ctx); + return 0; +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + fz_stream *stream = NULL; + fz_document *doc = NULL; + fz_pixmap *pix = NULL; + int i, n; + + if (size == 0 || size > MAX_INPUT_SIZE) + return 0; + + fz_var(stream); + fz_var(doc); + fz_var(pix); + + fz_try(ctx) + { + stream = fz_open_memory(ctx, data, size); + doc = fz_open_document_with_stream(ctx, "cbz", stream); + + n = fz_count_pages(ctx, doc); + for (i = 0; i < n && i < 3; i++) /* Limit to 3 pages */ + { + pix = fz_new_pixmap_from_page_number(ctx, doc, i, fz_identity, fz_device_rgb(ctx), 0); + fz_drop_pixmap(ctx, pix); + pix = NULL; + } + } + fz_always(ctx) + { + fz_drop_pixmap(ctx, pix); + fz_drop_document(ctx, doc); + fz_drop_stream(ctx, stream); + } + fz_catch(ctx) + { + /* Ignore errors - we're fuzzing */ + } + + return 0; +} diff --git a/fuzz/fuzz_cmap.c b/fuzz/fuzz_cmap.c new file mode 100644 index 0000000000..50d318a0c7 --- /dev/null +++ b/fuzz/fuzz_cmap.c @@ -0,0 +1,87 @@ +// Copyright (C) 2025 Artifex Software, Inc. +// +// This file is part of MuPDF. +// +// MuPDF is free software: you can redistribute it and/or modify it under the +// terms of the GNU Affero General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +// details. +// +// You should have received a copy of the GNU Affero General Public License +// along with MuPDF. If not, see +// +// Alternative licensing terms are available from the licensor. +// For commercial licensing, see or contact +// Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco, +// CA 94129, USA, for further information. + +/* + * Fuzzer for MuPDF CMap parsing (character mapping tables) + * CMap files are used for CJK font encoding in PDFs + */ + +#include +#include +#include + +#include +#include + +#define MAX_INPUT_SIZE (256 * 1024) /* 256KB limit */ + +static fz_context *ctx = NULL; + +int LLVMFuzzerInitialize(int *argc, char ***argv) +{ + (void)argc; + (void)argv; + ctx = fz_new_context(NULL, NULL, FZ_STORE_DEFAULT); + return 0; +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + fz_stream *stream = NULL; + pdf_cmap *cmap = NULL; + + if (size == 0 || size > MAX_INPUT_SIZE) + return 0; + + fz_var(stream); + fz_var(cmap); + + fz_try(ctx) + { + stream = fz_open_memory(ctx, data, size); + cmap = pdf_load_cmap(ctx, stream); + + if (cmap) + { + /* Exercise cmap lookup */ + for (int i = 0; i < 256; i++) + { + pdf_lookup_cmap(cmap, i); + } + + /* Try some multi-byte lookups */ + pdf_lookup_cmap_full(cmap, 0x4E2D, NULL); /* CJK character */ + pdf_lookup_cmap_full(cmap, 0x6587, NULL); + } + } + fz_always(ctx) + { + pdf_drop_cmap(ctx, cmap); + fz_drop_stream(ctx, stream); + } + fz_catch(ctx) + { + /* Ignore errors - we're fuzzing */ + } + + return 0; +} diff --git a/fuzz/fuzz_colorspace.c b/fuzz/fuzz_colorspace.c new file mode 100644 index 0000000000..dd7a84d956 --- /dev/null +++ b/fuzz/fuzz_colorspace.c @@ -0,0 +1,89 @@ +// Copyright (C) 2025 Artifex Software, Inc. +// +// This file is part of MuPDF. +// +// MuPDF is free software: you can redistribute it and/or modify it under the +// terms of the GNU Affero General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +// details. +// +// You should have received a copy of the GNU Affero General Public License +// along with MuPDF. If not, see +// +// Alternative licensing terms are available from the licensor. +// For commercial licensing, see or contact +// Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco, +// CA 94129, USA, for further information. + +/* + * Fuzzer for MuPDF color space handling + * This targets ICC profile parsing and color conversion + */ + +#include +#include +#include + +#include + +#define MAX_INPUT_SIZE (256 * 1024) /* 256KB limit */ + +static fz_context *ctx = NULL; + +int LLVMFuzzerInitialize(int *argc, char ***argv) +{ + (void)argc; + (void)argv; + ctx = fz_new_context(NULL, NULL, FZ_STORE_DEFAULT); + return 0; +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + fz_colorspace *cs = NULL; + fz_buffer *buf = NULL; + + if (size == 0 || size > MAX_INPUT_SIZE) + return 0; + + fz_var(cs); + fz_var(buf); + + fz_try(ctx) + { + buf = fz_new_buffer_from_copied_data(ctx, data, size); + + /* Try to load as ICC profile */ + cs = fz_new_icc_colorspace(ctx, FZ_COLORSPACE_NONE, 0, NULL, buf); + + if (cs) + { + /* Exercise color conversion */ + float src[FZ_MAX_COLORS] = {0}; + float dst[FZ_MAX_COLORS] = {0}; + int n = fz_colorspace_n(ctx, cs); + + for (int i = 0; i < n && i < FZ_MAX_COLORS; i++) + src[i] = 0.5f; + + /* Convert to device RGB */ + fz_convert_color(ctx, cs, src, fz_device_rgb(ctx), dst, NULL, fz_default_color_params); + } + } + fz_always(ctx) + { + fz_drop_colorspace(ctx, cs); + fz_drop_buffer(ctx, buf); + } + fz_catch(ctx) + { + /* Ignore errors - we're fuzzing */ + } + + return 0; +} diff --git a/fuzz/fuzz_epub.c b/fuzz/fuzz_epub.c new file mode 100644 index 0000000000..a325cd3fd3 --- /dev/null +++ b/fuzz/fuzz_epub.c @@ -0,0 +1,86 @@ +// Copyright (C) 2025 Artifex Software, Inc. +// +// This file is part of MuPDF. +// +// MuPDF is free software: you can redistribute it and/or modify it under the +// terms of the GNU Affero General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +// details. +// +// You should have received a copy of the GNU Affero General Public License +// along with MuPDF. If not, see +// +// Alternative licensing terms are available from the licensor. +// For commercial licensing, see or contact +// Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco, +// CA 94129, USA, for further information. + +/* + * Fuzzer for MuPDF EPUB document parsing and rendering + */ + +#include +#include +#include + +#include + +#define MAX_INPUT_SIZE (1 * 1024 * 1024) /* 1MB limit */ + +static fz_context *ctx = NULL; + +int LLVMFuzzerInitialize(int *argc, char ***argv) +{ + (void)argc; + (void)argv; + ctx = fz_new_context(NULL, NULL, FZ_STORE_DEFAULT); + if (ctx) + fz_register_document_handlers(ctx); + return 0; +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + fz_stream *stream = NULL; + fz_document *doc = NULL; + fz_pixmap *pix = NULL; + int i, n; + + if (size == 0 || size > MAX_INPUT_SIZE) + return 0; + + fz_var(stream); + fz_var(doc); + fz_var(pix); + + fz_try(ctx) + { + stream = fz_open_memory(ctx, data, size); + doc = fz_open_document_with_stream(ctx, "epub", stream); + + n = fz_count_pages(ctx, doc); + for (i = 0; i < n && i < 3; i++) /* Limit to 3 pages */ + { + pix = fz_new_pixmap_from_page_number(ctx, doc, i, fz_identity, fz_device_rgb(ctx), 0); + fz_drop_pixmap(ctx, pix); + pix = NULL; + } + } + fz_always(ctx) + { + fz_drop_pixmap(ctx, pix); + fz_drop_document(ctx, doc); + fz_drop_stream(ctx, stream); + } + fz_catch(ctx) + { + /* Ignore errors - we're fuzzing */ + } + + return 0; +} diff --git a/fuzz/fuzz_filter.c b/fuzz/fuzz_filter.c new file mode 100644 index 0000000000..3170436bbf --- /dev/null +++ b/fuzz/fuzz_filter.c @@ -0,0 +1,142 @@ +// Copyright (C) 2025 Artifex Software, Inc. +// +// This file is part of MuPDF. +// +// MuPDF is free software: you can redistribute it and/or modify it under the +// terms of the GNU Affero General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +// details. +// +// You should have received a copy of the GNU Affero General Public License +// along with MuPDF. If not, see +// +// Alternative licensing terms are available from the licensor. +// For commercial licensing, see or contact +// Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco, +// CA 94129, USA, for further information. + +/* + * Fuzzer for MuPDF decompression filters + * This targets FlateDecode, LZW, DCT, JBIG2, JPX and other filters + */ + +#include +#include +#include + +#include + +#define MAX_INPUT_SIZE (512 * 1024) /* 512KB limit */ +#define MAX_OUTPUT_SIZE (4 * 1024 * 1024) /* 4MB output limit */ + +static fz_context *ctx = NULL; + +int LLVMFuzzerInitialize(int *argc, char ***argv) +{ + (void)argc; + (void)argv; + ctx = fz_new_context(NULL, NULL, FZ_STORE_DEFAULT); + return 0; +} + +static void test_filter(fz_context *ctx, const uint8_t *data, size_t size, + fz_stream *(*open_filter)(fz_context *, fz_stream *)) +{ + fz_stream *input = NULL; + fz_stream *filter = NULL; + unsigned char buf[4096]; + size_t total = 0; + + fz_var(input); + fz_var(filter); + + fz_try(ctx) + { + input = fz_open_memory(ctx, data, size); + filter = open_filter(ctx, input); + input = NULL; /* Filter takes ownership */ + + /* Read until EOF or limit */ + while (total < MAX_OUTPUT_SIZE) + { + size_t n = fz_read(ctx, filter, buf, sizeof(buf)); + if (n == 0) + break; + total += n; + } + } + fz_always(ctx) + { + fz_drop_stream(ctx, filter); + fz_drop_stream(ctx, input); + } + fz_catch(ctx) + { + /* Ignore errors */ + } +} + +static fz_stream *open_flate(fz_context *ctx, fz_stream *chain) +{ + return fz_open_flated(ctx, chain, 15); +} + +static fz_stream *open_lzw(fz_context *ctx, fz_stream *chain) +{ + return fz_open_lzwd(ctx, chain, 0, 9, 0, 1); +} + +static fz_stream *open_rld(fz_context *ctx, fz_stream *chain) +{ + return fz_open_rld(ctx, chain); +} + +static fz_stream *open_a85d(fz_context *ctx, fz_stream *chain) +{ + return fz_open_a85d(ctx, chain); +} + +static fz_stream *open_ahxd(fz_context *ctx, fz_stream *chain) +{ + return fz_open_ahxd(ctx, chain); +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + if (size == 0 || size > MAX_INPUT_SIZE) + return 0; + + /* Use first byte to select filter type */ + uint8_t filter_type = data[0] % 5; + data++; + size--; + + if (size == 0) + return 0; + + switch (filter_type) + { + case 0: + test_filter(ctx, data, size, open_flate); + break; + case 1: + test_filter(ctx, data, size, open_lzw); + break; + case 2: + test_filter(ctx, data, size, open_rld); + break; + case 3: + test_filter(ctx, data, size, open_a85d); + break; + case 4: + test_filter(ctx, data, size, open_ahxd); + break; + } + + return 0; +} diff --git a/fuzz/fuzz_font.c b/fuzz/fuzz_font.c new file mode 100644 index 0000000000..ee9b3d1bbe --- /dev/null +++ b/fuzz/fuzz_font.c @@ -0,0 +1,91 @@ +// Copyright (C) 2025 Artifex Software, Inc. +// +// This file is part of MuPDF. +// +// MuPDF is free software: you can redistribute it and/or modify it under the +// terms of the GNU Affero General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +// details. +// +// You should have received a copy of the GNU Affero General Public License +// along with MuPDF. If not, see +// +// Alternative licensing terms are available from the licensor. +// For commercial licensing, see or contact +// Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco, +// CA 94129, USA, for further information. + +/* + * Fuzzer for MuPDF font loading (TTF, OTF, CFF, Type1) + */ + +#include +#include +#include + +#include + +#define MAX_INPUT_SIZE (1 * 1024 * 1024) /* 1MB limit */ + +static fz_context *ctx = NULL; + +int LLVMFuzzerInitialize(int *argc, char ***argv) +{ + (void)argc; + (void)argv; + ctx = fz_new_context(NULL, NULL, FZ_STORE_DEFAULT); + return 0; +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + fz_font *font = NULL; + fz_buffer *buf = NULL; + + if (size == 0 || size > MAX_INPUT_SIZE) + return 0; + + fz_var(font); + fz_var(buf); + + fz_try(ctx) + { + buf = fz_new_buffer_from_copied_data(ctx, data, size); + + /* Try loading as different font types */ + font = fz_new_font_from_buffer(ctx, NULL, buf, 0, 0); + if (font) + { + /* Exercise font metrics */ + fz_font_ascender(ctx, font); + fz_font_descender(ctx, font); + + /* Try to encode some glyphs */ + int gid; + for (int i = 0; i < 256; i++) + { + gid = fz_encode_character(ctx, font, i); + if (gid > 0) + { + fz_advance_glyph(ctx, font, gid, 0); + } + } + } + } + fz_always(ctx) + { + fz_drop_font(ctx, font); + fz_drop_buffer(ctx, buf); + } + fz_catch(ctx) + { + /* Ignore errors - we're fuzzing */ + } + + return 0; +} diff --git a/fuzz/fuzz_html.c b/fuzz/fuzz_html.c new file mode 100644 index 0000000000..39f6742b41 --- /dev/null +++ b/fuzz/fuzz_html.c @@ -0,0 +1,86 @@ +// Copyright (C) 2025 Artifex Software, Inc. +// +// This file is part of MuPDF. +// +// MuPDF is free software: you can redistribute it and/or modify it under the +// terms of the GNU Affero General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +// details. +// +// You should have received a copy of the GNU Affero General Public License +// along with MuPDF. If not, see +// +// Alternative licensing terms are available from the licensor. +// For commercial licensing, see or contact +// Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco, +// CA 94129, USA, for further information. + +/* + * Fuzzer for MuPDF HTML document parsing and rendering + */ + +#include +#include +#include + +#include + +#define MAX_INPUT_SIZE (512 * 1024) /* 512KB limit */ + +static fz_context *ctx = NULL; + +int LLVMFuzzerInitialize(int *argc, char ***argv) +{ + (void)argc; + (void)argv; + ctx = fz_new_context(NULL, NULL, FZ_STORE_DEFAULT); + if (ctx) + fz_register_document_handlers(ctx); + return 0; +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + fz_stream *stream = NULL; + fz_document *doc = NULL; + fz_pixmap *pix = NULL; + int i, n; + + if (size == 0 || size > MAX_INPUT_SIZE) + return 0; + + fz_var(stream); + fz_var(doc); + fz_var(pix); + + fz_try(ctx) + { + stream = fz_open_memory(ctx, data, size); + doc = fz_open_document_with_stream(ctx, "html", stream); + + n = fz_count_pages(ctx, doc); + for (i = 0; i < n && i < 3; i++) /* Limit to 3 pages */ + { + pix = fz_new_pixmap_from_page_number(ctx, doc, i, fz_identity, fz_device_rgb(ctx), 0); + fz_drop_pixmap(ctx, pix); + pix = NULL; + } + } + fz_always(ctx) + { + fz_drop_pixmap(ctx, pix); + fz_drop_document(ctx, doc); + fz_drop_stream(ctx, stream); + } + fz_catch(ctx) + { + /* Ignore errors - we're fuzzing */ + } + + return 0; +} diff --git a/fuzz/fuzz_html5.c b/fuzz/fuzz_html5.c new file mode 100644 index 0000000000..36d07d6882 --- /dev/null +++ b/fuzz/fuzz_html5.c @@ -0,0 +1,72 @@ +// Copyright (C) 2025 Artifex Software, Inc. +// +// This file is part of MuPDF. +// +// MuPDF is free software: you can redistribute it and/or modify it under the +// terms of the GNU Affero General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +// details. +// +// You should have received a copy of the GNU Affero General Public License +// along with MuPDF. If not, see +// +// Alternative licensing terms are available from the licensor. +// For commercial licensing, see or contact +// Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco, +// CA 94129, USA, for further information. + +/* + * Fuzzer for MuPDF HTML5 parsing + */ + +#include +#include +#include + +#include + +#define MAX_INPUT_SIZE (256 * 1024) /* 256KB limit */ + +static fz_context *ctx = NULL; + +int LLVMFuzzerInitialize(int *argc, char ***argv) +{ + (void)argc; + (void)argv; + ctx = fz_new_context(NULL, NULL, FZ_STORE_DEFAULT); + return 0; +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + fz_buffer *buf = NULL; + fz_xml *xml = NULL; + + if (size == 0 || size > MAX_INPUT_SIZE) + return 0; + + fz_var(buf); + fz_var(xml); + + fz_try(ctx) + { + buf = fz_new_buffer_from_copied_data(ctx, data, size); + xml = fz_parse_xml_from_html5(ctx, buf); + } + fz_always(ctx) + { + fz_drop_xml(ctx, xml); + fz_drop_buffer(ctx, buf); + } + fz_catch(ctx) + { + /* Ignore errors - we're fuzzing */ + } + + return 0; +} diff --git a/fuzz/fuzz_image.c b/fuzz/fuzz_image.c new file mode 100644 index 0000000000..683922611c --- /dev/null +++ b/fuzz/fuzz_image.c @@ -0,0 +1,85 @@ +// Copyright (C) 2025 Artifex Software, Inc. +// +// This file is part of MuPDF. +// +// MuPDF is free software: you can redistribute it and/or modify it under the +// terms of the GNU Affero General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +// details. +// +// You should have received a copy of the GNU Affero General Public License +// along with MuPDF. If not, see +// +// Alternative licensing terms are available from the licensor. +// For commercial licensing, see or contact +// Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco, +// CA 94129, USA, for further information. + +/* + * Fuzzer for MuPDF image loading (PNG, JPEG, TIFF, BMP, GIF, PSD, PNM, etc.) + */ + +#include +#include +#include + +#include + +#define MAX_INPUT_SIZE (1 * 1024 * 1024) /* 1MB limit */ +#define MAX_IMAGE_DIM 8192 /* Safeguard against OOM */ + +static fz_context *ctx = NULL; + +int LLVMFuzzerInitialize(int *argc, char ***argv) +{ + (void)argc; + (void)argv; + ctx = fz_new_context(NULL, NULL, FZ_STORE_DEFAULT); + return 0; +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + fz_buffer *buf = NULL; + fz_image *img = NULL; + fz_pixmap *pix = NULL; + + if (size == 0 || size > MAX_INPUT_SIZE) + return 0; + + fz_var(buf); + fz_var(img); + fz_var(pix); + + fz_try(ctx) + { + buf = fz_new_buffer_from_copied_data(ctx, data, size); + img = fz_new_image_from_buffer(ctx, buf); + if (img) + { + /* Check dimensions before decoding full pixmap to prevent OOM */ + if (img->w > 0 && img->h > 0 && + img->w < MAX_IMAGE_DIM && img->h < MAX_IMAGE_DIM) + { + pix = fz_get_pixmap_from_image(ctx, img, NULL, NULL, NULL, NULL); + } + } + } + fz_always(ctx) + { + fz_drop_pixmap(ctx, pix); + fz_drop_image(ctx, img); + fz_drop_buffer(ctx, buf); + } + fz_catch(ctx) + { + /* Ignore errors - we're fuzzing */ + } + + return 0; +} diff --git a/fuzz/fuzz_json.c b/fuzz/fuzz_json.c new file mode 100644 index 0000000000..5d0e6292a3 --- /dev/null +++ b/fuzz/fuzz_json.c @@ -0,0 +1,86 @@ +// Copyright (C) 2025 Artifex Software, Inc. +// +// This file is part of MuPDF. +// +// MuPDF is free software: you can redistribute it and/or modify it under the +// terms of the GNU Affero General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +// details. +// +// You should have received a copy of the GNU Affero General Public License +// along with MuPDF. If not, see +// +// Alternative licensing terms are available from the licensor. +// For commercial licensing, see or contact +// Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco, +// CA 94129, USA, for further information. + +/* + * Fuzzer for MuPDF JSON parsing + */ + +#include +#include +#include + +#include + +#define MAX_INPUT_SIZE (256 * 1024) /* 256KB limit */ + +static fz_context *ctx = NULL; + +int LLVMFuzzerInitialize(int *argc, char ***argv) +{ + (void)argc; + (void)argv; + ctx = fz_new_context(NULL, NULL, FZ_STORE_DEFAULT); + return 0; +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + fz_pool *pool = NULL; + fz_json *json = NULL; + char *str = NULL; + + if (size == 0 || size > MAX_INPUT_SIZE) + return 0; + + fz_var(pool); + fz_var(str); + + fz_try(ctx) + { + /* Null-terminate the input for JSON parsing */ + str = fz_malloc(ctx, size + 1); + memcpy(str, data, size); + str[size] = '\0'; + + pool = fz_new_pool(ctx); + json = fz_parse_json(ctx, pool, str); + + /* If parsing succeeded, try to serialize it back */ + if (json) + { + fz_buffer *buf = fz_new_buffer(ctx, 1024); + fz_append_json(ctx, buf, json); + fz_drop_buffer(ctx, buf); + } + } + fz_always(ctx) + { + fz_drop_pool(ctx, pool); + fz_free(ctx, str); + } + fz_catch(ctx) + { + /* Ignore errors - we're fuzzing */ + } + + return 0; +} diff --git a/fuzz/fuzz_path.c b/fuzz/fuzz_path.c new file mode 100644 index 0000000000..0f85bb827d --- /dev/null +++ b/fuzz/fuzz_path.c @@ -0,0 +1,197 @@ +// Copyright (C) 2025 Artifex Software, Inc. +// +// This file is part of MuPDF. +// +// MuPDF is free software: you can redistribute it and/or modify it under the +// terms of the GNU Affero General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +// details. +// +// You should have received a copy of the GNU Affero General Public License +// along with MuPDF. If not, see +// +// Alternative licensing terms are available from the licensor. +// For commercial licensing, see or contact +// Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco, +// CA 94129, USA, for further information. + +/* + * Fuzzer for MuPDF path operations + * This targets vector path construction and stroking + */ + +#include +#include +#include + +#include + +#define MAX_INPUT_SIZE (64 * 1024) /* 64KB limit */ + +static fz_context *ctx = NULL; + +int LLVMFuzzerInitialize(int *argc, char ***argv) +{ + (void)argc; + (void)argv; + ctx = fz_new_context(NULL, NULL, FZ_STORE_DEFAULT); + return 0; +} + +/* Path operation types */ +enum { + OP_MOVETO = 0, + OP_LINETO, + OP_CURVETO, + OP_CURVETOV, + OP_CURVETOY, + OP_CLOSEPATH, + OP_RECTTO, + OP_COUNT +}; + +/* Small pixmap dimensions for rasterization tests */ +#define RASTER_WIDTH 64 +#define RASTER_HEIGHT 64 + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + fz_path *path = NULL; + fz_stroke_state *stroke = NULL; + fz_pixmap *pix = NULL; + fz_device *dev = NULL; + fz_rect bounds; + size_t i = 0; + + if (size < 4 || size > MAX_INPUT_SIZE) + return 0; + + fz_var(path); + fz_var(stroke); + fz_var(pix); + fz_var(dev); + + fz_try(ctx) + { + path = fz_new_path(ctx); + stroke = fz_new_stroke_state(ctx); + + /* Configure stroke from first few bytes */ + stroke->linewidth = (data[i++] % 100) / 10.0f; + stroke->miterlimit = (data[i++] % 100) / 10.0f + 1.0f; + stroke->start_cap = data[i++] % 3; + stroke->end_cap = data[i++] % 3; + + /* Build path from remaining data */ + while (i + 1 < size) + { + uint8_t op = data[i++] % OP_COUNT; + float x, y, x2, y2, x3, y3; + + switch (op) + { + case OP_MOVETO: + if (i + 2 > size) break; + x = (float)(int8_t)data[i++]; + y = (float)(int8_t)data[i++]; + fz_moveto(ctx, path, x, y); + break; + + case OP_LINETO: + if (i + 2 > size) break; + x = (float)(int8_t)data[i++]; + y = (float)(int8_t)data[i++]; + fz_lineto(ctx, path, x, y); + break; + + case OP_CURVETO: + if (i + 6 > size) break; + x = (float)(int8_t)data[i++]; + y = (float)(int8_t)data[i++]; + x2 = (float)(int8_t)data[i++]; + y2 = (float)(int8_t)data[i++]; + x3 = (float)(int8_t)data[i++]; + y3 = (float)(int8_t)data[i++]; + fz_curveto(ctx, path, x, y, x2, y2, x3, y3); + break; + + case OP_CURVETOV: + if (i + 4 > size) break; + x2 = (float)(int8_t)data[i++]; + y2 = (float)(int8_t)data[i++]; + x3 = (float)(int8_t)data[i++]; + y3 = (float)(int8_t)data[i++]; + fz_curvetov(ctx, path, x2, y2, x3, y3); + break; + + case OP_CURVETOY: + if (i + 4 > size) break; + x = (float)(int8_t)data[i++]; + y = (float)(int8_t)data[i++]; + x3 = (float)(int8_t)data[i++]; + y3 = (float)(int8_t)data[i++]; + fz_curvetoy(ctx, path, x, y, x3, y3); + break; + + case OP_CLOSEPATH: + fz_closepath(ctx, path); + break; + + case OP_RECTTO: + if (i + 4 > size) break; + x = (float)(int8_t)data[i++]; + y = (float)(int8_t)data[i++]; + x2 = (float)(uint8_t)data[i++]; + y2 = (float)(uint8_t)data[i++]; + fz_rectto(ctx, path, x, y, x + x2, y + y2); + break; + } + } + + /* Exercise path operations */ + bounds = fz_bound_path(ctx, path, stroke, fz_identity); + (void)bounds; + + /* Trim the path */ + fz_trim_path(ctx, path); + + /* Exercise the rasterizer by filling and stroking the path */ + pix = fz_new_pixmap(ctx, fz_device_rgb(ctx), RASTER_WIDTH, RASTER_HEIGHT, NULL, 1); + fz_clear_pixmap_with_value(ctx, pix, 255); + dev = fz_new_draw_device(ctx, fz_identity, pix); + + /* Fill the path */ + { + float color[3] = { 0.5f, 0.5f, 0.5f }; + fz_fill_path(ctx, dev, path, 0, fz_identity, + fz_device_rgb(ctx), color, 1.0f, fz_default_color_params); + } + + /* Stroke the path */ + { + float color[3] = { 0.0f, 0.0f, 0.0f }; + fz_stroke_path(ctx, dev, path, stroke, fz_identity, + fz_device_rgb(ctx), color, 1.0f, fz_default_color_params); + } + + fz_close_device(ctx, dev); + } + fz_always(ctx) + { + fz_drop_device(ctx, dev); + fz_drop_pixmap(ctx, pix); + fz_drop_stroke_state(ctx, stroke); + fz_drop_path(ctx, path); + } + fz_catch(ctx) + { + /* Ignore errors - we're fuzzing */ + } + + return 0; +} diff --git a/fuzz/fuzz_pdf_lexer.c b/fuzz/fuzz_pdf_lexer.c new file mode 100644 index 0000000000..8632eae5ab --- /dev/null +++ b/fuzz/fuzz_pdf_lexer.c @@ -0,0 +1,84 @@ +// Copyright (C) 2025 Artifex Software, Inc. +// +// This file is part of MuPDF. +// +// MuPDF is free software: you can redistribute it and/or modify it under the +// terms of the GNU Affero General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +// details. +// +// You should have received a copy of the GNU Affero General Public License +// along with MuPDF. If not, see +// +// Alternative licensing terms are available from the licensor. +// For commercial licensing, see or contact +// Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco, +// CA 94129, USA, for further information. + +/* + * Fuzzer for MuPDF PDF lexer - low-level token parsing + * This targets the core PDF parsing infrastructure + */ + +#include +#include +#include + +#include +#include + +#define MAX_INPUT_SIZE (256 * 1024) /* 256KB limit */ + +static fz_context *ctx = NULL; + +int LLVMFuzzerInitialize(int *argc, char ***argv) +{ + (void)argc; + (void)argv; + ctx = fz_new_context(NULL, NULL, FZ_STORE_DEFAULT); + return 0; +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + fz_stream *stream = NULL; + pdf_lexbuf lexbuf; + pdf_token tok; + int token_count = 0; + const int max_tokens = 10000; /* Prevent infinite loops */ + + if (size == 0 || size > MAX_INPUT_SIZE) + return 0; + + memset(&lexbuf, 0, sizeof(lexbuf)); + + fz_var(stream); + + fz_try(ctx) + { + pdf_lexbuf_init(ctx, &lexbuf, PDF_LEXBUF_SMALL); + stream = fz_open_memory(ctx, data, size); + + /* Lex all tokens from the input */ + do { + tok = pdf_lex(ctx, stream, &lexbuf); + token_count++; + } while (tok != PDF_TOK_EOF && tok != PDF_TOK_ERROR && token_count < max_tokens); + } + fz_always(ctx) + { + fz_drop_stream(ctx, stream); + pdf_lexbuf_fin(ctx, &lexbuf); + } + fz_catch(ctx) + { + /* Ignore errors - we're fuzzing */ + } + + return 0; +} diff --git a/fuzz/fuzz_pdf_object.c b/fuzz/fuzz_pdf_object.c new file mode 100644 index 0000000000..f2b4947ced --- /dev/null +++ b/fuzz/fuzz_pdf_object.c @@ -0,0 +1,124 @@ +// Copyright (C) 2025 Artifex Software, Inc. +// +// This file is part of MuPDF. +// +// MuPDF is free software: you can redistribute it and/or modify it under the +// terms of the GNU Affero General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +// details. +// +// You should have received a copy of the GNU Affero General Public License +// along with MuPDF. If not, see +// +// Alternative licensing terms are available from the licensor. +// For commercial licensing, see or contact +// Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco, +// CA 94129, USA, for further information. + +/* + * Fuzzer for MuPDF PDF object parsing + * This targets PDF array, dictionary, and indirect object parsing + */ + +#include +#include +#include + +#include +#include + +#define MAX_INPUT_SIZE (256 * 1024) /* 256KB limit */ + +static fz_context *ctx = NULL; +static pdf_document *doc = NULL; + +int LLVMFuzzerInitialize(int *argc, char ***argv) +{ + (void)argc; + (void)argv; + ctx = fz_new_context(NULL, NULL, FZ_STORE_DEFAULT); + if (ctx) + { + fz_register_document_handlers(ctx); + /* Create a minimal PDF document for parsing context */ + fz_try(ctx) + { + doc = pdf_create_document(ctx); + } + fz_catch(ctx) + { + doc = NULL; + } + } + return 0; +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + fz_stream *stream = NULL; + pdf_lexbuf lexbuf; + pdf_obj *obj = NULL; + + if (size == 0 || size > MAX_INPUT_SIZE || doc == NULL) + return 0; + + memset(&lexbuf, 0, sizeof(lexbuf)); + + fz_var(stream); + fz_var(obj); + + fz_try(ctx) + { + pdf_lexbuf_init(ctx, &lexbuf, PDF_LEXBUF_SMALL); + stream = fz_open_memory(ctx, data, size); + + /* Try parsing as different object types */ + + /* Try as array */ + fz_try(ctx) + { + fz_seek(ctx, stream, 0, SEEK_SET); + obj = pdf_parse_array(ctx, doc, stream, &lexbuf); + pdf_drop_obj(ctx, obj); + obj = NULL; + } + fz_catch(ctx) { } + + /* Try as dictionary */ + fz_try(ctx) + { + fz_seek(ctx, stream, 0, SEEK_SET); + obj = pdf_parse_dict(ctx, doc, stream, &lexbuf); + pdf_drop_obj(ctx, obj); + obj = NULL; + } + fz_catch(ctx) { } + + /* Try as stream object */ + fz_try(ctx) + { + fz_seek(ctx, stream, 0, SEEK_SET); + obj = pdf_parse_stm_obj(ctx, doc, stream, &lexbuf); + pdf_drop_obj(ctx, obj); + obj = NULL; + } + fz_catch(ctx) { } + } + fz_always(ctx) + { + pdf_drop_obj(ctx, obj); + fz_drop_stream(ctx, stream); + pdf_lexbuf_fin(ctx, &lexbuf); + } + fz_catch(ctx) + { + /* Ignore errors - we're fuzzing */ + } + + return 0; +} diff --git a/fuzz/fuzz_pdf_stream.c b/fuzz/fuzz_pdf_stream.c new file mode 100644 index 0000000000..722274030b --- /dev/null +++ b/fuzz/fuzz_pdf_stream.c @@ -0,0 +1,104 @@ +// Copyright (C) 2025 Artifex Software, Inc. +// +// This file is part of MuPDF. +// +// MuPDF is free software: you can redistribute it and/or modify it under the +// terms of the GNU Affero General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +// details. +// +// You should have received a copy of the GNU Affero General Public License +// along with MuPDF. If not, see +// +// Alternative licensing terms are available from the licensor. +// For commercial licensing, see or contact +// Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco, +// CA 94129, USA, for further information. + +/* + * Fuzzer for MuPDF PDF content stream interpretation + * This targets the PDF glyph/content stream interpreter + */ + +#include +#include +#include + +#include +#include + +#define MAX_INPUT_SIZE (256 * 1024) /* 256KB limit */ + +static fz_context *ctx = NULL; +static pdf_document *doc = NULL; + +int LLVMFuzzerInitialize(int *argc, char ***argv) +{ + (void)argc; + (void)argv; + ctx = fz_new_context(NULL, NULL, FZ_STORE_DEFAULT); + if (ctx) + { + fz_register_document_handlers(ctx); + fz_try(ctx) + { + doc = pdf_create_document(ctx); + } + fz_catch(ctx) + { + doc = NULL; + } + } + return 0; +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + fz_buffer *buf = NULL; + pdf_obj *resources = NULL; + fz_device *dev = NULL; + fz_cookie cookie = { 0 }; + + if (size == 0 || size > MAX_INPUT_SIZE || doc == NULL) + return 0; + + fz_var(buf); + fz_var(resources); + fz_var(dev); + + fz_try(ctx) + { + fz_matrix ctm = fz_identity; + fz_rect bbox = fz_empty_rect; + + /* Create a buffer with the fuzz input as content stream */ + buf = fz_new_buffer_from_copied_data(ctx, data, size); + + /* Create minimal resources dictionary */ + resources = pdf_new_dict(ctx, doc, 4); + + /* Create a bbox device to consume the output */ + dev = fz_new_bbox_device(ctx, &bbox); + + /* Try to run the glyph stream (Type3 font charprocs) */ + pdf_run_glyph(ctx, doc, resources, buf, dev, ctm, NULL, NULL, NULL, NULL); + } + fz_always(ctx) + { + fz_close_device(ctx, dev); + fz_drop_device(ctx, dev); + pdf_drop_obj(ctx, resources); + fz_drop_buffer(ctx, buf); + } + fz_catch(ctx) + { + /* Ignore errors - we're fuzzing */ + } + + return 0; +} diff --git a/fuzz/fuzz_stext.c b/fuzz/fuzz_stext.c new file mode 100644 index 0000000000..78405f0e06 --- /dev/null +++ b/fuzz/fuzz_stext.c @@ -0,0 +1,89 @@ +// Copyright (C) 2025 Artifex Software, Inc. +// +// This file is part of MuPDF. +// +// MuPDF is free software: you can redistribute it and/or modify it under the +// terms of the GNU Affero General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +// details. +// +// You should have received a copy of the GNU Affero General Public License +// along with MuPDF. If not, see +// +// Alternative licensing terms are available from the licensor. +// For commercial licensing, see or contact +// Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco, +// CA 94129, USA, for further information. + +/* + * Fuzzer for MuPDF structured text extraction from PDF + */ + +#include +#include +#include + +#include + +#define MAX_INPUT_SIZE (1 * 1024 * 1024) /* 1MB limit */ + +static fz_context *ctx = NULL; + +int LLVMFuzzerInitialize(int *argc, char ***argv) +{ + (void)argc; + (void)argv; + ctx = fz_new_context(NULL, NULL, FZ_STORE_DEFAULT); + if (ctx) + fz_register_document_handlers(ctx); + return 0; +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + fz_stream *stream = NULL; + fz_document *doc = NULL; + fz_stext_page *stext = NULL; + fz_stext_options opts = { 0 }; + int i, n; + + if (size == 0 || size > MAX_INPUT_SIZE) + return 0; + + fz_var(stream); + fz_var(doc); + fz_var(stext); + + fz_try(ctx) + { + stream = fz_open_memory(ctx, data, size); + doc = fz_open_document_with_stream(ctx, "pdf", stream); + + n = fz_count_pages(ctx, doc); + for (i = 0; i < n && i < 3; i++) /* Limit to 3 pages */ + { + fz_page *page = fz_load_page(ctx, doc, i); + stext = fz_new_stext_page_from_page(ctx, page, &opts); + fz_drop_stext_page(ctx, stext); + stext = NULL; + fz_drop_page(ctx, page); + } + } + fz_always(ctx) + { + fz_drop_stext_page(ctx, stext); + fz_drop_document(ctx, doc); + fz_drop_stream(ctx, stream); + } + fz_catch(ctx) + { + /* Ignore errors - we're fuzzing */ + } + + return 0; +} diff --git a/fuzz/fuzz_svg.c b/fuzz/fuzz_svg.c new file mode 100644 index 0000000000..2cb8508e76 --- /dev/null +++ b/fuzz/fuzz_svg.c @@ -0,0 +1,82 @@ +// Copyright (C) 2025 Artifex Software, Inc. +// +// This file is part of MuPDF. +// +// MuPDF is free software: you can redistribute it and/or modify it under the +// terms of the GNU Affero General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +// details. +// +// You should have received a copy of the GNU Affero General Public License +// along with MuPDF. If not, see +// +// Alternative licensing terms are available from the licensor. +// For commercial licensing, see or contact +// Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco, +// CA 94129, USA, for further information. + +/* + * Fuzzer for MuPDF SVG parsing and rendering + */ + +#include +#include +#include + +#include + +#define MAX_INPUT_SIZE (512 * 1024) /* 512KB limit */ + +static fz_context *ctx = NULL; + +int LLVMFuzzerInitialize(int *argc, char ***argv) +{ + (void)argc; + (void)argv; + ctx = fz_new_context(NULL, NULL, FZ_STORE_DEFAULT); + if (ctx) + fz_register_document_handlers(ctx); + return 0; +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + fz_stream *stream = NULL; + fz_document *doc = NULL; + fz_pixmap *pix = NULL; + + if (size == 0 || size > MAX_INPUT_SIZE) + return 0; + + fz_var(stream); + fz_var(doc); + fz_var(pix); + + fz_try(ctx) + { + stream = fz_open_memory(ctx, data, size); + doc = fz_open_document_with_stream(ctx, "svg", stream); + + if (fz_count_pages(ctx, doc) > 0) + { + pix = fz_new_pixmap_from_page_number(ctx, doc, 0, fz_identity, fz_device_rgb(ctx), 0); + } + } + fz_always(ctx) + { + fz_drop_pixmap(ctx, pix); + fz_drop_document(ctx, doc); + fz_drop_stream(ctx, stream); + } + fz_catch(ctx) + { + /* Ignore errors - we're fuzzing */ + } + + return 0; +} diff --git a/fuzz/fuzz_xml.c b/fuzz/fuzz_xml.c new file mode 100644 index 0000000000..4921574d2b --- /dev/null +++ b/fuzz/fuzz_xml.c @@ -0,0 +1,79 @@ +// Copyright (C) 2025 Artifex Software, Inc. +// +// This file is part of MuPDF. +// +// MuPDF is free software: you can redistribute it and/or modify it under the +// terms of the GNU Affero General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +// details. +// +// You should have received a copy of the GNU Affero General Public License +// along with MuPDF. If not, see +// +// Alternative licensing terms are available from the licensor. +// For commercial licensing, see or contact +// Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco, +// CA 94129, USA, for further information. + +/* + * Fuzzer for MuPDF XML parsing + */ + +#include +#include +#include + +#include + +#define MAX_INPUT_SIZE (256 * 1024) /* 256KB limit */ + +static fz_context *ctx = NULL; + +int LLVMFuzzerInitialize(int *argc, char ***argv) +{ + (void)argc; + (void)argv; + ctx = fz_new_context(NULL, NULL, FZ_STORE_DEFAULT); + return 0; +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + fz_buffer *buf = NULL; + fz_xml *xml = NULL; + + if (size == 0 || size > MAX_INPUT_SIZE) + return 0; + + fz_var(buf); + fz_var(xml); + + fz_try(ctx) + { + buf = fz_new_buffer_from_copied_data(ctx, data, size); + + /* Test regular XML parsing */ + xml = fz_parse_xml(ctx, buf, 0); + fz_drop_xml(ctx, xml); + xml = NULL; + + /* Test XML parsing with whitespace preservation */ + xml = fz_parse_xml(ctx, buf, 1); + } + fz_always(ctx) + { + fz_drop_xml(ctx, xml); + fz_drop_buffer(ctx, buf); + } + fz_catch(ctx) + { + /* Ignore errors - we're fuzzing */ + } + + return 0; +} diff --git a/fuzz/fuzz_xps.c b/fuzz/fuzz_xps.c new file mode 100644 index 0000000000..7adf3c19fb --- /dev/null +++ b/fuzz/fuzz_xps.c @@ -0,0 +1,86 @@ +// Copyright (C) 2025 Artifex Software, Inc. +// +// This file is part of MuPDF. +// +// MuPDF is free software: you can redistribute it and/or modify it under the +// terms of the GNU Affero General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +// details. +// +// You should have received a copy of the GNU Affero General Public License +// along with MuPDF. If not, see +// +// Alternative licensing terms are available from the licensor. +// For commercial licensing, see or contact +// Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco, +// CA 94129, USA, for further information. + +/* + * Fuzzer for MuPDF XPS document parsing and rendering + */ + +#include +#include +#include + +#include + +#define MAX_INPUT_SIZE (1 * 1024 * 1024) /* 1MB limit */ + +static fz_context *ctx = NULL; + +int LLVMFuzzerInitialize(int *argc, char ***argv) +{ + (void)argc; + (void)argv; + ctx = fz_new_context(NULL, NULL, FZ_STORE_DEFAULT); + if (ctx) + fz_register_document_handlers(ctx); + return 0; +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + fz_stream *stream = NULL; + fz_document *doc = NULL; + fz_pixmap *pix = NULL; + int i, n; + + if (size == 0 || size > MAX_INPUT_SIZE) + return 0; + + fz_var(stream); + fz_var(doc); + fz_var(pix); + + fz_try(ctx) + { + stream = fz_open_memory(ctx, data, size); + doc = fz_open_document_with_stream(ctx, "xps", stream); + + n = fz_count_pages(ctx, doc); + for (i = 0; i < n && i < 5; i++) /* Limit to 5 pages */ + { + pix = fz_new_pixmap_from_page_number(ctx, doc, i, fz_identity, fz_device_rgb(ctx), 0); + fz_drop_pixmap(ctx, pix); + pix = NULL; + } + } + fz_always(ctx) + { + fz_drop_pixmap(ctx, pix); + fz_drop_document(ctx, doc); + fz_drop_stream(ctx, stream); + } + fz_catch(ctx) + { + /* Ignore errors - we're fuzzing */ + } + + return 0; +}