├── 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 [![Build Status](https://travis-ci.org/Barro/java-afl.svg?branch=master)](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 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 | --------------------------------------------------------------------------------