You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tika.apache.org by ta...@apache.org on 2020/09/17 16:01:12 UTC

[tika] branch main updated: TIKA-3199 -- improve fuzzing of PDF files, focus on streams and object numbers/object references

This is an automated email from the ASF dual-hosted git repository.

tallison pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tika.git


The following commit(s) were added to refs/heads/main by this push:
     new f74a53d  TIKA-3199 -- improve fuzzing of PDF files, focus on streams and object numbers/object references
f74a53d is described below

commit f74a53dda6625775089365c3ba15856875a0d5a1
Author: tallison <ta...@apache.org>
AuthorDate: Thu Sep 17 12:00:18 2020 -0400

    TIKA-3199 -- improve fuzzing of PDF files, focus on streams and object numbers/object references
---
 .../java/org/apache/tika/io/TikaInputStream.java   |  15 ++
 tika-fuzzing/pom.xml                               |  12 +-
 .../tika/fuzzing/general/GeneralTransformer.java   |   6 +-
 .../org/apache/tika/fuzzing/pdf/EvilCOSWriter.java | 226 +++++++++++++++++++--
 .../tika/fuzzing/pdf/PDFTransformerConfig.java     | 124 ++++++++++-
 tika-fuzzing/src/test/resources/log4j.properties   |  24 +++
 6 files changed, 389 insertions(+), 18 deletions(-)

diff --git a/tika-core/src/main/java/org/apache/tika/io/TikaInputStream.java b/tika-core/src/main/java/org/apache/tika/io/TikaInputStream.java
index 8a0b930..f451f33 100644
--- a/tika-core/src/main/java/org/apache/tika/io/TikaInputStream.java
+++ b/tika-core/src/main/java/org/apache/tika/io/TikaInputStream.java
@@ -237,6 +237,14 @@ public class TikaInputStream extends TaggedInputStream {
         return new TikaInputStream(path);
     }
 
+    public static TikaInputStream get(Path path, Metadata metadata, TemporaryResources tmp)
+            throws IOException {
+        long length = Files.size(path);
+        metadata.set(TikaCoreProperties.RESOURCE_NAME_KEY, path.getFileName().toString());
+        metadata.set(Metadata.CONTENT_LENGTH, Long.toString(length));
+        return new TikaInputStream(path, tmp, length);
+    }
+
     /**
      * Creates a TikaInputStream from the given file.
      * <p>
@@ -527,6 +535,13 @@ public class TikaInputStream extends TaggedInputStream {
         this.length = Files.size(path);
     }
 
+    private TikaInputStream(Path path, TemporaryResources tmp, long length) throws IOException {
+        super(new BufferedInputStream(Files.newInputStream(path)));
+        this.path = path;
+        this.tmp = tmp;
+        this.length = length;
+    }
+
     /**
      * Creates a TikaInputStream instance. This private constructor is used
      * by the static factory methods based on the available information.
diff --git a/tika-fuzzing/pom.xml b/tika-fuzzing/pom.xml
index fdf57f1..38ec02d 100644
--- a/tika-fuzzing/pom.xml
+++ b/tika-fuzzing/pom.xml
@@ -42,7 +42,12 @@
         </dependency>
         <dependency>
             <groupId>org.apache.tika</groupId>
-            <artifactId>tika-parsers</artifactId>
+            <artifactId>tika-parser-pkg-module</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.tika</groupId>
+            <artifactId>tika-parser-pdf-module</artifactId>
             <version>${project.version}</version>
         </dependency>
         <!-- logging -->
@@ -60,6 +65,11 @@
         </dependency>
         <!-- test -->
         <dependency>
+            <groupId>org.apache.tika</groupId>
+            <artifactId>tika-parser-digest-commons</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
             <groupId>junit</groupId>
             <artifactId>junit</artifactId>
             <scope>test</scope>
diff --git a/tika-fuzzing/src/main/java/org/apache/tika/fuzzing/general/GeneralTransformer.java b/tika-fuzzing/src/main/java/org/apache/tika/fuzzing/general/GeneralTransformer.java
index 803784e..32ede3b 100644
--- a/tika-fuzzing/src/main/java/org/apache/tika/fuzzing/general/GeneralTransformer.java
+++ b/tika-fuzzing/src/main/java/org/apache/tika/fuzzing/general/GeneralTransformer.java
@@ -16,9 +16,9 @@
  */
 package org.apache.tika.fuzzing.general;
 
-import org.apache.commons.compress.utils.IOUtils;
 import org.apache.tika.exception.TikaException;
 import org.apache.tika.fuzzing.Transformer;
+import org.apache.tika.io.IOUtils;
 import org.apache.tika.mime.MediaType;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -75,12 +75,12 @@ public class GeneralTransformer implements Transformer {
         int transformerCount = (maxTransforms == 1) ? 1 : 1 + random.nextInt(maxTransforms);
         int[] transformerIndices = new int[transformerCount];
         for (int i = 0; i < transformerCount; i++) {
-            transformerIndices[i] = random.nextInt(transformerCount);
+            transformerIndices[i] = random.nextInt(transformers.length);
         }
         //TODO -- make this actually streaming
         ByteArrayOutputStream bos = new ByteArrayOutputStream();
         IOUtils.copy(is, bos);
-        for (int i = 0; i < transformerIndices.length-1; i++) {
+        for (int i = 0; i < transformerIndices.length; i++) {
             byte[] bytes = bos.toByteArray();
             bos = new ByteArrayOutputStream();
             transformers[transformerIndices[i]].transform(
diff --git a/tika-fuzzing/src/main/java/org/apache/tika/fuzzing/pdf/EvilCOSWriter.java b/tika-fuzzing/src/main/java/org/apache/tika/fuzzing/pdf/EvilCOSWriter.java
index 0484c93..8a576a7 100644
--- a/tika-fuzzing/src/main/java/org/apache/tika/fuzzing/pdf/EvilCOSWriter.java
+++ b/tika-fuzzing/src/main/java/org/apache/tika/fuzzing/pdf/EvilCOSWriter.java
@@ -32,6 +32,9 @@ import org.apache.pdfbox.cos.COSStream;
 import org.apache.pdfbox.cos.COSString;
 import org.apache.pdfbox.cos.COSUpdateInfo;
 import org.apache.pdfbox.cos.ICOSVisitor;
+import org.apache.pdfbox.filter.DecodeResult;
+import org.apache.pdfbox.filter.Filter;
+import org.apache.pdfbox.filter.FilterFactory;
 import org.apache.pdfbox.io.IOUtils;
 import org.apache.pdfbox.io.RandomAccessInputStream;
 import org.apache.pdfbox.io.RandomAccessRead;
@@ -40,12 +43,24 @@ import org.apache.pdfbox.pdfwriter.COSStandardOutputStream;
 import org.apache.pdfbox.pdfwriter.COSWriter;
 import org.apache.pdfbox.pdfwriter.COSWriterXRefEntry;
 import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.common.PDStream;
 import org.apache.pdfbox.pdmodel.encryption.SecurityHandler;
 import org.apache.pdfbox.pdmodel.fdf.FDFDocument;
 import org.apache.pdfbox.pdmodel.interactive.digitalsignature.COSFilterInputStream;
 import org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureInterface;
 import org.apache.pdfbox.util.Hex;
-
+import org.apache.tika.exception.TikaException;
+import org.apache.tika.fuzzing.Transformer;
+import org.apache.tika.fuzzing.general.GeneralTransformer;
+import org.apache.tika.io.IOExceptionWithCause;
+import org.apache.tika.io.TemporaryResources;
+import org.apache.tika.io.TikaInputStream;
+import org.apache.tika.metadata.Metadata;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.Closeable;
 import java.io.IOException;
@@ -53,6 +68,8 @@ import java.io.InputStream;
 import java.io.OutputStream;
 import java.io.SequenceInputStream;
 import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.text.DecimalFormat;
@@ -73,6 +90,9 @@ import java.util.Random;
 import java.util.Set;
 
 public class EvilCOSWriter implements ICOSVisitor, Closeable {
+
+    private static final Logger LOG = LoggerFactory.getLogger(EvilCOSWriter.class);
+
     /**
      * The dictionary open token.
      */
@@ -172,6 +192,8 @@ public class EvilCOSWriter implements ICOSVisitor, Closeable {
     // the current object number
     private long number = 0;
 
+    private int roughtNumberOfObjects = 0;
+
     // maps the object to the keys generated in the writer
     // these are used for indirect references in other objects
     //A hashtable is used on purpose over a hashmap
@@ -217,6 +239,8 @@ public class EvilCOSWriter implements ICOSVisitor, Closeable {
     private byte[] incrementPart;
     private COSArray byteRangeArray;
 
+    private FilterFactory filterFactory = FilterFactory.INSTANCE;
+
     private final PDFTransformerConfig config;
     private final Random random = new Random();
     /**
@@ -382,6 +406,7 @@ public class EvilCOSWriter implements ICOSVisitor, Closeable {
         COSDictionary root = trailer.getCOSDictionary(COSName.ROOT);
         COSDictionary info = trailer.getCOSDictionary(COSName.INFO);
         COSDictionary encrypt = trailer.getCOSDictionary(COSName.ENCRYPT);
+        roughtNumberOfObjects = doc.getObjects().size();
         if (root != null) {
             addObjectToWrite(root);
         }
@@ -451,11 +476,9 @@ public class EvilCOSWriter implements ICOSVisitor, Closeable {
         // write the object
 
         long objectNumber = currentObjectKey.getNumber();
-        if (config.getRandomizeObjectNumbers()) {
-            if (random.nextFloat() < 0.99) {
-                long orig = objectNumber;
-                objectNumber = 1;//random.nextInt(((int)objectNumber)*2);
-            }
+        if (config.getRandomizeObjectNumbers() > -1.0f && random.nextFloat() <
+            config.getRandomizeObjectNumbers()) {
+                objectNumber = random.nextInt(((int)objectNumber)*2);
         }
         getStandardOutput().write(String.valueOf(objectNumber).getBytes(StandardCharsets.ISO_8859_1));
         getStandardOutput().write(SPACE);
@@ -468,18 +491,186 @@ public class EvilCOSWriter implements ICOSVisitor, Closeable {
         // fail with an NPE
         mutate(obj);
         if (obj != null) {
-            obj.accept(this);
+            writeObjContents(obj);
         }
         getStandardOutput().writeEOL();
         getStandardOutput().write(ENDOBJ);
         getStandardOutput().writeEOL();
     }
 
-    private void mutate(COSBase obj) {
+    private void writeObjContents(COSBase obj) throws IOException {
+        if (! (obj instanceof COSObject)) {
+            obj.accept(this);
+            return;
+        }
+
+        COSObject cosObject = (COSObject)obj;
+        COSBase underlyingObject = cosObject.getObject();
+        if (underlyingObject instanceof COSStream && config.getRawStreamTransformer() != null) {
+            COSStream cosStream = (COSStream)underlyingObject;
+            Transformer rawStreamTransformer = config.getRawStreamTransformer();
+            ByteArrayOutputStream bos = new ByteArrayOutputStream();
+            try (InputStream is = cosStream.createRawInputStream()) {
+                IOUtils.copy(is, bos);
+            }
+            ByteArrayOutputStream transformed = new ByteArrayOutputStream();
+            try {
+                rawStreamTransformer.transform(new ByteArrayInputStream(bos.toByteArray()), transformed);
+            } catch (TikaException e) {
+                throw new IOExceptionWithCause(e);
+            }
+            try (OutputStream os = cosStream.createRawOutputStream()) {
+                IOUtils.copy(new ByteArrayInputStream(transformed.toByteArray()), os);
+            }
+            //stream automatically sets the length correctly
+            obj.accept(this);
+        } else {
+            obj.accept(this);
+        }
+    }
+
+    private void mutate(COSBase obj) throws IOException {
+
         //stub
         if (obj instanceof COSStream) {
             COSStream stream = (COSStream)obj;
-            //manipulate filters and stream length
+            //get the raw unfiltered bytes
+            byte[] bytes = new PDStream(stream).toByteArray();
+            //transform the underlying stream _before_ filters are applied
+            if (config.getStreamTransformer() != null) {
+                ByteArrayOutputStream bos = new ByteArrayOutputStream();
+                try {
+                    config.getStreamTransformer().transform(new ByteArrayInputStream(bytes), bos);
+                } catch (TikaException e) {
+                    throw new IOExceptionWithCause(e);
+                }
+                bytes = bos.toByteArray();
+            }
+            COSBase filters = getFilters(stream.getFilters());
+            if (filters instanceof COSNull) {
+                stream.removeItem(COSName.FILTER);
+            } else {
+                List<COSName> usedFilters = new ArrayList<>();
+                long length = -1;
+                try (TikaInputStream rawBytes = TikaInputStream.get(bytes)) {
+                    try (TikaInputStream filtered = runFilters(filters, rawBytes, usedFilters)) {
+                        //rewrite raw bytes after running own filters
+                        try (OutputStream streamOut = stream.createRawOutputStream()) {
+                            IOUtils.copy(filtered, streamOut);
+                        }
+                        length = filtered.getLength();
+                    }
+                }
+                Collections.reverse(usedFilters);
+                COSArray actualFilters = new COSArray();
+                for (COSName f : usedFilters) {
+                    actualFilters.add(f);
+                }
+                //TODO: parameterize wonkifying length and filters
+                stream.setLong(COSName.LENGTH, length);
+                stream.setItem(COSName.FILTER, actualFilters);
+            }
+        } else if (obj instanceof COSObject) {
+                COSBase underlyingObject = ((COSObject)obj).getObject();
+                mutate(underlyingObject);
+
+        }
+    }
+
+    private TikaInputStream runFilters(COSBase filters, TikaInputStream is, List<COSName> usedFilters) throws IOException {
+        if (filters instanceof COSNull) {
+        } else if (filters instanceof COSName) {
+            is = runFilter((COSName)filters, is, new COSDictionary(), 0);
+            usedFilters.add((COSName)filters);
+            LOG.debug("filter:" + filters.toString() +" "+0 + " : " + is.getLength() );
+        } else if (filters instanceof COSArray) {
+            COSArray filterArray = (COSArray)filters;
+            //need to apply them in reverse order!
+            boolean transformed = false;
+            for (int i = filterArray.size()-1; i >= 0; i--) {
+                COSName filter = (COSName)filterArray.get(i);
+                is = runFilter(filter, is, new COSDictionary(), 0);
+                if (random.nextFloat() > 0.1 && transformed == false) {
+                    is = transformRawStream(is);
+                    transformed = true;
+                }
+                usedFilters.add(filter);
+                LOG.debug("filter:" + filter.toString() +" "+i + " : " +  is.getLength());
+                if (is.getLength() > config.getMaxFilteredStreamLength()) {
+                    LOG.debug("stopping early");
+                    return is;
+                }
+            }
+            return is;
+        } else {
+            throw new IllegalArgumentException("Can't handle this class here: "+filters.getClass());
+        }
+        return transformRawStream(is);
+    }
+
+    private TikaInputStream transformRawStream(TikaInputStream is) throws IOException {
+        if (config.getRawStreamTransformer() != null) {
+            if (is.getLength() < 10000000) {
+                try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
+                    config.getRawStreamTransformer().transform(is, bos);
+                    bos.flush();
+                    bos.close();
+                    return TikaInputStream.get(bos.toByteArray());
+                } catch (TikaException e) {
+                    throw new IOExceptionWithCause(e);
+                }
+            } else {
+                TemporaryResources tmp = new TemporaryResources();
+                Path p = tmp.createTempFile();
+                try (OutputStream os = new BufferedOutputStream(Files.newOutputStream(p))) {
+                    config.getRawStreamTransformer().transform(is, os);
+                    os.flush();
+                } catch (TikaException e) {
+                    throw new IOExceptionWithCause(e);
+                }
+                return TikaInputStream.get(p, new Metadata(), tmp);
+            }
+        }
+        return is;
+    }
+
+    private TikaInputStream runFilter(COSName filterCOSName, TikaInputStream tis, COSDictionary filterParameters,
+                             int filterIndex) throws IOException {
+
+        Filter filter = filterFactory.getFilter(filterCOSName);
+        if (tis.getLength() < 100000000) {
+            try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
+                filter.encode(tis, bos, filterParameters, filterIndex);
+                bos.flush();
+                bos.close();
+                return TikaInputStream.get(bos.toByteArray());
+            } finally {
+                tis.close();
+            }
+        } else {
+            TemporaryResources tmp = new TemporaryResources();
+            Path p = tmp.createTempFile();
+            try (OutputStream os = new BufferedOutputStream(Files.newOutputStream(p))) {
+                filter.encode(tis, os, filterParameters, filterIndex);
+            } finally {
+                tis.close();
+            }
+            return TikaInputStream.get(p, new Metadata(), tmp);
+        }
+    }
+
+    private COSBase getFilters(COSBase existingFilters) {
+        List<COSName> filters = config.getFilters(existingFilters);
+        if (filters.size() == 0) {
+            return COSNull.NULL;
+        } else if (filters.size() == 1) {
+            return filters.get(0);
+        } else {
+            COSArray arr = new COSArray();
+            for (COSName n : filters) {
+                arr.add(n);
+            }
+            return arr;
         }
     }
 
@@ -1037,7 +1228,17 @@ public class EvilCOSWriter implements ICOSVisitor, Closeable {
      */
     public void writeReference(COSBase obj) throws IOException {
         COSObjectKey key = getObjectKey(obj);
-        getStandardOutput().write(String.valueOf(key.getNumber()).getBytes(StandardCharsets.ISO_8859_1));
+        float randomThreshold = config.getRandomizeRefNumbers();
+        float r = random.nextFloat();
+        if (randomThreshold > 0.0f &&
+                r < randomThreshold) {
+            long num = random.nextInt(roughtNumberOfObjects);
+            LOG.debug("corrupting ref number: "+key.getNumber() + " -> "+num);
+            getStandardOutput().write(String.valueOf(num).getBytes(StandardCharsets.ISO_8859_1));
+        } else {
+            getStandardOutput().write(String.valueOf(key.getNumber()).getBytes(StandardCharsets.ISO_8859_1));
+
+        }
         getStandardOutput().write(SPACE);
         getStandardOutput().write(String.valueOf(key.getGeneration()).getBytes(StandardCharsets.ISO_8859_1));
         getStandardOutput().write(SPACE);
@@ -1070,8 +1271,8 @@ public class EvilCOSWriter implements ICOSVisitor, Closeable {
                 input.close();
             }
         }
-    }
 
+    }
     @Override
     public Object visitFromString(COSString obj) throws IOException {
         if (willEncrypt) {
@@ -1085,7 +1286,8 @@ public class EvilCOSWriter implements ICOSVisitor, Closeable {
     }
 
     /**
-     * This will write the pdf document.
+     * This will write the pdf document.  }
+     *
      *
      * @param doc The document to write.
      * @throws IOException If an error occurs while generating the data.
diff --git a/tika-fuzzing/src/main/java/org/apache/tika/fuzzing/pdf/PDFTransformerConfig.java b/tika-fuzzing/src/main/java/org/apache/tika/fuzzing/pdf/PDFTransformerConfig.java
index d152878..d864ef2 100644
--- a/tika-fuzzing/src/main/java/org/apache/tika/fuzzing/pdf/PDFTransformerConfig.java
+++ b/tika-fuzzing/src/main/java/org/apache/tika/fuzzing/pdf/PDFTransformerConfig.java
@@ -16,11 +16,131 @@
  */
 package org.apache.tika.fuzzing.pdf;
 
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSObject;
+import org.apache.tika.fuzzing.Transformer;
+import org.apache.tika.fuzzing.general.ByteDeleter;
+import org.apache.tika.fuzzing.general.ByteFlipper;
+import org.apache.tika.fuzzing.general.ByteInjector;
+import org.apache.tika.fuzzing.general.GeneralTransformer;
+import org.apache.tika.fuzzing.general.SpanSwapper;
+import org.apache.tika.fuzzing.general.Truncator;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Random;
+import java.util.Set;
+
 public class PDFTransformerConfig {
 
-    private boolean randomizeObjectNumbers = true;
+    private final Random random = new Random();
+
+    private float randomizeObjectNumbers = -1.0f;
+
+    private float randomizeRefNumbers = -1.0f;
+
+    private int maxFilters = 1;
+    private int minFilters = 1;
 
-    public boolean getRandomizeObjectNumbers() {
+    private long maxFilteredStreamLength = -1;
+
+    private Set<COSName> allowableFilters = new HashSet<>();
+
+    private Transformer streamTransformer = new GeneralTransformer(1,
+            new ByteDeleter(),
+            new ByteFlipper(), new ByteInjector(), new SpanSwapper(), new Truncator());
+
+    private Transformer rawStreamTransformer = new GeneralTransformer(1,
+            new ByteDeleter(),
+            new ByteFlipper(), new ByteInjector(), new SpanSwapper(), new Truncator());
+
+    public float getRandomizeObjectNumbers() {
         return randomizeObjectNumbers;
     }
+
+    public void setRandomizeObjectNumbers(float randomizeObjectNumbers) {
+        this.randomizeObjectNumbers = randomizeObjectNumbers;
+    }
+
+    public void setRandomizeRefNumbers(float randomizeRefNumbers) {
+        this.randomizeRefNumbers = randomizeRefNumbers;
+    }
+
+    public float getRandomizeRefNumbers() {
+        return randomizeRefNumbers;
+    }
+
+    public Transformer getRawStreamTransformer() {
+        return rawStreamTransformer;
+    }
+
+    public Transformer getStreamTransformer() {
+        return streamTransformer;
+    }
+
+    public void setStreamTransformer(Transformer transformer) {
+        this.streamTransformer = transformer;
+    }
+
+    public void setRawStreamTransformer(Transformer transformer) {
+        this.rawStreamTransformer = transformer;
+    }
+
+    public void setMaxFilters(int maxFilters) {
+        this.maxFilters = maxFilters;
+    }
+
+    public Set<COSName> getAllowableFilters() {
+        return allowableFilters;
+    }
+
+    public void setAllowableFilters(Set<COSName> allowableFilters) {
+        this.allowableFilters = allowableFilters;
+    }
+
+    public List<COSName> getFilters(COSBase existingFilters) {
+        if (maxFilters < 0) {
+            List<COSName> ret = new ArrayList<>();
+            if (existingFilters instanceof COSArray) {
+                for (COSBase obj : ((COSArray)existingFilters)) {
+                    ret.add((COSName)obj);
+                }
+            } else if (existingFilters instanceof COSName) {
+                ret.add((COSName)existingFilters);
+            }
+            return ret;
+        }
+
+        int numFilters;
+        if (maxFilters-minFilters == 0) {
+            numFilters = maxFilters;
+        } else {
+            numFilters = minFilters + random.nextInt(maxFilters - minFilters);
+        }
+
+        List<COSName> allowable = new ArrayList<>();
+        allowable.addAll(allowableFilters);
+
+        List<COSName> filters = new ArrayList<>();
+        for (int i = 0; i < numFilters; i++) {
+            int index = random.nextInt(allowable.size());
+            filters.add(allowable.get(index));
+        }
+        return filters;
+    }
+
+    public void setMinFilters(int minFilters) {
+        this.minFilters = minFilters;
+    }
+
+    public long getMaxFilteredStreamLength() {
+        return maxFilteredStreamLength;
+    }
+
+    public void setMaxFilteredStreamLength(long maxFilteredStreamLength) {
+        this.maxFilteredStreamLength = maxFilteredStreamLength;
+    }
 }
diff --git a/tika-fuzzing/src/test/resources/log4j.properties b/tika-fuzzing/src/test/resources/log4j.properties
new file mode 100644
index 0000000..92b6d56
--- /dev/null
+++ b/tika-fuzzing/src/test/resources/log4j.properties
@@ -0,0 +1,24 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#info,debug, error,fatal ...
+log4j.rootLogger=debug,stderr
+
+#console
+log4j.appender.stderr=org.apache.log4j.ConsoleAppender
+log4j.appender.stderr.layout=org.apache.log4j.PatternLayout
+log4j.appender.stderr.Target=System.err
+
+log4j.appender.stderr.layout.ConversionPattern= %-5p %m%n