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