├── in
├── a.txt
├── b.txt
├── c.txt
├── d.txt
└── e.txt
├── .gitignore
├── .travis-setup-osx.sh
├── WORKSPACE
├── java-afl-cmin
├── java-afl-tmin
├── java-afl-showmap
├── java-afl-fuzz
├── .travis-setup-linux.sh
├── .travis.yml
├── .travis-ci.sh
├── test
├── Null.java
├── Crashing.java
├── NoAttribute.java
├── Forking.java
├── Deferred.java
├── Utils.java
└── Persistent.java
├── javafl
├── CustomInit.java
├── fuzz.java
├── JavaAflInject.java
├── JavaAfl.java
├── run.java
└── JavaAflInstrument.java
├── test-fuzz.sh
├── test.sh
├── BUILD.bazel
├── CMakeLists.txt
├── JavaAfl.c
├── COPYING
└── README.md
/in/a.txt:
--------------------------------------------------------------------------------
1 | a
--------------------------------------------------------------------------------
/in/b.txt:
--------------------------------------------------------------------------------
1 | b
--------------------------------------------------------------------------------
/in/c.txt:
--------------------------------------------------------------------------------
1 | c
--------------------------------------------------------------------------------
/in/d.txt:
--------------------------------------------------------------------------------
1 | d
--------------------------------------------------------------------------------
/in/e.txt:
--------------------------------------------------------------------------------
1 | e
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | out/
2 | bazel-*
3 | build/
4 | build-*
5 |
--------------------------------------------------------------------------------
/.travis-setup-osx.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -xeuo pipefail
4 |
5 | HOMEBREW_NO_AUTO_UPDATE=1 brew install bazel coreutils cmake ninja
6 |
--------------------------------------------------------------------------------
/WORKSPACE:
--------------------------------------------------------------------------------
1 | maven_jar(
2 | name = "asm",
3 | artifact = "org.ow2.asm:asm:6.1",
4 | sha1 = "94a0d17ba8eb24833cd54253ace9b053786a9571",
5 | )
6 |
--------------------------------------------------------------------------------
/java-afl-cmin:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | set -eu
4 |
5 | if ! command -v afl-cmin > /dev/null; then
6 | echo >&2 "$0: afl-cmin not found!"
7 | echo >&2
8 | echo >&2 "You can get it from as part of afl."
9 | exit 127
10 | fi
11 |
12 | AFL_SKIP_BIN_CHECK=1 exec afl-cmin "$@"
13 |
--------------------------------------------------------------------------------
/java-afl-tmin:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | set -eu
4 |
5 | if ! command -v afl-tmin > /dev/null; then
6 | echo >&2 "$0: afl-tmin not found!"
7 | echo >&2
8 | echo >&2 "You can get it from as part of afl."
9 | exit 127
10 | fi
11 |
12 | AFL_SKIP_BIN_CHECK=1 exec afl-tmin "$@"
13 |
--------------------------------------------------------------------------------
/java-afl-showmap:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | set -eu
4 |
5 | if ! command -v afl-showmap > /dev/null; then
6 | echo >&2 "$0: afl-showmap not found!"
7 | echo >&2
8 | echo >&2 "You can get it from as part of afl."
9 | exit 127
10 | fi
11 |
12 | AFL_SKIP_BIN_CHECK=1 exec afl-showmap "$@"
13 |
--------------------------------------------------------------------------------
/java-afl-fuzz:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | set -eu
4 |
5 | if ! command -v afl-fuzz > /dev/null; then
6 | echo >&2 "$0: afl-fuzz not found!"
7 | echo >&2
8 | echo >&2 "You can get it from as part of afl."
9 | exit 127
10 | fi
11 |
12 | JAVA_AFL_PERSISTENT=1 AFL_SKIP_BIN_CHECK=1 AFL_DUMB_FORKSRV=1 exec afl-fuzz "$@"
13 |
--------------------------------------------------------------------------------
/.travis-setup-linux.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -xeuo pipefail
4 |
5 | BAZEL_VERSION=0.11.1
6 |
7 | wget https://github.com/ninja-build/ninja/releases/download/v1.8.2/ninja-linux.zip -O "$TRAVIS_BUILD_DIR"/ninja-linux.zip
8 | sudo unzip -d /usr/bin/ "$TRAVIS_BUILD_DIR"/ninja-linux.zip
9 | wget https://github.com/bazelbuild/bazel/releases/download/"${BAZEL_VERSION}"/bazel_"${BAZEL_VERSION}"-linux-x86_64.deb
10 | sudo dpkg -i bazel_"${BAZEL_VERSION}"-linux-x86_64.deb
11 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: c
2 |
3 | os:
4 | - linux
5 | - osx
6 |
7 | sudo: false
8 |
9 | addons:
10 | apt:
11 | packages:
12 | - openjdk-8-jdk
13 | - curl
14 | - cmake
15 | jdk: openjdk8
16 |
17 | compiler:
18 | - gcc
19 | - clang
20 |
21 | sudo: required
22 | before_install:
23 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then ./.travis-setup-linux.sh; fi
24 | - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then ./.travis-setup-osx.sh; fi
25 |
26 | osx_image: xcode9.2
27 |
28 | install: true
29 |
30 | script: ./.travis-ci.sh
31 |
--------------------------------------------------------------------------------
/.travis-ci.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -xeuo pipefail
4 |
5 | DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")"; pwd)
6 | INSTALL_DIR=$DIR/installs
7 |
8 | wget http://lcamtuf.coredump.cx/afl/releases/afl-2.52b.tgz
9 | tar xf afl-2.52b.tgz
10 | cd afl-2.52b
11 | make -j "$(nproc || sysctl -n hw.ncpu)" PREFIX="$INSTALL_DIR"
12 | make install PREFIX="$INSTALL_DIR"
13 |
14 | export PATH=$INSTALL_DIR/bin:$PATH
15 | cd "$DIR"
16 |
17 | if [[ "$TRAVIS_OS_NAME" == linux ]]; then
18 | ./build.sh
19 | ./test.sh
20 | ./test-fuzz.sh
21 | fi
22 |
23 | # Test CMake builds:
24 | ( mkdir -p build-cmake && cd build-cmake && cmake .. -GNinja)
25 | ninja -C build-cmake
26 |
27 | # Test Bazel builds:
28 | bazel build ...:all
29 |
--------------------------------------------------------------------------------
/test/Null.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2018 Jussi Judin
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package test;
18 |
19 | public class Null
20 | {
21 | public static void main(String[] args)
22 | {
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/javafl/CustomInit.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2018 Jussi Judin
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package javafl;
18 |
19 | @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
20 | @java.lang.annotation.Target(java.lang.annotation.ElementType.METHOD)
21 | public @interface CustomInit {}
22 |
--------------------------------------------------------------------------------
/test/Crashing.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2018 Jussi Judin
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package test;
18 |
19 | public class Crashing
20 | {
21 | public static void main(String[] args) throws java.io.IOException
22 | {
23 | byte[] buffer = new byte[100];
24 | // Make this crash on very likely generated inputs but not on
25 | // files inside in/ directory:
26 | if (System.in.read(buffer) > 10) {
27 | throw new RuntimeException("I will crash now!");
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/test/NoAttribute.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2018 Jussi Judin
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package test;
18 |
19 | public class NoAttribute
20 | {
21 | public static void main(String[] args) throws java.io.IOException
22 | {
23 | String value = "value";
24 | byte[] buffer = new byte[5];
25 | javafl.fuzz.init();
26 | System.in.read(buffer);
27 | String read = new String(buffer);
28 | if (read.equals(value)) {
29 | System.out.println("Got value!");
30 | } else {
31 | System.out.println("Got something else: " + read);
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/test/Forking.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2018 Jussi Judin
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package test;
18 |
19 | import java.util.HashMap;
20 |
21 | public class Forking
22 | {
23 | public static void main(String[] args) throws java.io.IOException
24 | {
25 | HashMap values = new HashMap();
26 | for (int i = 0; i < 'z' - 'a'; ++i) {
27 | byte key = (byte)('a' + i);
28 | values.put(key, i);
29 | }
30 | byte[] data = new byte[128];
31 | int read = 128;
32 | if (args.length >= 1) {
33 | read = (new java.io.FileInputStream(args[0])).read(data, 0, data.length);
34 | } else {
35 | read = System.in.read(data, 0, data.length);
36 | }
37 | test.Utils.fuzz_one(data, read, values);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/test/Deferred.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2018 Jussi Judin
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package test;
18 |
19 | import java.util.HashMap;
20 |
21 | public class Deferred
22 | {
23 | @javafl.CustomInit
24 | public static void main(String[] args) throws java.io.IOException
25 | {
26 | HashMap values = new HashMap();
27 | for (int i = 0; i < 'z' - 'a'; ++i) {
28 | byte key = (byte)('a' + i);
29 | values.put(key, i);
30 | }
31 | byte[] data = new byte[128];
32 | int read = 128;
33 | javafl.fuzz.init();
34 | if (args.length >= 1) {
35 | read = (new java.io.FileInputStream(args[0])).read(data, 0, data.length);
36 | } else {
37 | read = System.in.read(data, 0, data.length);
38 | }
39 | test.Utils.fuzz_one(data, read, values);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/test/Utils.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2018 Jussi Judin
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package test;
18 |
19 | import java.util.HashMap;
20 |
21 | public class Utils
22 | {
23 | static public void fuzz_one(byte[] data, int size, HashMap values)
24 | {
25 | long total = 0;
26 | for (int i = 0; i < size; i++) {
27 | byte key = data[i];
28 | Integer value = values.getOrDefault(key, null);
29 | if (value == null) {
30 | continue;
31 | }
32 | if (value % 5 == 0) {
33 | total += value * 5;
34 | total += key;
35 | } else if (value % 3 == 0) {
36 | total += value * 3;
37 | total += key;
38 | } else if (value % 2 == 0) {
39 | total += value * 2;
40 | total += key;
41 | } else {
42 | total += value + key;
43 | }
44 | }
45 | System.out.println(total);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/test/Persistent.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2018 Jussi Judin
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package test;
18 |
19 | import java.util.HashMap;
20 |
21 | public class Persistent
22 | {
23 | @javafl.CustomInit
24 | public static void main(String[] args) throws java.io.IOException
25 | {
26 | HashMap values = new HashMap();
27 | for (int i = 0; i < 'z' - 'a'; ++i) {
28 | byte key = (byte)('a' + i);
29 | values.put(key, i);
30 | }
31 | byte[] data = new byte[128];
32 | int read = 128;
33 | while (javafl.fuzz.loop(100000)) {
34 | if (args.length >= 1) {
35 | read = (new java.io.FileInputStream(args[0])).read(data, 0, data.length);
36 | } else {
37 | read = System.in.read(data, 0, data.length);
38 | // Throw away all buffering information from stdin:
39 | try {
40 | System.in.skip(9999999);
41 | } catch (java.io.IOException e) {
42 | // pass
43 | }
44 | }
45 | test.Utils.fuzz_one(data, read, values);
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/javafl/fuzz.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2018 Jussi Judin
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package javafl;
18 |
19 | public class fuzz
20 | {
21 | // This is here so that this class won't be accidentally instrumented.
22 | static public final String INSTRUMENTATION_MARKER = "__JAVA-AFL-INSTRUMENTED-CLASSFILE__";
23 |
24 | // Function to use in the deferred mode in combination
25 | // with @javafl.CustomInit annotation:
26 | static public void init()
27 | {
28 | javafl.JavaAfl._init(false);
29 | }
30 |
31 | static private boolean _allow_persistent = false;
32 | static private int _current_iteration = 0;
33 | static public boolean loop(int iterations)
34 | {
35 | if (_current_iteration == 0) {
36 | String persistent_set = System.getenv("JAVA_AFL_PERSISTENT");
37 | _allow_persistent = persistent_set != null;
38 | javafl.JavaAfl._init(_allow_persistent);
39 | _current_iteration = 1;
40 | return true;
41 | }
42 | if (_allow_persistent && _current_iteration < iterations) {
43 | javafl.JavaAfl._send_map();
44 | _current_iteration++;
45 | return true;
46 | }
47 | if (_allow_persistent) {
48 | javafl.JavaAfl._send_map();
49 | }
50 | return false;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/javafl/JavaAflInject.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2018 Jussi Judin
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package javafl;
18 |
19 | import org.objectweb.asm.ClassReader;
20 | import org.objectweb.asm.ClassWriter;
21 | import java.io.File;
22 | import java.io.FileInputStream;
23 | import java.io.IOException;
24 |
25 | public class JavaAflInject
26 | {
27 | static class InjectingReader extends ClassReader
28 | {
29 | private String _data;
30 |
31 | public InjectingReader(
32 | FileInputStream file, String data) throws IOException
33 | {
34 | super(file);
35 | if (data.length() > 65535) {
36 | // TODO Java's constant pool limits the size of each
37 | // string constant to 64 kilobytes. This could be
38 | // worked around by combining multiple values during
39 | // runtime. It's actually quite possible to exceed
40 | // this by having plenty of debugging data added.
41 | throw new IllegalArgumentException(
42 | "Injected value can not exceed 64 kilobytes!");
43 | }
44 | _data = data;
45 | }
46 |
47 | @Override
48 | public String readUTF8(int item, char[] buf)
49 | {
50 | String value = super.readUTF8(item, buf);
51 | if (value == null) {
52 | return null;
53 | }
54 | if (value.equals("")) {
55 | return _data;
56 | }
57 | return value;
58 | }
59 | }
60 |
61 | public static void main(String args[]) throws IOException
62 | {
63 | if (args.length != 2) {
64 | System.err.println("Usage: JavaAflInject javafl/JavaAfl.class libjava-afl.so");
65 | System.exit(1);
66 | }
67 |
68 | String class_filename = args[0];
69 | String library_filename = args[1];
70 | File library = new File(library_filename);
71 | long library_size = library.length();
72 | byte library_data[] = new byte[(int)library_size];
73 | (new FileInputStream(library)).read(library_data);
74 | java.io.ByteArrayOutputStream data_output = new java.io.ByteArrayOutputStream();
75 | java.util.zip.GZIPOutputStream gzip = new java.util.zip.GZIPOutputStream(data_output);
76 | gzip.write(library_data, 0, library_data.length);
77 | gzip.finish();
78 | String jni_data = data_output.toString("ISO-8859-1");
79 | ClassReader reader = new InjectingReader(
80 | new FileInputStream(class_filename), jni_data);
81 | ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
82 | reader.accept(writer, ClassReader.SKIP_DEBUG);
83 | byte[] bytes = writer.toByteArray();
84 | (new java.io.FileOutputStream(class_filename)).write(bytes);
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/test-fuzz.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | #
3 | # Copyright 2018 Jussi Judin
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | set -xeuo pipefail
18 |
19 | export AFL_SKIP_CPUFREQ=1
20 | export AFL_NO_UI=1
21 | # Enable running in Travis CI without root access:
22 | export AFL_NO_AFFINITY=1
23 | export AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES=1
24 |
25 | test_timeout=10
26 | testcase_timeout=1000+
27 |
28 | function check_fuzz_status()
29 | {
30 | local mode=$1
31 | local queue_files
32 | queue_files=$(find out/fuzz-"$mode"/queue -type f | grep -v .state | wc -l)
33 | if [[ "$queue_files" -lt 15 ]]; then
34 | echo >&2 "$mode mode does not seem to provide unique paths!"
35 | exit 1
36 | fi
37 | local unstable_results
38 | unstable_results=$(
39 | grep stability out/fuzz-"$mode"/fuzzer_stats | grep -v "100.00%" || :)
40 | if [[ -n "${unstable_results:-}" ]]; then
41 | echo >&2 "$mode mode was unstable: $unstable_results"
42 | exit 1
43 | fi
44 | }
45 |
46 | for mode in Forking Deferred Persistent; do
47 | rm -rf out/fuzz-"$mode"
48 | timeout --preserve-status -s INT "$test_timeout" \
49 | ./java-afl-fuzz -t "$testcase_timeout" -m 30000 -i in/ -o out/fuzz-"$mode" \
50 | -- java -cp out/ins test."$mode"
51 | check_fuzz_status "$mode"
52 |
53 | # TODO persistent dynamic instrumentation is not 100% stable.
54 | if [[ "$mode" == Persistent ]]; then
55 | continue
56 | fi
57 | timeout --preserve-status -s INT "$test_timeout" \
58 | ./java-afl-fuzz -t "$testcase_timeout" -m 30000 -i in/ -o out/fuzz-"$mode" \
59 | -- java -cp java-afl-run.jar:out javafl.run test."$mode"
60 | check_fuzz_status "$mode"
61 | done
62 |
63 | rm -rf out/fuzz-Null
64 | timeout --preserve-status -s INT "$test_timeout" \
65 | ./java-afl-fuzz -t "$testcase_timeout" -m 30000 -i in/ -o out/fuzz-Null \
66 | -- java -cp out/ins test.Null
67 | queue_files=$(find out/fuzz-Null/queue -name 'id:*' -type f | grep -v .state | wc -l)
68 | in_files=$(find in/ -type f | wc -l)
69 | if [[ "$queue_files" -ne "$in_files" ]]; then
70 | echo >&2 "When input is not read, program should not create any outputs!"
71 | exit 1
72 | fi
73 |
74 | rm -rf out/fuzz-Crashing
75 | timeout --preserve-status -s INT "$test_timeout" \
76 | ./java-afl-fuzz -t "$testcase_timeout" -m 30000 -i in/ -o out/fuzz-Crashing \
77 | -- java -cp out/ins test.Crashing
78 | crash_files=$(find out/fuzz-Crashing/crashes -name 'id:*' -type f | grep -v .state | wc -l)
79 | if [[ "$crash_files" -lt 1 ]]; then
80 | echo >&2 "There definitely should be some crashes!"
81 | exit 1
82 | fi
83 |
84 | rm -rf out/fuzz-Crashing
85 | timeout --preserve-status -s INT "$test_timeout" \
86 | ./java-afl-fuzz -t "$testcase_timeout" -m 30000 -i in/ -o out/fuzz-Crashing \
87 | -- java -cp java-afl-run.jar:out javafl.run test.Crashing
88 | crash_files=$(find out/fuzz-Crashing/crashes -name 'id:*' -type f | grep -v .state | wc -l)
89 | if [[ "$crash_files" -lt 1 ]]; then
90 | echo >&2 "There definitely should be some dynamic crashes!"
91 | exit 1
92 | fi
93 |
--------------------------------------------------------------------------------
/test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # Copyright 2018 Jussi Judin
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | set -xeuo pipefail
18 |
19 | # Default mode should produce differing files between runs:
20 | java -jar java-afl-instrument.jar \
21 | out/default/1 \
22 | out/test/Utils.class
23 | java -jar java-afl-instrument.jar \
24 | out/default/2 \
25 | out/test/Utils.class
26 | if cmp --quiet out/default/1/test/Utils.class out/default/2/test/Utils.class; then
27 | echo >&2 "Instrumented files should be different by default!"
28 | exit 1
29 | fi
30 |
31 | # Deterministic mode should produce identical files between runs:
32 | java -jar java-afl-instrument.jar \
33 | --deterministic \
34 | out/deterministic/1 \
35 | out/test/Utils.class
36 | java -jar java-afl-instrument.jar \
37 | --deterministic \
38 | out/deterministic/2 \
39 | out/test/Utils.class
40 | if ! cmp out/deterministic/1/test/Utils.class out/deterministic/2/test/Utils.class; then
41 | echo >&2 "Instrumented files should be identical in deterministic mode!"
42 | exit 1
43 | fi
44 |
45 | # Test that jarfile instrumentation works without issues.
46 | ./java-afl-showmap -m 30000 -o out/tuples-forking.txt -- java -jar out/ins/test.jar < in/a.txt
47 | tuples_forking=$(wc -l < out/tuples-forking.txt)
48 | if [[ "$tuples_forking" -lt 6 ]]; then
49 | echo >&2 "Failed to generate enough tuples in forking implementation!"
50 | exit 1
51 | fi
52 |
53 | ./java-afl-showmap -m 30000 -o out/tuples-forking.txt -- java -cp out/ins test.Forking < in/a.txt
54 | tuples_forking=$(wc -l < out/tuples-forking.txt)
55 | if [[ "$tuples_forking" -lt 6 ]]; then
56 | echo >&2 "Failed to generate enough tuples in forking implementation!"
57 | exit 1
58 | fi
59 |
60 | ./java-afl-showmap -m 30000 -o out/tuples-deferred.txt -- java -cp out/ins test.Deferred < in/a.txt
61 | tuples_deferred=$(wc -l < out/tuples-deferred.txt)
62 | if [[ "$tuples_deferred" -lt 6 ]]; then
63 | echo >&2 "Failed to generate enough tuples in deferred implementation!"
64 | exit 1
65 | fi
66 |
67 | ./java-afl-showmap -m 30000 -o out/tuples-persistent.txt -- java -cp out/ins test.Persistent < in/a.txt
68 | tuples_persistent=$(wc -l < out/tuples-persistent.txt)
69 | if [[ "$tuples_persistent" -lt 6 ]]; then
70 | echo >&2 "Failed to generate enough tuples in persistent implementation!"
71 | exit 1
72 | fi
73 |
74 | ./java-afl-showmap -m 30000 -o /dev/null -- \
75 | java -cp out/ins test.NoAttribute < in/a.txt
76 |
77 | rm -rf out/min/
78 | ./java-afl-cmin -m 30000 -i in/ -o out/min/ -- java -jar out/ins/test.jar
79 | files_in=$(find in/ -type f | wc -l)
80 | files_cmin=$(find out/min/ -type f | wc -l)
81 | if [[ "$files_cmin" -eq "$files_in" ]]; then
82 | echo >&2 "java-afl-cmin does not seem to do its work!"
83 | echo >&2 "Files in in/ match the files in out/min/!"
84 | exit 1
85 | fi
86 |
87 | echo -n aaaaaa > out/to-min.txt
88 | ./java-afl-tmin -m 30000 -i out/to-min.txt -o out/min.txt -- java -jar out/ins/test.jar
89 | size_orig=$(stat --format=%s out/to-min.txt)
90 | size_min=$(stat --format=%s out/min.txt)
91 | if [[ "$size_orig" -le "$size_min" ]]; then
92 | echo >&2 "java-afl-tmin does not seem to do its work!"
93 | echo >&2 "Input file was $size_orig bytes and output is $size_min bytes!"
94 | exit 1
95 | fi
96 |
--------------------------------------------------------------------------------
/BUILD.bazel:
--------------------------------------------------------------------------------
1 | # Copyright 2018 Jussi Judin
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | config_setting(
16 | name = "linux-mode",
17 | values = { "cpu": "k8" }
18 | )
19 |
20 | config_setting(
21 | name = "darwin-mode",
22 | values = { "cpu": "darwin" }
23 | )
24 |
25 | java_library(
26 | name = "JavaAflRaw",
27 | srcs = ["javafl/JavaAfl.java"],
28 | )
29 |
30 | genrule(
31 | name = "javafl-javah",
32 | srcs = ["javafl/JavaAfl.java"],
33 | outs = ["javafl/javafl_JavaAfl.h"],
34 | cmd = """\
35 | javah -d $(@D) -jni javafl.JavaAfl
36 | """,
37 | )
38 |
39 | genrule(
40 | name = "javafl-map-size",
41 | outs = ["javafl/afl-map-size.h"],
42 | cmd = select({
43 | ":linux-mode": """\
44 | if command -v afl-showmap > /dev/null; then
45 | afl-showmap -b -o binary.map -- true 2>/dev/null || :
46 | stat --format "#define MAP_SIZE %s" binary.map > $@
47 | else
48 | echo > $@
49 | fi
50 | """,
51 | ":darwin-mode": """\
52 | if command -v afl-showmap > /dev/null; then
53 | afl-showmap -b -o binary.map -- true 2>/dev/null || :
54 | stat -f "#define MAP_SIZE %z" binary.map > $@
55 | else
56 | echo > $@
57 | fi
58 | """,
59 | })
60 | )
61 |
62 | cc_library(
63 | name = "java-afl-lib",
64 | srcs = [
65 | "@local_jdk//:jni_header",
66 | "JavaAfl.c",
67 | ] + select({
68 | ":linux-mode": ["@local_jdk//:jni_md_header-linux"],
69 | ":darwin-mode": ["@local_jdk//:jni_md_header-darwin"],
70 | }),
71 | hdrs = [
72 | ":javafl-javah",
73 | ":javafl-map-size",
74 | ],
75 | includes = [
76 | "external/local_jdk/include",
77 | "javafl",
78 | ] + select({
79 | ":linux-mode": ["external/local_jdk/include/linux"],
80 | ":darwin-mode": ["external/local_jdk/include/darwin"],
81 | }),
82 | defines = ["HAVE_AFL_MAP_SIZE_H"],
83 | )
84 |
85 | cc_binary(
86 | name = "libjava-afl.so",
87 | deps = [ ":java-afl-lib"],
88 | linkshared = 1,
89 | )
90 |
91 | java_library(
92 | name = "JavaAflInject",
93 | srcs = ["javafl/JavaAflInject.java"],
94 | deps = ["@asm//jar"],
95 | )
96 |
97 | genrule(
98 | name = "JavaAfl-injected",
99 | srcs = [
100 | ":JavaAflRaw",
101 | ":JavaAflInject",
102 | ":libjava-afl.so",
103 | "@asm//jar",
104 | ],
105 | outs = ["javafl/JavaAfl.class"],
106 | cmd = """\
107 | jar xf $(location JavaAflRaw);
108 | java -cp $(location :JavaAflInject):$(location @asm//jar) \
109 | javafl.JavaAflInject javafl/JavaAfl.class $(location :libjava-afl.so);
110 | cp javafl/JavaAfl.class $@
111 | """,
112 | )
113 |
114 | java_binary(
115 | name = "java-afl-instrument",
116 | srcs = [
117 | "javafl/CustomInit.java",
118 | "javafl/fuzz.java",
119 | "javafl/JavaAflInstrument.java",
120 | ],
121 | deps = [
122 | ":JavaAflRaw",
123 | "@asm//jar",
124 | ],
125 | resources = [
126 | ":JavaAfl-injected",
127 | ],
128 | main_class = "javafl.JavaAflInstrument",
129 | visibility = ["//visibility:public"],
130 | )
131 |
132 | java_binary(
133 | name = "java-afl-run",
134 | srcs = [
135 | "javafl/run.java",
136 | ],
137 | deps = [
138 | ":JavaAflRaw",
139 | ":java-afl-instrument",
140 | "@asm//jar",
141 | ],
142 | resources = [
143 | ":JavaAfl-injected",
144 | ],
145 | main_class = "javafl.run",
146 | visibility = ["//visibility:public"],
147 | )
148 |
--------------------------------------------------------------------------------
/javafl/JavaAfl.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2018 Jussi Judin
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package javafl;
18 |
19 | public class JavaAfl implements Thread.UncaughtExceptionHandler
20 | {
21 | // This is here so that this class won't be accidentally instrumented.
22 | static public final String INSTRUMENTATION_MARKER = "__JAVA-AFL-INSTRUMENTED-CLASSFILE__";
23 |
24 | public void uncaughtException(Thread t, Throwable e)
25 | {
26 | javafl.JavaAfl._handle_uncaught_exception();
27 | e.printStackTrace(System.err);
28 | System.exit(1);
29 | }
30 |
31 | // Map size link between C code Java:
32 | static private native int _get_map_size();
33 |
34 | // These are fields that the instrumentation part uses to do its thing:
35 | public static byte map[];
36 | public static int prev_location;
37 |
38 | // If you change the string value of this, you also need to change
39 | // the corresponding value at JavaAflInject.java file!
40 | // This enables only passing 64 kilobytes of data. It is more than
41 | // enough with the help of gzip compression on Linux even when
42 | // there is tons of debugging data added to the resulting JNI
43 | // library.
44 | private final static String _jni_code = "";
45 |
46 | static {
47 | java.io.File jni_target = null;
48 | try {
49 | byte jni_code_compressed[] = _jni_code.getBytes("ISO-8859-1");
50 | java.util.zip.GZIPInputStream input = new java.util.zip.GZIPInputStream(
51 | new java.io.ByteArrayInputStream(jni_code_compressed));
52 | jni_target = java.io.File.createTempFile("libjava-afl-", ".so");
53 | java.io.FileOutputStream output = new java.io.FileOutputStream(jni_target);
54 | byte buffer[] = new byte[4096];
55 | int read = input.read(buffer, 0, buffer.length);
56 | while (read > 0) {
57 | output.write(buffer, 0, read);
58 | read = input.read(buffer, 0, buffer.length);
59 | }
60 | System.load(jni_target.getAbsolutePath());
61 | } catch (java.io.IOException e) {
62 | throw new RuntimeException(e);
63 | } finally {
64 | if (jni_target != null) {
65 | // We need to explicitly delete a file here instead of
66 | // using File.deleteOnExit(), as the JNI
67 | // instrumentation can exit from JVM without running
68 | // exit handlers.
69 | jni_target.delete();
70 | }
71 | }
72 | map = new byte[_get_map_size()];
73 | }
74 |
75 | static public void _before_main()
76 | {
77 | javafl.JavaAfl._init(false);
78 | }
79 |
80 | static protected void _init(boolean is_persistent)
81 | {
82 | _init_impl(is_persistent);
83 | JavaAfl handler = new JavaAfl();
84 | Thread.setDefaultUncaughtExceptionHandler(handler);
85 | Runtime.getRuntime().addShutdownHook(new Thread() {
86 | @Override
87 | public void run() {
88 | // TODO bad exit combination when in persistent mode...
89 | JavaAfl._after_main();
90 | }
91 | });
92 | }
93 |
94 | static protected native void _init_impl(boolean is_persistent);
95 | static public native void _handle_uncaught_exception();
96 | static public native void _after_main();
97 |
98 | static protected native void _send_map();
99 | }
100 |
--------------------------------------------------------------------------------
/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | # Copyright 2018 Jussi Judin
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | cmake_minimum_required(VERSION 2.8)
16 |
17 | project(java-afl)
18 |
19 | # ASM 6.1 Dependency:
20 | set(ASM_JAR ${CMAKE_CURRENT_BINARY_DIR}/asm-6.1.jar)
21 | file(
22 | DOWNLOAD
23 | https://search.maven.org/remotecontent?filepath=org/ow2/asm/asm/6.1/asm-6.1.jar
24 | ${ASM_JAR}
25 | EXPECTED_HASH
26 | SHA256=db788a985a2359666aa29a9a638f03bb67254e4bd5f453a32717593de887b6b1)
27 | add_custom_command(OUTPUT ${ASM_JAR} COMMAND cmake -E touch_nocreate "${ASM_JAR}")
28 | add_custom_target(asm-jar DEPENDS ${ASM_JAR})
29 |
30 | find_package(JNI REQUIRED)
31 | find_package(Java REQUIRED)
32 | include(UseJava)
33 |
34 | add_jar(java-afl-java-javah javafl/JavaAfl.java)
35 | set(javah-include-dir ${CMAKE_CURRENT_BINARY_DIR}/java-afl-javah)
36 | create_javah(
37 | TARGET java-afl-javah
38 | GENERATED_FILES java-afl-jni-headers
39 | CLASSES javafl.JavaAfl
40 | OUTPUT_DIR ${javah-include-dir}
41 | CLASSPATH java-afl-java-javah)
42 |
43 | add_library(java-afl-lib SHARED JavaAfl.c)
44 | target_include_directories(
45 | java-afl-lib PRIVATE ${JNI_INCLUDE_DIRS} ${javah-include-dir})
46 | add_dependencies(java-afl-lib java-afl-javah)
47 |
48 |
49 | find_program(PROGRAM_AFL_SHOWMAP afl-showmap)
50 | if (NOT PROGRAM_AFL_SHOWMAP STREQUAL PROGRAM_AFL_SHOWMAP-NOTFOUND)
51 | execute_process(
52 | COMMAND afl-showmap -b -o binary.map -- true
53 | OUTPUT_QUIET ERROR_QUIET)
54 | execute_process(
55 | COMMAND wc -c binary.map
56 | OUTPUT_VARIABLE AFL_MAP_SIZE_RAW)
57 | string(STRIP "${AFL_MAP_SIZE_RAW}" AFL_MAP_SIZE_RAW)
58 | string(REPLACE " " ";" AFL_MAP_SIZE_LIST "${AFL_MAP_SIZE_RAW}")
59 | list(GET AFL_MAP_SIZE_LIST 0 AFL_MAP_SIZE)
60 | target_compile_definitions(java-afl-lib PRIVATE -DMAP_SIZE=${AFL_MAP_SIZE})
61 | endif()
62 |
63 | set(CMAKE_JAVA_INCLUDE_PATH ${ASM_JAR})
64 | add_jar(java-afl-inject
65 | SOURCES javafl/JavaAflInject.java javafl/JavaAfl.java
66 | ENTRY_POINT JavaAflInstrument
67 | OUTPUT_NAME java-afl-classes
68 | )
69 |
70 | add_custom_command(
71 | OUTPUT injected
72 | COMMAND ${CMAKE_COMMAND} -E remove_directory injected/
73 | COMMAND ${CMAKE_COMMAND} -E make_directory injected/
74 | COMMAND ${CMAKE_COMMAND} -E remove_directory injected-work/
75 | COMMAND ${CMAKE_COMMAND} -E make_directory injected-work/
76 | COMMAND cd injected-work
77 | COMMAND ${Java_JAR_EXECUTABLE} -xf $
78 | COMMAND ${Java_JAR_EXECUTABLE} -xf ${ASM_JAR}
79 | COMMAND cd ..
80 | COMMAND ${Java_JAVA_EXECUTABLE} -cp injected-work/
81 | javafl.JavaAflInject injected-work/javafl/JavaAfl.class $
82 | COMMAND ${CMAKE_COMMAND} -E copy injected-work/javafl/JavaAfl.class injected/javafl/JavaAfl.class
83 | DEPENDS java-afl-inject java-afl-lib
84 | )
85 | add_custom_target(java-afl-injected DEPENDS injected)
86 |
87 | # set(CMAKE_JAVA_INCLUDE_PATH ${CMAKE_JAVA_INCLUDE_PATH})
88 | add_jar(java-afl-base
89 | SOURCES
90 | javafl/CustomInit.java
91 | javafl/fuzz.java
92 | javafl/JavaAflInstrument.java
93 | javafl/run.java
94 | ENTRY_POINT javafl.JavaAflInstrument
95 | )
96 | add_dependencies(java-afl-base asm-jar)
97 |
98 | add_custom_command(
99 | OUTPUT java-afl-instrument.jar
100 | COMMAND ${CMAKE_COMMAND} -E remove_directory combined-instrument/
101 | COMMAND ${CMAKE_COMMAND} -E make_directory combined-instrument/javafl
102 | COMMAND cd combined-instrument/
103 | COMMAND ${Java_JAR_EXECUTABLE} -xf $
104 | COMMAND ${Java_JAR_EXECUTABLE} -xf ${ASM_JAR}
105 | COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/injected/javafl/JavaAfl.class javafl/
106 | COMMAND ${Java_JAR_EXECUTABLE} -cfe ${CMAKE_CURRENT_BINARY_DIR}/java-afl-instrument.jar javafl.JavaAflInstrument .
107 | DEPENDS java-afl-injected asm-jar
108 | )
109 | add_custom_target(java-afl-instrument DEPENDS java-afl-instrument.jar)
110 |
111 | add_custom_command(
112 | OUTPUT java-afl-run.jar
113 | COMMAND ${CMAKE_COMMAND} -E remove_directory combined-run/
114 | COMMAND ${CMAKE_COMMAND} -E make_directory combined-run/javafl
115 | COMMAND cd combined-run/
116 | COMMAND ${Java_JAR_EXECUTABLE} -xf $
117 | COMMAND ${Java_JAR_EXECUTABLE} -xf ${ASM_JAR}
118 | COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/injected/javafl/JavaAfl.class javafl/
119 | COMMAND ${Java_JAR_EXECUTABLE} -cfe ${CMAKE_CURRENT_BINARY_DIR}/java-afl-run.jar javafl.run .
120 | DEPENDS java-afl-injected asm-jar
121 | )
122 | add_custom_target(java-afl-run DEPENDS java-afl-run.jar)
123 |
124 | add_jar(java-afl-all
125 | SOURCES
126 | # This is just a dummy source file that enables to run this target:
127 | javafl/CustomInit.java
128 | INCLUDE_JARS java-afl-instrument.jar java-afl-run.jar)
129 | add_dependencies(java-afl-all java-afl-instrument java-afl-run)
130 |
--------------------------------------------------------------------------------
/javafl/run.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2018 Jussi Judin
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package javafl;
18 |
19 | import java.util.HashMap;
20 | import java.io.InputStream;
21 | import java.io.ByteArrayOutputStream;
22 | import javafl.JavaAflInstrument;
23 |
24 | public class run extends ClassLoader
25 | {
26 | private HashMap> _cache;
27 | private JavaAflInstrument.InstrumentationOptions _options;
28 |
29 | public run(
30 | ClassLoader parent,
31 | JavaAflInstrument.InstrumentationOptions options) {
32 | super(parent);
33 | _cache = new HashMap>();
34 | _options = options;
35 | }
36 |
37 | @Override
38 | public Class> loadClass(String name) throws ClassNotFoundException
39 | {
40 | if (name.startsWith("java.")) {
41 | return super.loadClass(name);
42 | }
43 |
44 | if (_cache.containsKey(name)) {
45 | Class> cached_class = _cache.get(name);
46 | // This likely means that there are other classes that
47 | // result in security exceptions that java.* package
48 | // related.
49 | if (cached_class == null) {
50 | return super.loadClass(name);
51 | }
52 | return cached_class;
53 | }
54 | String resource = name.replace(".", "/") + ".class";
55 | InputStream stream = getResourceAsStream(resource);
56 | if (stream == null) {
57 | throw new ClassNotFoundException("Could not load class " + name);
58 | }
59 | ByteArrayOutputStream class_buffer = new ByteArrayOutputStream();
60 | byte[] buffer = new byte[4096];
61 | try {
62 | int read = stream.read(buffer, 0, buffer.length);
63 | while (read > 0) {
64 | class_buffer.write(buffer, 0, read);
65 | read = stream.read(buffer, 0, buffer.length);
66 | }
67 | } catch (java.io.IOException e) {
68 | // System.out.println("foo " + e);
69 | return super.loadClass(name);
70 | }
71 | byte[] class_data = class_buffer.toByteArray();
72 | JavaAflInstrument.InstrumentedClass instrumented = JavaAflInstrument.try_instrument_class(
73 | class_data, resource, _options);
74 | // System.out.println("bytes: " + class_data.length + " vs. " + instrumented.data.length);
75 | try {
76 | Class> result = defineClass(name, instrumented.data, 0, instrumented.data.length);
77 | _cache.put(name, result);
78 | return result;
79 | } catch (java.lang.SecurityException e) {
80 | return super.loadClass(name);
81 | }
82 | }
83 |
84 | private static int usage()
85 | {
86 | System.err.println(
87 | "Usage: java-afl-run [--custom-init] main.Class [args-to-main.Class]...");
88 | return 1;
89 | }
90 |
91 | public static void main(String[] args) throws
92 | ClassNotFoundException,
93 | NoSuchMethodException,
94 | IllegalAccessException,
95 | java.lang.reflect.InvocationTargetException
96 | {
97 | if (args.length < 1) {
98 | System.exit(usage());
99 | }
100 |
101 | JavaAflInstrument.InstrumentationOptions options =
102 | new JavaAflInstrument.InstrumentationOptions(
103 | 100, false, true);
104 |
105 | int arg_index = 0;
106 | String argument = args[arg_index];
107 | if (argument.equals("--custom-init")) {
108 | options.has_custom_init = true;
109 | arg_index++;
110 | }
111 |
112 | String ratio_str = System.getenv("AFL_INST_RATIO");
113 | if (ratio_str != null) {
114 | options.ratio = Integer.parseInt(ratio_str);
115 | }
116 | ratio_str = System.getenv("JAVA_AFL_INST_RATIO");
117 | if (ratio_str != null) {
118 | options.ratio = Integer.parseInt(ratio_str);
119 | }
120 | if (options.ratio < 0 || options.ratio > 100) {
121 | System.err.println("AFL_INST_RATIO must be between 0 and 100!");
122 | System.exit(1);
123 | }
124 |
125 | if (args.length <= arg_index) {
126 | System.exit(usage());
127 | }
128 | int map_size = javafl.JavaAfl.map.length;
129 | ClassLoader my_loader = run.class.getClassLoader();
130 | ClassLoader loader = new run(my_loader, options);
131 | Class> clazz = null;
132 | String class_name = args[arg_index];
133 | try {
134 | clazz = loader.loadClass(class_name);
135 | } catch (ClassNotFoundException e) {
136 | System.err.println(
137 | "No class " + class_name + " found! Make sure that it can be found from the CLASSPATH: " + e.getMessage());
138 | System.exit(1);
139 | }
140 | java.lang.reflect.Method main_method = clazz.getMethod(
141 | "main", args.getClass());
142 | if (main_method == null) {
143 | System.err.println(
144 | "No main(String[]) method found for class " + class_name);
145 | System.exit(1);
146 | }
147 | String[] new_args = java.util.Arrays.copyOfRange(
148 | args, arg_index + 1, args.length);
149 | main_method.invoke(null, (Object)new_args);
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/JavaAfl.c:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2018 Jussi Judin
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | #include
18 | #include
19 | #include
20 | #include
21 | #include
22 | #include
23 | #include
24 | #include
25 | #include
26 |
27 | #include
28 |
29 | // Use afl's config.h for constants.
30 | #ifdef HAVE_AFL_CONFIG_H
31 |
32 | #include
33 |
34 | #else // #ifndef HAVE_AFL_CONFIG_H
35 |
36 | #ifdef HAVE_AFL_MAP_SIZE_H
37 |
38 | #include
39 |
40 | #endif // #ifdef HAVE_AFL_MAP_SIZE_H
41 |
42 | // These constants must be kept in sync with afl-fuzz:
43 | #ifndef MAP_SIZE
44 |
45 | #ifndef MAP_SIZE_POW2
46 | #define MAP_SIZE_POW2 16;
47 | #endif // #ifndef MAP_SIZE_POW2
48 |
49 | static const size_t MAP_SIZE = 1 << MAP_SIZE_POW2;
50 | #endif // #ifndef MAP_SIZE
51 |
52 | static const char SHM_ENV_VAR[] = "__AFL_SHM_ID";
53 | static const int FORKSRV_FD = 198;
54 |
55 | #endif // #ifndef HAVE_AFL_CONFIG_H
56 |
57 | // These are global helper variables to avoid discovering the same
58 | // information again and again.
59 | static void* g_afl_area = (void*)-1;
60 | static void* g_zero_area = NULL;
61 | static jfieldID g_map_field_id = NULL;
62 | static bool g_is_persistent = false;
63 | static bool g_initialized = false;
64 |
65 | static void init_map_field(JNIEnv *env, jclass cls)
66 | {
67 | jfieldID map_field_id = (*env)->GetStaticFieldID(env, cls, "map", "[B");
68 | if (map_field_id == NULL) {
69 | fprintf(stderr, "No map field found from JavaAfl class!\n");
70 | abort();
71 | }
72 | g_map_field_id = map_field_id;
73 | }
74 |
75 | static jint get_prev_location(JNIEnv *env, jclass cls)
76 | {
77 | jfieldID prev_location_field_id = (*env)->GetStaticFieldID(env, cls, "prev_location", "I");
78 | return (*env)->GetStaticIntField(env, cls, prev_location_field_id);
79 | }
80 |
81 | static jobject get_map_field(JNIEnv *env, jclass cls)
82 | {
83 | return (*env)->GetStaticObjectField(env, cls, g_map_field_id);
84 | }
85 |
86 | JNIEXPORT jint JNICALL Java_javafl_JavaAfl__1get_1map_1size
87 | (JNIEnv * env, jclass cls)
88 | {
89 | return MAP_SIZE;
90 | }
91 |
92 | JNIEXPORT void JNICALL Java_javafl_JavaAfl__1init_1impl
93 | (JNIEnv * env, jclass cls, jboolean is_persistent)
94 | {
95 | if (g_initialized) {
96 | fprintf(
97 | stderr,
98 | "Tried to initialize java-afl twice! "
99 | "If you are using deferred or persistent mode, remember to "
100 | "annotate your main() function with @javafl.CustomInit!\n"
101 | );
102 | abort();
103 | }
104 |
105 | bool use_forkserver = true;
106 | {
107 | int result = write(FORKSRV_FD + 1, "\x00\x00\x00\x00", 4);
108 | if (result == -1) {
109 | if (errno == EBADF) {
110 | use_forkserver = false;
111 | } else {
112 | perror("Failed to send data to fork server");
113 | abort();
114 | }
115 | }
116 | }
117 |
118 | bool child_stopped = false;
119 | union
120 | {
121 | char child_pid_buf[4];
122 | pid_t child_pid;
123 | } child_pid_data = { .child_pid = 0 };
124 | while (use_forkserver) {
125 | union
126 | {
127 | char child_killed_buf[4];
128 | unsigned child_killed;
129 | } child_killed_data;
130 | // Wait for parent. It can also tell us that it has killed the
131 | // child process::
132 | if (read(FORKSRV_FD, child_killed_data.child_killed_buf, 4) != 4) {
133 | perror("Failed to read child killed data");
134 | abort();
135 | }
136 | // This handles the race condition where the child receives
137 | // SIGSTOP first in the persistent mode and then is killed by
138 | // the parent for timing out.
139 | if (child_stopped && child_killed_data.child_killed) {
140 | child_stopped = false;
141 | if (waitpid(child_pid_data.child_pid, NULL, 0) == -1) {
142 | perror("Waiting for the child process failed!");
143 | abort();
144 | }
145 | }
146 | if (child_stopped) {
147 | // In persistent mode the child will send SIGSTOP to
148 | // itself after it has written map data to the shared
149 | // memory. This makes it run for another round in
150 | // persistent mode.
151 | kill(child_pid_data.child_pid, SIGCONT);
152 | child_stopped = false;
153 | } else {
154 | child_pid_data.child_pid = fork();
155 | if (!child_pid_data.child_pid) {
156 | // Child will directly jump to shared memory handling
157 | // related code.
158 | break;
159 | }
160 | }
161 | // Parent will repeatedly write status information back to
162 | // afl-fuzz process.
163 | write(FORKSRV_FD + 1, child_pid_data.child_pid_buf, 4);
164 | int wstatus;
165 | int options = 0;
166 | if (is_persistent) {
167 | options = WUNTRACED;
168 | }
169 | waitpid(child_pid_data.child_pid, &wstatus, options);
170 | child_stopped = WIFSTOPPED(wstatus);
171 | write(FORKSRV_FD + 1, &wstatus, sizeof(wstatus));
172 | }
173 | if (use_forkserver) {
174 | close(FORKSRV_FD);
175 | close(FORKSRV_FD + 1);
176 | }
177 |
178 | g_is_persistent = is_persistent;
179 | g_initialized = true;
180 |
181 | const char* afl_shm_id = getenv(SHM_ENV_VAR);
182 | if (afl_shm_id == NULL) {
183 | return;
184 | }
185 |
186 | // This area of zeros is here only to be able to zero the map
187 | // memory on Java side fast when in persistent fuzzing mode.
188 | g_zero_area = calloc(1, MAP_SIZE);
189 |
190 | jint start_location = get_prev_location(env, cls);
191 | // Have at least something in the map so that afl-fuzz or
192 | // afl-showmap don't give confusing hard to debug messages.
193 | ((char*)g_zero_area)[start_location] = 1;
194 |
195 | g_afl_area = shmat(atoi(afl_shm_id), NULL, 0);
196 | if (g_afl_area == (void*)-1) {
197 | perror("No shared memory area!");
198 | abort();
199 | }
200 | init_map_field(env, cls);
201 | // It's possible that Java side instrumentation has already
202 | // written something to the map. Reset it so that especially
203 | // persistent mode gets a clean slate to start. Deferred mode
204 | // also should benefit from the map being less full in the
205 | // beginning.
206 | (*env)->SetByteArrayRegion(
207 | env, get_map_field(env, cls), 0, MAP_SIZE, g_zero_area);
208 | }
209 |
210 | /**
211 | * Copies map data generated in Java side to the shared memory and at
212 | * the same time zeroes it.
213 | */
214 | static void send_map(JNIEnv * env, jclass cls)
215 | {
216 | if (g_afl_area != (void*)-1) {
217 | jobject map_field = get_map_field(env, cls);
218 | (*env)->GetByteArrayRegion(env, map_field, 0, MAP_SIZE, g_afl_area);
219 | (*env)->SetByteArrayRegion(env, map_field, 0, MAP_SIZE, g_zero_area);
220 | }
221 | }
222 |
223 | JNIEXPORT void JNICALL Java_javafl_JavaAfl__1after_1main
224 | (JNIEnv * env, jclass cls)
225 | {
226 | // Do nothing if we're not running inside something that can read
227 | // maps. This makes Java exit handlers to run properly.
228 | if (g_afl_area == (void*)-1) {
229 | return;
230 | }
231 | // In persistent mode javafl.JavaAfl.loop() does the final map update for
232 | // us. Doing map updates after the main loop leads into
233 | // instability, as there are map updates in the code after that.
234 | // TODO rethink this approach, as there is quite a lot of ifs
235 | // taking care of the persistent mode.
236 | if (!g_is_persistent) {
237 | send_map(env, cls);
238 | }
239 |
240 | // JVM likely waits for specific threads exit that do not exist
241 | // anymore in the forked child processes. This exits from the
242 | // program before any exit handlers get to run.
243 | _Exit(0);
244 | }
245 |
246 | JNIEXPORT void JNICALL Java_javafl_JavaAfl__1handle_1uncaught_1exception
247 | (JNIEnv * env, jclass cls)
248 | {
249 | if (g_afl_area == (void*)-1) {
250 | return;
251 | }
252 | send_map(env, cls);
253 | abort();
254 | }
255 |
256 | JNIEXPORT void JNICALL Java_javafl_JavaAfl__1send_1map
257 | (JNIEnv * env, jclass cls)
258 | {
259 | send_map(env, cls);
260 | kill(getpid(), SIGSTOP);
261 | }
262 |
--------------------------------------------------------------------------------
/COPYING:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This is a fork server based approach to fuzz Java applications on Java
2 | virtual machine with
3 | [american fuzzy lop](http://lcamtuf.coredump.cx/afl/). See
4 | [caveats section](#caveats) about the downsides of this approach.
5 |
6 | ## Usage
7 |
8 | Fuzzing with american fuzzy lop works by instrumenting the compiled
9 | Java bytecode with probabilistic program coverage revealing
10 | instrumentation. There are general types of instrumeting fuzzing modes
11 | in programs that can be fuzzed with `afl-fuzz` command. The default
12 | fork server mode does not need any modifications to the program source
13 | code and can work as is. There are also more efficient deferred fork
14 | server and persistent modes that enable you to skip some
15 | initialization code and keep the JVM running longer than for just one
16 | input.
17 |
18 | ### Ahead of time instrumentation
19 |
20 | Ahead of time instrumentation works by instrumenting specific .jar or
21 | .class files that you want to run with `afl-fuzz` for your
22 | program. This is done by running the built `java-afl-instrument.jar`
23 | and instrumenting each jar or class file that you want to include in
24 | your program. No source code modifications are necessary to get
25 | started:
26 |
27 | ```bash
28 | $ java -jar java-afl-instrument.jar instrumented/ ClassToTest.class
29 | $ java -jar java-afl-instrument.jar instrumented/ jar-to-test.jar
30 | ```
31 |
32 | As instrumentation injects native JNI code into the used files, so you
33 | can only run these files on similar enough systems that
34 | `java-afl-instrument.jar` was run on.
35 |
36 | Then you are ready to fuzz your Java application with `afl-fuzz`. It
37 | can be done with this type of command with the provided
38 | `java-afl-fuzz` wrapper script:
39 |
40 | ```bash
41 | $ java-afl-fuzz -m 20000 -i in/ -o /dev/shm/fuzz-out/ -- java -cp instrumented/ ClassToTest
42 | $ java-afl-fuzz -m 20000 -i in/ -o /dev/shm/fuzz-out/ -- java -jar instrumented/jar-to-test.jar
43 | ```
44 |
45 | ### Just in time instrumentation
46 |
47 | Just in time instrumentation works by wrapping the main function of a
48 | program that you want to run around a custom instrumentation injecting
49 | [ClassLoader](https://docs.oracle.com/javase/7/docs/api/java/lang/ClassLoader.html). This
50 | way you will get more thorough instrumentation than just running ahead
51 | of time instrumentation on your program, but at the same time the
52 | instrumentation likely covers code that you are not interested in.
53 |
54 | Just in time instrumentation works by adding both `java-afl-run.jar`
55 | and the target classes to CLASSPATH and running `javafl.run`
56 | class with the target class name as a parameter:
57 |
58 | ```bash
59 | $ java-afl-fuzz -m 20000 -i in/ -o /dev/shm/fuzz-out/ \
60 | -- java -cp java-afl-run.jar:. javafl.run ClassToTest
61 | $ java-afl-fuzz -m 20000 -i in/ -o /dev/shm/fuzz-out/ \
62 | -- java -cp java-afl-run.jar:jar-to-test.jar javafl.run ClassToTest
63 | ```
64 |
65 | Notice that there is no need to first instrument the class files, as
66 | it is done on fly. This has the same platform specific limitations as
67 | ahead of time compilation, as this instrumentation injects native JNI
68 | code into the used files. So you can only fuzz programs with
69 | `java-afl-run.jar` on similar enough systems that `java-afl-run.jar`
70 | was built on.
71 |
72 | ### java-afl-fuzz parameters
73 |
74 | Parameters to `java-afl-fuzz` command have following functions:
75 |
76 | * `-i in/`: Input directory of initial data that then gets modified
77 | over the fuzzing process.
78 | * `-o /dev/shm/fuzz-out/`: Output directory for fuzzing state
79 | data. This should always be on a shared memory drive and never in a
80 | directory pointing to a physical hard drive.
81 | * `-m 20000`: Higher virtual memory limit that enables JVM to run, as
82 | the default memory limit in `afl-fuzz` is 50 megabytes. JVM can
83 | allocate around 10 gigabytes of virtual memory by default.
84 |
85 | More detailed description of available options can be found from
86 | [american fuzzy lop's README](http://lcamtuf.coredump.cx/afl/README.txt). You
87 | may also want to adjust maximum heap size with
88 | [`-Xmx`](https://docs.oracle.com/cd/E15523_01/web.1111/e13814/jvm_tuning.htm#PERFM164)
89 | option to be smaller than the default if you fuzz multiple JVM
90 | instances on the same machine to keep memory usage sane.
91 |
92 | ### Advanced usage
93 |
94 | More efficient deferred and persistent modes start each fuzzing
95 | iteration later than at the beginning of `main()` function. Using
96 | deferred or persistent mode requires either a special annotation for
97 | the `main()` function or `--custom-init` flag to the instrument
98 | program:
99 |
100 |
101 | ```java
102 | public class ProgramCustom {
103 | @javafl.CustomInit
104 | public static void main(String args[]) {
105 | ...
106 | }
107 | }
108 | ```
109 |
110 | Or you can instrument unmodified code in such way that the init
111 | function does not need to reside inside `main()` by making
112 | `--custom-init` as the first parameter:
113 |
114 | ```bash
115 | $ java -jar java-afl-instrument.jar --custom-init instrumented/ ClassToTest.class
116 | $ java -jar java-afl-instrument.jar --custom-init instrumented/ jar-to-test.jar
117 | ```
118 |
119 | To put the application into deferred mode where all the initialization
120 | code that comes before `javafl.fuzz.init()` function can be done in
121 | following fashion:
122 |
123 | ```java
124 | public class ProgramPersistent {
125 | @javafl.CustomInit
126 | public static void main(String[] args) {
127 | ...
128 | javafl.fuzz.init();
129 | // You need to read the actual input after initialization point.
130 | System.in.read(data_buffer);
131 | ... do actual input processing...
132 | }
133 | }
134 | ```
135 |
136 | To put the program into a persistent mode you need wrap the part that
137 | you want to execute around a `while (javafl.fuzz.loop())`
138 | loop. If you read the input from `System.in`, you need to take care
139 | that you flush Java's buffering on it after you have read your data:
140 |
141 | ```java
142 | public class ProgramPersistent {
143 | @javafl.CustomInit
144 | public static void main(String[] args) {
145 | ...
146 | byte[] data = new byte[128];
147 | int read = 128;
148 | while (javafl.fuzz.loop(100000)) {
149 | read = System.in.read(data, 0, data.length);
150 | // Throw away all buffering information from stdin for the
151 | // next iteration:
152 | System.in.skip(9999999);
153 | ... do actual input processing...
154 | }
155 | ...
156 | }
157 | }
158 | ```
159 |
160 | ### Options controlling instrumentation
161 |
162 | Command line switches to `java-afl-instrument.jar`:
163 |
164 | * `--custom-init`
165 | * `--deterministic`: by default java-afl produces random class files
166 | to make it possible to probabilistically get bigger coverage on the
167 | program from two differently instrumented programs than from
168 | one. This switch makes the instrumentation depend solely on the
169 | input data for each class and will always result in the same result
170 | between different instrumentation runs. Just in time instrumentation
171 | is always deterministic.
172 |
173 | Environmental variables:
174 |
175 | * `AFL_INST_RATIO`: by default 100% of program control flow altering
176 | locations are instrumented. This makes it possible to
177 | probabilistically select a smaller instrumentation ratio. Smaller
178 | instrumentation ratios are useful in big programs where resulting
179 | program execution path traces would otherwise fill the default 16
180 | bit state map and increasing the map size would add unneeded
181 | performance penalty.
182 |
183 | ## Building
184 |
185 | As there are tons of different tools to build Java programs with
186 | automatic dependency fetching, java-afl supports more than one way to
187 | build itself.
188 |
189 | If you pass american fuzzy lop's source code directory that has
190 | `config.h` file in it, you can pass following C flags to JNI
191 | compilation part:
192 |
193 | ```
194 | CFLAGS="-I -DHAVE_AFL_CONFIG_H"
195 | ```
196 |
197 | This makes the compiled information match to what afl-fuzz expects if
198 | it has been modified in any way. Build systems also try to deduce this
199 | (TODO) during compilation from existing `afl-showmap` command if such
200 | exists.
201 |
202 | ### Bazel
203 |
204 | [Bazel](https://bazel.build/) a build tool that can handle very large
205 | programs with ease.
206 |
207 | ```bash
208 | $ bazel build :java-afl-instrument_deploy.jar :java-afl-run_deploy.jar
209 | # Stand-alone jars are under bazel-bin/ as java-afl-instrument_deploy.jar and java-afl-run_deploy.jar
210 | ```
211 |
212 | ### CMake
213 |
214 | [CMake](https://cmake.org/) is the PHP of build systems. Widely
215 | available and gets stuff done but becomes quite painful after a
216 | while.
217 |
218 | ```bash
219 | $ ( mkdir -p build-cmake && cd build-cmake && cmake .. -GNinja )
220 | $ ninja -C build-cmake
221 | # Stand-alone jars are under build-cmake/ as java-afl-instrument.jar and java-afl-run.jar
222 | ```
223 |
224 | ### Travis CI [](https://travis-ci.org/Barro/java-afl)
225 |
226 | Requires Ubuntu 14.04 based system. You need to have
227 | [ASM 6.1](http://asm.ow2.org/) to build this as a dependency in
228 | addition to Java 8 and afl build dependencies. Currently there is a
229 | crude build script to build and test this implementation:
230 |
231 | ```bash
232 | $ ./build.sh
233 | ```
234 |
235 | Even though building requires Java 8, this should be able to
236 | instrument programs that run only on some older versions of Java.
237 |
238 | ## Performance
239 |
240 | Performance numbers on Intel Core i7-3770K CPU @ 3.50GHz with OpenJDK
241 | 1.8.0_151 and afl 2.52b. These tests were done with the simple test
242 | programs that are provided at [test/](test/) directory.
243 |
244 | * Fork server mode around 750 executions/second for a program that
245 | does nothing. Closer to 300 when there is actually something
246 | happening.
247 | * Deferred mode naturally gets something between the fork server mode
248 | and persistent mode. Depends how heavy the initialization is,
249 | probably maybe some tens of percents.
250 | * Persistent mode around 14000 executions/second. Highly depends on
251 | how much and how long JVM is able to optimize before being
252 | killed. See [caveats](#caveats) section about this. Around 31000
253 | iterations/second for an empty while loop, that is close to the
254 | maximum that native C code can handle with `afl-fuzz` in persistent
255 | mode.
256 |
257 | ## TODO
258 |
259 | * Fix persistent mode loop dynamic instrumentation.
260 | * Check if a dynamically instrumentable class is a file and load it or
261 | a full jar file instead.
262 | * Support deferred init for arbitrary given method without source code
263 | modifications. Just prefer the loop syntax and non-forking mode
264 | instead of fork server one for more speed.
265 | * Remove the need for `@javafl.CustomInit`.
266 | * Create a non-forking alternative mode.
267 | * More ways to build this:
268 | * Ant
269 | * Maven
270 | * Gradle
271 | * Alternative method implementations based on fuzzing mode (similar to
272 | C preprocessor's #ifdef/#ifndef). Probably somehow with annotations
273 | or `System.getProperty("FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION")`.
274 |
275 | ## Greetz
276 |
277 | * Great thanks to [Michał Zalewski](http://lcamtuf.coredump.cx/) for
278 | [american fuzzy lop](http://lcamtuf.coredump.cx/afl), a crude but
279 | effective fuzzer. Especially the idea of using a bitmap and randomly
280 | generated program locations as a fast probabilistic memory bound
281 | approximation of the program execution path.
282 | * Inspired by [python-afl](http://jwilk.net/software/python-afl) and
283 | [Kelinci](https://github.com/isstac/kelinci). Just in time
284 | instrumentation idea from [JQF](https://github.com/rohanpadhye/jqf).
285 |
286 | ## Dependencies
287 |
288 | Mandatory dependencies to build this:
289 |
290 | * GNU/Linux system with recently new basic utilities.
291 | * C compiler.
292 | * Java 1.8 or newer to build and to instrument classes with afl-fuzz
293 | compatible instrumentation. Runtime Java version of instrumented
294 | classes should be anything that the original class worked with.
295 | * [ASM 6.1](http://asm.ow2.org/)
296 |
297 | Optional dependencies for building include one of these:
298 |
299 | * Bazel
300 | * CMake
301 |
302 | ## Caveats
303 |
304 | Java virtual machine is a multi-threaded application and
305 | [fork()](http://man7.org/linux/man-pages/man2/fork.2.html) call only
306 | preserves the thread that called it. This creates some issues from
307 | performance and stability point of view:
308 |
309 | * There is no garbage collector running in the forked
310 | process (at least with OpenJDK). Therefore there is a limit on
311 | objects that the fuzz target can allocate during its lifetime. This
312 | shouldn't be an issue with a generally lightweight fuzz targets that
313 | can execute hundreds of times per second, but can become an issue
314 | with more heavy ones.
315 | * Performance will suffer, as JVM will not be able to use knowledge
316 | about hotspots in often executed functions.
317 | * Persistent mode has a limited number of cycles that it can run
318 | before it runs out of memory due to no garbage collector running.
319 | TODO create a non-forking alternative for persistent mode.
320 | * This will make afl-fuzz to result in a timeout every so often when
321 | the program runs out of some resource. If the timeout is set
322 | manually to be relatively long in otherwise fast fuzz target, it
323 | will needlessly delay the recovery from a resource leaking situation.
324 |
325 | ## License
326 |
327 | Copyright 2018 Jussi Judin
328 |
329 | Licensed under the Apache License, Version 2.0 (the "License");
330 | you may not use this file except in compliance with the License.
331 | You may obtain a copy of the License at
332 |
333 | http://www.apache.org/licenses/LICENSE-2.0
334 |
335 | Unless required by applicable law or agreed to in writing, software
336 | distributed under the License is distributed on an "AS IS" BASIS,
337 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
338 | See the License for the specific language governing permissions and
339 | limitations under the License.
340 |
--------------------------------------------------------------------------------
/javafl/JavaAflInstrument.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2018 Jussi Judin
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package javafl;
18 |
19 | import java.io.ByteArrayOutputStream;
20 | import java.io.ByteArrayInputStream;
21 | import java.io.File;
22 | import java.io.FileOutputStream;
23 | import java.io.FileInputStream;
24 | import java.io.InputStream;
25 | import java.io.IOException;
26 | import java.util.Arrays;
27 | import java.util.Enumeration;
28 | import java.util.Random;
29 | import java.util.jar.JarInputStream;
30 | import java.util.jar.JarEntry;
31 | import java.util.jar.JarFile;
32 | import java.util.jar.JarOutputStream;
33 |
34 |
35 | import org.objectweb.asm.ClassReader;
36 | import org.objectweb.asm.AnnotationVisitor;
37 | import org.objectweb.asm.ClassVisitor;
38 | import org.objectweb.asm.ClassWriter;
39 | import org.objectweb.asm.Label;
40 | import org.objectweb.asm.MethodVisitor;
41 | import org.objectweb.asm.Opcodes;
42 |
43 | import static org.objectweb.asm.Opcodes.*;
44 |
45 | public class JavaAflInstrument
46 | {
47 | static private int total_locations = 0;
48 | static private int total_jarfiles = 0;
49 | static private int total_classfiles = 0;
50 |
51 | static class InstrumentedClass
52 | {
53 | String directory;
54 | byte[] data;
55 |
56 | InstrumentedClass(byte[] data_)
57 | {
58 | directory = "";
59 | data = data_;
60 | }
61 |
62 | InstrumentedClass(String directory_, byte[] data_)
63 | {
64 | directory = directory_;
65 | data = data_;
66 | }
67 | }
68 |
69 | static class InstrumentationOptions
70 | {
71 | int ratio;
72 | boolean has_custom_init;
73 | boolean deterministic;
74 |
75 | InstrumentationOptions(int ratio_, boolean has_custom_init_, boolean deterministic_)
76 | {
77 | ratio = ratio_;
78 | has_custom_init = has_custom_init_;
79 | deterministic = deterministic_;
80 | }
81 |
82 | InstrumentationOptions(InstrumentationOptions other)
83 | {
84 | this(other.ratio, other.has_custom_init, other.deterministic);
85 | }
86 | }
87 |
88 | static class InstrumentingMethodVisitor extends MethodVisitor
89 | {
90 | private boolean _has_custom_init;
91 | private int _instrumentation_ratio;
92 | private boolean _is_main;
93 | private InstrumentationOptions _options;
94 | private Random _random;
95 |
96 | public InstrumentingMethodVisitor(
97 | MethodVisitor mv_,
98 | Random random,
99 | InstrumentationOptions options,
100 | boolean is_main)
101 | {
102 | super(Opcodes.ASM6, mv_);
103 | _is_main = is_main;
104 | _random = random;
105 | _instrumentation_ratio = options.ratio;
106 | _has_custom_init = options.has_custom_init;
107 | }
108 |
109 | private void _aflMaybeLog()
110 | {
111 | JavaAflInstrument.total_locations++;
112 | int location_id = _random.nextInt(javafl.JavaAfl.map.length);
113 | // + &JavaAfl.map
114 | mv.visitFieldInsn(GETSTATIC, "javafl/JavaAfl", "map", "[B");
115 | // + location_id
116 | mv.visitLdcInsn(location_id);
117 | // + JavaAfl.prev_location
118 | mv.visitFieldInsn(GETSTATIC, "javafl/JavaAfl", "prev_location", "I");
119 | // - 2 values (location_id, prev_location)
120 | // + location_id ^ prev_location -> tuple_index
121 | mv.visitInsn(IXOR);
122 | // + &JavaAfl.map
123 | // + tuple_index
124 | mv.visitInsn(DUP2);
125 | // - 2 values (&JavaAfl.map, tuple_index)
126 | // + JavaAfl.map[tuple_index] -> previous_tuple_value
127 | mv.visitInsn(BALOAD);
128 | // + 1
129 | mv.visitInsn(ICONST_1);
130 | // - 2 values (1, previous_tuple_value)
131 | // + 1 + previous_tuple_value -> new_tuple_value
132 | mv.visitInsn(IADD);
133 | // = (byte)new_tuple_value
134 | mv.visitInsn(I2B);
135 | // - 3 values (new_tuple_value, tuple_index, &JavaAfl.map)
136 | // = new_tuple_value
137 | mv.visitInsn(BASTORE);
138 | // Stack modifications are now +-0 here.
139 |
140 | // + location_id >> 1 = shifted_location
141 | mv.visitLdcInsn(location_id >> 1);
142 | // - 1 value (shifted_location)
143 | mv.visitFieldInsn(PUTSTATIC, "javafl/JavaAfl", "prev_location", "I");
144 | }
145 |
146 | @Override
147 | public void visitCode()
148 | {
149 | mv.visitCode();
150 | if (_is_main && !_has_custom_init) {
151 | mv.visitMethodInsn(
152 | INVOKESTATIC,
153 | "javafl/JavaAfl",
154 | "_before_main",
155 | "()V",
156 | false);
157 | }
158 | if (_random.nextInt(100) < _instrumentation_ratio) {
159 | _aflMaybeLog();
160 | }
161 | }
162 |
163 | @Override
164 | public void visitJumpInsn(int opcode, Label label) {
165 | mv.visitJumpInsn(opcode, label);
166 | _aflMaybeLog();
167 | }
168 |
169 | @Override
170 | public void visitLabel(Label label) {
171 | mv.visitLabel(label);
172 | _aflMaybeLog();
173 | }
174 |
175 | @Override
176 | public void visitInsn(int opcode)
177 | {
178 | // Main gets special treatment in handling returns. It
179 | // can't return anything else than void:
180 | if (_is_main && opcode == RETURN) {
181 | mv.visitMethodInsn(
182 | INVOKESTATIC,
183 | "javafl/JavaAfl",
184 | "_after_main",
185 | "()V",
186 | false);
187 | }
188 | mv.visitInsn(opcode);
189 | }
190 |
191 | @Override
192 | public AnnotationVisitor visitAnnotation(String desc, boolean visible)
193 | {
194 | // TODO it should be possible to also get the full class
195 | // descriptor name out during the compilation time...
196 | if (desc.equals("L" + javafl.CustomInit.class.getName().replace(".", "/") + ";")) {
197 | _has_custom_init = true;
198 | }
199 | return null;
200 | }
201 | }
202 |
203 | static class InstrumentingClassVisitor extends ClassVisitor
204 | {
205 | ClassWriter _writer;
206 | InstrumentationOptions _options;
207 | Random _random;
208 |
209 | public InstrumentingClassVisitor(
210 | ClassWriter cv,
211 | Random random,
212 | InstrumentationOptions options)
213 | {
214 | super(Opcodes.ASM6, cv);
215 | _writer = cv;
216 | _random = random;
217 | _options = options;
218 | }
219 |
220 | @Override
221 | public MethodVisitor visitMethod(
222 | int access,
223 | String name,
224 | String desc,
225 | String signature,
226 | String[] exceptions)
227 | {
228 | MethodVisitor mv = cv.visitMethod(
229 | access, name, desc, signature, exceptions);
230 | if (mv == null) {
231 | return null;
232 | }
233 |
234 | // Instrument all public static main functions with the
235 | // start-up and teardown instrumentation.
236 | int public_static = Opcodes.ACC_STATIC | Opcodes.ACC_PUBLIC;
237 | if (name.equals("main") && ((access & public_static) != 0)) {
238 | _writer.newMethod(
239 | "javafl/JavaAfl",
240 | "_before_main",
241 | "()V",
242 | false);
243 | _writer.newMethod(
244 | "javafl/JavaAfl",
245 | "_after_main",
246 | "()V",
247 | false);
248 | mv = new InstrumentingMethodVisitor(
249 | mv, _random, _options, true);
250 | } else {
251 | mv = new InstrumentingMethodVisitor(
252 | mv, _random, _options, false);
253 | }
254 | return mv;
255 | }
256 | }
257 |
258 | private static boolean is_instrumented(ClassReader reader)
259 | {
260 | // It would be sooo much more easy if Java had memmem() like
261 | // function in its standard library...
262 | int items = reader.getItemCount();
263 | byte marker_bytes[] = javafl.JavaAfl.INSTRUMENTATION_MARKER.getBytes();
264 | for (int i = 0 ; i < items; i++) {
265 | int index = reader.getItem(i);
266 | int item_size = reader.b[index] * 256 + reader.b[index + 1];
267 | if (item_size != marker_bytes.length) {
268 | continue;
269 | }
270 | int start = index + 2;
271 | int end = start + marker_bytes.length;
272 | if (reader.b.length < end) {
273 | return false;
274 | }
275 | byte value[] = Arrays.copyOfRange(reader.b, start, end);
276 | if (Arrays.equals(marker_bytes, value)) {
277 | return true;
278 | }
279 | }
280 | return false;
281 | }
282 |
283 | private static void instrument_classfile(
284 | File output_dir, String filename, InstrumentationOptions options)
285 | {
286 | File source_file = new File(filename);
287 | byte[] input_data = new byte[(int)source_file.length()];
288 | try {
289 | FileInputStream input_stream = new FileInputStream(source_file);
290 | int read = input_stream.read(input_data);
291 | if (read != input_data.length) {
292 | System.err.println("Unable to fully read " + filename);
293 | }
294 | InstrumentedClass instrumented = instrument_class(
295 | input_data, filename, options);
296 | File output_target_base = new File(
297 | output_dir, instrumented.directory);
298 | if (!output_target_base.exists()) {
299 | output_target_base.mkdirs();
300 | }
301 | File output_target_file = new File(
302 | output_target_base, source_file.getName());
303 | (new FileOutputStream(output_target_file)).write(instrumented.data);
304 | total_classfiles++;
305 | } catch (IOException e) {
306 | System.err.println("Unable to instrument " + filename + ": " + e);
307 | }
308 | }
309 |
310 | static class RetryableInstrumentationException extends RuntimeException
311 | {
312 | public InstrumentedClass class_data;
313 | RetryableInstrumentationException(InstrumentedClass class_data_)
314 | {
315 | class_data = class_data_;
316 | }
317 | }
318 |
319 | private static InstrumentedClass instrument_class(
320 | byte[] input, String filename, InstrumentationOptions options)
321 | {
322 | int old_locations = JavaAflInstrument.total_locations;
323 | InstrumentationOptions try_options = new InstrumentationOptions(
324 | options);
325 | // Some relatively shortlist of instrumentation ratios that
326 | // eventually end up in zero:
327 | int[] max_ratios = {80, 65, 50, 35, 25, 15, 10, 5, 2, 1, 0};
328 | for (int i = 0; i < 100; i++) {
329 | try {
330 | return try_instrument_class(input, filename, try_options);
331 | } catch (RetryableInstrumentationException e) {
332 | JavaAflInstrument.total_locations = old_locations;
333 | if (try_options.ratio == 0) {
334 | System.err.println(
335 | "Unable instrument " + filename + " at all!");
336 | return e.class_data;
337 | }
338 | for (int new_ratio : max_ratios) {
339 | if (try_options.ratio > new_ratio) {
340 | try_options.ratio = new_ratio;
341 | System.err.println(
342 | "Instrumenting " + filename
343 | + " with ratio " + new_ratio + "!");
344 | break;
345 | }
346 | }
347 | }
348 | }
349 | assert false : "We should never reach here! File a bug!";
350 | throw new AssertionError("We should never reach here! File a bug!");
351 | }
352 |
353 | protected static InstrumentedClass try_instrument_class(
354 | byte[] input, String filename, InstrumentationOptions options)
355 | {
356 | if (!filename.endsWith(".class")) {
357 | return new InstrumentedClass(input);
358 | }
359 | // Work around ClassReader bug on zero length file:
360 | if (input.length == 0) {
361 | System.err.println("Empty file: " + filename);
362 | return new InstrumentedClass(input);
363 | }
364 | ClassReader reader;
365 | try {
366 | reader = new ClassReader(input);
367 | } catch (java.lang.IllegalArgumentException e) {
368 | System.err.println(
369 | "File " + filename + " is not a valid class file.");
370 | return new InstrumentedClass(input);
371 | }
372 | String name = reader.getClassName();
373 | File directory_file = new File(name).getParentFile();
374 | String directory = "";
375 | if (directory_file != null) {
376 | directory = directory_file.toString();
377 | }
378 | if (is_instrumented(reader)) {
379 | System.err.println("Already instrumented " + filename);
380 | total_classfiles--;
381 | return new InstrumentedClass(directory, input);
382 | }
383 | ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
384 | Random random;
385 | if (options.deterministic) {
386 | random = new Random(java.util.Arrays.hashCode(input));
387 | } else {
388 | random = new Random();
389 | }
390 | ClassVisitor visitor = new InstrumentingClassVisitor(
391 | writer, random, options);
392 | try {
393 | reader.accept(visitor, ClassReader.SKIP_DEBUG);
394 | } catch (java.lang.TypeNotPresentException e) {
395 | System.err.println(
396 | "Error while processing " + filename + ": " + e.getMessage());
397 | return new InstrumentedClass(directory, input);
398 | } catch (java.lang.IllegalArgumentException e) {
399 | System.err.println(
400 | "Error while processing " + filename + ": " + e.getMessage());
401 | return new InstrumentedClass(directory, input);
402 | }
403 |
404 | try {
405 | writer.newUTF8(javafl.JavaAfl.INSTRUMENTATION_MARKER);
406 | return new InstrumentedClass(directory, writer.toByteArray());
407 | } catch (java.lang.IndexOutOfBoundsException e) {
408 | // It's possible that the instrumentation makes the method
409 | // larger than 64 kilobytes that is the limit that Java
410 | // bytecode imposes on methods.
411 | throw new RetryableInstrumentationException(
412 | new InstrumentedClass(directory, input));
413 | }
414 | }
415 |
416 | private static File _instrument_jar(
417 | JarFile input, File output, InstrumentationOptions options) throws IOException
418 | {
419 | FileOutputStream output_jarfile = new FileOutputStream(output);
420 | JarOutputStream jar = new JarOutputStream(output_jarfile);
421 | Enumeration extends JarEntry> entries = input.entries();
422 |
423 | while (entries.hasMoreElements()) {
424 | JarEntry entry = entries.nextElement();
425 | InputStream stream = input.getInputStream(entry);
426 |
427 | if (entry.isDirectory()) {
428 | jar.putNextEntry(new JarEntry(entry));
429 | continue;
430 | }
431 | InstrumentedClass instrumented = instrument_class(
432 | input_stream_to_bytes(stream),
433 | input.getName() + "/" + entry.getName(),
434 | options);
435 | byte[] instrumented_class = instrumented.data;
436 | jar.putNextEntry(new JarEntry(entry.getName()));
437 | if (instrumented_class == null) {
438 | jar.write(input_stream_to_bytes(stream));
439 | } else {
440 | jar.write(instrumented_class);
441 | }
442 | }
443 | add_JavaAfl_to_jar(jar);
444 | jar.close();
445 | return output;
446 | }
447 |
448 | private static void _instrument_file(
449 | File output_dir, String filename, InstrumentationOptions options)
450 | {
451 | if (filename.endsWith(".class")) {
452 | instrument_classfile(output_dir, filename, options);
453 | return;
454 | }
455 | File source_file = new File(filename);
456 | File physical_output = null;
457 | File rename_target = new File(output_dir, source_file.getName());
458 | try {
459 | JarFile jar_input = new JarFile(source_file, false);
460 | physical_output = File.createTempFile(
461 | ".java-afl-new-", ".jar", output_dir);
462 | _instrument_jar(
463 | jar_input, physical_output, options);
464 | physical_output.renameTo(rename_target);
465 | total_jarfiles++;
466 | } catch (java.io.FileNotFoundException e) {
467 | System.err.println(
468 | "File " + filename + " is not a valid file: " + e.getMessage());
469 | } catch (IOException e) {
470 | if (physical_output != null) {
471 | physical_output.delete();
472 | }
473 | System.err.println("Failed to read jar " + filename + ": " + e.getMessage());
474 | }
475 | }
476 |
477 | private static byte[] input_stream_to_bytes(InputStream stream)
478 | {
479 | ByteArrayOutputStream bytestream = new ByteArrayOutputStream();
480 | byte[] buffer = new byte[4096];
481 | try {
482 | int read = stream.read(buffer);
483 | while (read > 0) {
484 | bytestream.write(buffer, 0, read);
485 | read = stream.read(buffer);
486 | }
487 | } catch (IOException e) {
488 | throw new RuntimeException(e);
489 | }
490 | return bytestream.toByteArray();
491 | }
492 |
493 | private static void add_JavaAfl_to_jar(JarOutputStream jar)
494 | {
495 | String[] filenames = {
496 | "javafl/CustomInit.class",
497 | "javafl/fuzz.class",
498 | "javafl/JavaAfl.class",
499 | "javafl/JavaAfl$1.class",
500 | };
501 | try {
502 | try {
503 | jar.putNextEntry(new JarEntry("javafl/"));
504 | } catch (java.util.zip.ZipException e) {
505 | System.err.println("Jar already has javafl/");
506 | }
507 | for (String filename : filenames) {
508 | try {
509 | jar.putNextEntry(new JarEntry(filename));
510 | jar.write(
511 | input_stream_to_bytes(
512 | (InputStream)JavaAflInstrument.class.getResource("/" + filename).getContent()));
513 | } catch (java.util.zip.ZipException e) {
514 | System.err.println("Jar already has " + filename);
515 | }
516 | }
517 | } catch (IOException e) {
518 | throw new RuntimeException(e);
519 | }
520 | }
521 |
522 | private static void add_JavaAfl_to_directory(File directory)
523 | {
524 | String[] filenames = {
525 | "javafl/CustomInit.class",
526 | "javafl/fuzz.class",
527 | "javafl/JavaAfl.class",
528 | "javafl/JavaAfl$1.class",
529 | };
530 | try {
531 | for (String filename : filenames) {
532 | File target = new File(directory, filename);
533 | File class_directory = target.getParentFile();
534 | if (!class_directory.exists()) {
535 | class_directory.mkdirs();
536 | }
537 | FileOutputStream output = new FileOutputStream(target);
538 | output.write(
539 | input_stream_to_bytes(
540 | (InputStream)JavaAflInstrument.class.getResource("/" + filename).getContent()));
541 | }
542 | } catch (IOException e) {
543 | throw new RuntimeException(e);
544 | }
545 | }
546 |
547 | private static int usage()
548 | {
549 | System.err.println(
550 | "Usage: instrumentor [--custom-init]|[--deterministic]|[--] output-dir input.jar|input.class...");
551 | return 1;
552 | }
553 |
554 | public static void main(String args[]) throws IOException
555 | {
556 | if (args.length < 2) {
557 | System.exit(usage());
558 | }
559 |
560 | InstrumentationOptions options = new InstrumentationOptions(
561 | 100, false, false);
562 | boolean parsing = true;
563 | int arg_index = -1;
564 | while (parsing) {
565 | arg_index++;
566 | String argument = args[arg_index];
567 | if (!argument.startsWith("--")) {
568 | parsing = false;
569 | } else if (argument.equals("--custom-init")) {
570 | options.has_custom_init = true;
571 | } else if (argument.equals("--deterministic")) {
572 | options.deterministic = true;
573 | } else if (argument.equals("-h") || argument.equals("--help")) {
574 | System.exit(usage());
575 | } else if (argument.equals("--")) {
576 | parsing = false;
577 | } else if (argument.startsWith("--")) {
578 | System.exit(usage());
579 | }
580 | }
581 | if (args.length <= arg_index) {
582 | System.exit(usage());
583 | }
584 |
585 | String ratio_str = System.getenv("AFL_INST_RATIO");
586 | if (ratio_str != null) {
587 | options.ratio = Integer.parseInt(ratio_str);
588 | }
589 | ratio_str = System.getenv("JAVA_AFL_INST_RATIO");
590 | if (ratio_str != null) {
591 | options.ratio = Integer.parseInt(ratio_str);
592 | }
593 | if (options.ratio < 0 || options.ratio > 100) {
594 | System.err.println("AFL_INST_RATIO must be between 0 and 100!");
595 | System.exit(1);
596 | }
597 |
598 | File output_dir = new File(args[arg_index]);
599 | if (!output_dir.exists()) {
600 | if (!output_dir.mkdirs()) {
601 | System.err.println("Unable to create output directory!");
602 | return;
603 | }
604 | }
605 | if (!output_dir.isDirectory()) {
606 | System.err.println(
607 | "Output directory " + output_dir + " is not a directory!");
608 | return;
609 | }
610 | for (int i = arg_index + 1; i < args.length; i++) {
611 | _instrument_file(output_dir, args[i], options);
612 | }
613 | if (total_classfiles > 0) {
614 | add_JavaAfl_to_directory(output_dir);
615 | }
616 | System.out.println(
617 | "Output files are available at " + output_dir.getCanonicalPath());
618 | System.out.println(
619 | "Instrumented " + total_classfiles + " .class files and "
620 | + total_jarfiles + " .jar files with "
621 | + total_locations + " locations.");
622 | }
623 | }
624 |
--------------------------------------------------------------------------------