mirror of https://github.com/hyperledger/besu
Performance improvements to EOF layout fuzzing (#7545)
* Performance improvements to fuzzing Turning off guidance speeds the rate of testing up by 10%. Also, add other options to store new guided-discovered tests. Signed-off-by: Danno Ferrin <danno@numisight.com> * bring in the whole javafuzz lib so we can tweak it. Signed-off-by: Danno Ferrin <danno@numisight.com> --------- Signed-off-by: Danno Ferrin <danno@numisight.com> Co-authored-by: Sally MacFarlane <macfarla.github@gmail.com>pull/7551/head
parent
2339c1df20
commit
fa73102097
@ -0,0 +1,449 @@ |
|||||||
|
/* |
||||||
|
* Copyright contributors to Hyperledger Besu. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||||
|
* the License. You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||||
|
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||||
|
* specific language governing permissions and limitations under the License. |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: Apache-2.0 |
||||||
|
*/ |
||||||
|
package org.hyperledger.besu.testfuzz.javafuzz; |
||||||
|
|
||||||
|
import org.hyperledger.besu.crypto.MessageDigestFactory; |
||||||
|
|
||||||
|
import java.io.File; |
||||||
|
import java.io.FileOutputStream; |
||||||
|
import java.io.IOException; |
||||||
|
import java.math.BigInteger; |
||||||
|
import java.nio.file.Files; |
||||||
|
import java.security.MessageDigest; |
||||||
|
import java.security.NoSuchAlgorithmException; |
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.Arrays; |
||||||
|
import java.util.List; |
||||||
|
import java.util.concurrent.ThreadLocalRandom; |
||||||
|
|
||||||
|
/** |
||||||
|
* Ported from <a |
||||||
|
* href="https://gitlab.com/gitlab-org/security-products/analyzers/fuzzers/javafuzz">...</a> because |
||||||
|
* fields like max input size were not configurable. |
||||||
|
*/ |
||||||
|
@SuppressWarnings("CatchAndPrintStackTrace") |
||||||
|
public class Corpus { |
||||||
|
private final ArrayList<byte[]> inputs; |
||||||
|
private final int maxInputSize; |
||||||
|
private static final int[] INTERESTING8 = {-128, -1, 0, 1, 16, 32, 64, 100, 127}; |
||||||
|
private static final int[] INTERESTING16 = { |
||||||
|
-32768, -129, 128, 255, 256, 512, 1000, 1024, 4096, 32767, -128, -1, 0, 1, 16, 32, 64, 100, 127 |
||||||
|
}; |
||||||
|
private static final int[] INTERESTING32 = { |
||||||
|
-2147483648, |
||||||
|
-100663046, |
||||||
|
-32769, |
||||||
|
32768, |
||||||
|
65535, |
||||||
|
65536, |
||||||
|
100663045, |
||||||
|
2147483647, |
||||||
|
-32768, |
||||||
|
-129, |
||||||
|
128, |
||||||
|
255, |
||||||
|
256, |
||||||
|
512, |
||||||
|
1000, |
||||||
|
1024, |
||||||
|
4096, |
||||||
|
32767, |
||||||
|
-128, |
||||||
|
-1, |
||||||
|
0, |
||||||
|
1, |
||||||
|
16, |
||||||
|
32, |
||||||
|
64, |
||||||
|
100, |
||||||
|
127 |
||||||
|
}; |
||||||
|
private String corpusPath; |
||||||
|
private int seedLength; |
||||||
|
|
||||||
|
/** |
||||||
|
* Create a corpus |
||||||
|
* |
||||||
|
* @param dirs The directory to store the corpus files |
||||||
|
*/ |
||||||
|
public Corpus(final String dirs) { |
||||||
|
this.maxInputSize = 0xc001; // 48k+1
|
||||||
|
this.corpusPath = null; |
||||||
|
this.inputs = new ArrayList<>(); |
||||||
|
if (dirs != null) { |
||||||
|
String[] arr = dirs.split(",", -1); |
||||||
|
for (String s : arr) { |
||||||
|
File f = new File(s); |
||||||
|
if (!f.exists()) { |
||||||
|
f.mkdirs(); |
||||||
|
} |
||||||
|
if (f.isDirectory()) { |
||||||
|
if (this.corpusPath == null) { |
||||||
|
this.corpusPath = f.getPath(); |
||||||
|
} |
||||||
|
this.loadDir(f); |
||||||
|
} else { |
||||||
|
try { |
||||||
|
this.inputs.add(Files.readAllBytes(f.toPath())); |
||||||
|
} catch (IOException e) { |
||||||
|
e.printStackTrace(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
this.seedLength = this.inputs.size(); |
||||||
|
} |
||||||
|
|
||||||
|
int getLength() { |
||||||
|
return this.inputs.size(); |
||||||
|
} |
||||||
|
|
||||||
|
private boolean randBool() { |
||||||
|
return ThreadLocalRandom.current().nextBoolean(); |
||||||
|
} |
||||||
|
|
||||||
|
private int rand(final int max) { |
||||||
|
return ThreadLocalRandom.current().nextInt(0, max); |
||||||
|
} |
||||||
|
|
||||||
|
private void loadDir(final File dir) { |
||||||
|
for (final File f : dir.listFiles()) { |
||||||
|
if (f.isFile()) { |
||||||
|
try { |
||||||
|
this.inputs.add(Files.readAllBytes(f.toPath())); |
||||||
|
} catch (IOException e) { |
||||||
|
e.printStackTrace(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
byte[] generateInput() throws NoSuchAlgorithmException { |
||||||
|
if (this.seedLength != 0) { |
||||||
|
this.seedLength--; |
||||||
|
return this.inputs.get(this.seedLength); |
||||||
|
} |
||||||
|
if (this.inputs.isEmpty()) { |
||||||
|
byte[] buf = new byte[] {}; |
||||||
|
this.putBuffer(buf); |
||||||
|
return buf; |
||||||
|
} |
||||||
|
byte[] buf = this.inputs.get(this.rand(this.inputs.size())); |
||||||
|
return this.mutate(buf); |
||||||
|
} |
||||||
|
|
||||||
|
void putBuffer(final byte[] buf) throws NoSuchAlgorithmException { |
||||||
|
if (this.inputs.contains(buf)) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
this.inputs.add(buf); |
||||||
|
|
||||||
|
writeCorpusFile(buf); |
||||||
|
} |
||||||
|
|
||||||
|
private void writeCorpusFile(final byte[] buf) throws NoSuchAlgorithmException { |
||||||
|
if (this.corpusPath != null) { |
||||||
|
MessageDigest md = MessageDigestFactory.create("SHA-256"); |
||||||
|
md.update(buf); |
||||||
|
byte[] digest = md.digest(); |
||||||
|
String hex = String.format("%064x", new BigInteger(1, digest)); |
||||||
|
try (FileOutputStream fos = new FileOutputStream(this.corpusPath + "/" + hex)) { |
||||||
|
fos.write(buf); |
||||||
|
} catch (IOException e) { |
||||||
|
e.printStackTrace(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private String dec2bin(final int dec) { |
||||||
|
String bin = Integer.toBinaryString(dec); |
||||||
|
String padding = new String(new char[32 - bin.length()]).replace("\0", "0"); |
||||||
|
return padding + bin; |
||||||
|
} |
||||||
|
|
||||||
|
private int exp2() { |
||||||
|
String bin = dec2bin(this.rand((int) Math.pow(2, 32))); |
||||||
|
int count = 0; |
||||||
|
for (int i = 0; i < 32; i++) { |
||||||
|
if (bin.charAt(i) == '0') { |
||||||
|
count++; |
||||||
|
} else { |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
return count; |
||||||
|
} |
||||||
|
|
||||||
|
int chooseLen(final int n) { |
||||||
|
int x = this.rand(100); |
||||||
|
if (x < 90) { |
||||||
|
return this.rand(Math.min(8, n)) + 1; |
||||||
|
} else if (x < 99) { |
||||||
|
return this.rand(Math.min(32, n)) + 1; |
||||||
|
} else { |
||||||
|
return this.rand(n) + 1; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
static void copy( |
||||||
|
final byte[] src, final int srcPos, final byte[] dst, final int dstPos, final int length) { |
||||||
|
System.arraycopy(src, srcPos, dst, dstPos, Math.min(length, src.length - srcPos)); |
||||||
|
} |
||||||
|
|
||||||
|
static void copy(final byte[] src, final int srcPos, final byte[] dst, final int dstPos) { |
||||||
|
System.arraycopy(src, srcPos, dst, dstPos, Math.min(src.length - srcPos, dst.length - dstPos)); |
||||||
|
} |
||||||
|
|
||||||
|
static byte[] concatZeros(final byte[] a, final int n) { |
||||||
|
byte[] c = new byte[a.length + n]; |
||||||
|
Arrays.fill(c, (byte) 0); |
||||||
|
System.arraycopy(a, 0, c, 0, a.length); |
||||||
|
return c; |
||||||
|
} |
||||||
|
|
||||||
|
byte[] mutate(final byte[] buf) { |
||||||
|
byte[] res = buf.clone(); |
||||||
|
int nm = 1 + this.exp2(); |
||||||
|
for (int i = 0; i < nm; i++) { |
||||||
|
int x = this.rand(16); |
||||||
|
if (x == 0) { |
||||||
|
// Remove a range of bytes.
|
||||||
|
if (res.length <= 1) { |
||||||
|
i--; |
||||||
|
continue; |
||||||
|
} |
||||||
|
int pos0 = this.rand(res.length); |
||||||
|
int pos1 = pos0 + this.chooseLen(res.length - pos0); |
||||||
|
copy(res, pos1, res, pos0, res.length - pos0); |
||||||
|
res = Arrays.copyOfRange(res, 0, res.length - (pos1 - pos0)); |
||||||
|
} else if (x == 1) { |
||||||
|
// Insert a range of random bytes.
|
||||||
|
int pos = this.rand(res.length + 1); |
||||||
|
int n = this.chooseLen(10); |
||||||
|
res = concatZeros(res, n); |
||||||
|
copy(res, pos, res, pos + n); |
||||||
|
for (int k = 0; k < n; k++) { |
||||||
|
res[pos + k] = (byte) this.rand(256); |
||||||
|
} |
||||||
|
} else if (x == 2) { |
||||||
|
// Duplicate a range of bytes.
|
||||||
|
if (res.length <= 1) { |
||||||
|
i--; |
||||||
|
continue; |
||||||
|
} |
||||||
|
int src = this.rand(res.length); |
||||||
|
int dst = this.rand(res.length); |
||||||
|
while (src == dst) { |
||||||
|
dst = this.rand(res.length); |
||||||
|
} |
||||||
|
int n = this.chooseLen(res.length - src); |
||||||
|
byte[] tmp = new byte[n]; |
||||||
|
Arrays.fill(tmp, (byte) 0); |
||||||
|
copy(res, src, tmp, 0); |
||||||
|
res = concatZeros(res, n); |
||||||
|
copy(res, dst, res, dst + n); |
||||||
|
System.arraycopy(tmp, 0, res, dst, n); |
||||||
|
} else if (x == 3) { |
||||||
|
// Copy a range of bytes.
|
||||||
|
if (res.length <= 1) { |
||||||
|
i--; |
||||||
|
continue; |
||||||
|
} |
||||||
|
int src = this.rand(res.length); |
||||||
|
int dst = this.rand(res.length); |
||||||
|
while (src == dst) { |
||||||
|
dst = this.rand(res.length); |
||||||
|
} |
||||||
|
int n = this.chooseLen(res.length - src); |
||||||
|
copy(res, src + n, res, dst); |
||||||
|
} else if (x == 4) { |
||||||
|
// Bit flip. Spooky!
|
||||||
|
if (res.length <= 1) { |
||||||
|
i--; |
||||||
|
continue; |
||||||
|
} |
||||||
|
int pos = this.rand(res.length); |
||||||
|
res[pos] ^= (byte) (1 << (byte) this.rand(8)); |
||||||
|
} else if (x == 5) { |
||||||
|
// Set a byte to a random value.
|
||||||
|
if (res.length <= 1) { |
||||||
|
i--; |
||||||
|
continue; |
||||||
|
} |
||||||
|
int pos = this.rand(res.length); |
||||||
|
res[pos] ^= (byte) (this.rand(255) + 1); |
||||||
|
} else if (x == 6) { |
||||||
|
// Swap 2 bytes.
|
||||||
|
if (res.length <= 1) { |
||||||
|
i--; |
||||||
|
continue; |
||||||
|
} |
||||||
|
int src = this.rand(res.length); |
||||||
|
int dst = this.rand(res.length); |
||||||
|
while (src == dst) { |
||||||
|
dst = this.rand(res.length); |
||||||
|
} |
||||||
|
byte tmp1 = res[src]; |
||||||
|
res[src] = res[dst]; |
||||||
|
res[dst] = tmp1; |
||||||
|
} else if (x == 7) { |
||||||
|
// Add/subtract from a byte.
|
||||||
|
if (res.length == 0) { |
||||||
|
i--; |
||||||
|
continue; |
||||||
|
} |
||||||
|
int pos = this.rand(res.length); |
||||||
|
int v = this.rand(35) + 1; |
||||||
|
if (this.randBool()) { |
||||||
|
res[pos] += (byte) v; |
||||||
|
} else { |
||||||
|
res[pos] -= (byte) v; |
||||||
|
} |
||||||
|
} else if (x == 8) { |
||||||
|
// Add/subtract from a uint16.
|
||||||
|
i--; |
||||||
|
// if (res.length < 2) {
|
||||||
|
// i--;
|
||||||
|
// continue;
|
||||||
|
// }
|
||||||
|
// int pos = this.rand(res.length - 1);
|
||||||
|
// int v = this.rand(35) + 1;
|
||||||
|
// if (this.randBool()) {
|
||||||
|
// v = 0 - v;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (this.randBool()) {
|
||||||
|
// res[pos] =
|
||||||
|
// } else {
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
|
||||||
|
} else if (x == 9) { |
||||||
|
i--; |
||||||
|
// Add/subtract from a uint32.
|
||||||
|
} else if (x == 10) { |
||||||
|
// Replace a byte with an interesting value.
|
||||||
|
if (res.length == 0) { |
||||||
|
i--; |
||||||
|
continue; |
||||||
|
} |
||||||
|
int pos = this.rand(res.length); |
||||||
|
res[pos] = (byte) INTERESTING8[this.rand(INTERESTING8.length)]; |
||||||
|
} else if (x == 11) { |
||||||
|
// Replace an uint16 with an interesting value.
|
||||||
|
if (res.length < 2) { |
||||||
|
i--; |
||||||
|
continue; |
||||||
|
} |
||||||
|
int pos = this.rand(res.length - 1); |
||||||
|
if (this.randBool()) { |
||||||
|
res[pos] = (byte) (INTERESTING16[this.rand(INTERESTING16.length)] & 0xFF); |
||||||
|
res[pos + 1] = (byte) ((INTERESTING16[this.rand(INTERESTING16.length)] >> 8) & 0xFF); |
||||||
|
} else { |
||||||
|
res[pos + 1] = (byte) (INTERESTING16[this.rand(INTERESTING16.length)] & 0xFF); |
||||||
|
res[pos] = (byte) ((INTERESTING16[this.rand(INTERESTING16.length)] >> 8) & 0xFF); |
||||||
|
} |
||||||
|
} else if (x == 12) { |
||||||
|
// Replace an uint32 with an interesting value.
|
||||||
|
if (res.length < 4) { |
||||||
|
i--; |
||||||
|
continue; |
||||||
|
} |
||||||
|
int pos = this.rand(res.length - 3); |
||||||
|
if (this.randBool()) { |
||||||
|
res[pos] = (byte) (INTERESTING32[this.rand(INTERESTING32.length)] & 0xFF); |
||||||
|
res[pos + 1] = (byte) ((INTERESTING32[this.rand(INTERESTING32.length)] >> 8) & 0xFF); |
||||||
|
res[pos + 2] = (byte) ((INTERESTING32[this.rand(INTERESTING32.length)] >> 16) & 0xFF); |
||||||
|
res[pos + 3] = (byte) ((INTERESTING32[this.rand(INTERESTING32.length)] >> 24) & 0xFF); |
||||||
|
} else { |
||||||
|
res[pos + 3] = (byte) (INTERESTING32[this.rand(INTERESTING32.length)] & 0xFF); |
||||||
|
res[pos + 2] = (byte) ((INTERESTING32[this.rand(INTERESTING32.length)] >> 8) & 0xFF); |
||||||
|
res[pos + 1] = (byte) ((INTERESTING32[this.rand(INTERESTING32.length)] >> 16) & 0xFF); |
||||||
|
res[pos] = (byte) ((INTERESTING32[this.rand(INTERESTING32.length)] >> 24) & 0xFF); |
||||||
|
} |
||||||
|
} else if (x == 13) { |
||||||
|
// Replace an ascii digit with another digit.
|
||||||
|
List<Integer> digits = new ArrayList<>(); |
||||||
|
for (int k = 0; k < res.length; k++) { |
||||||
|
if (res[k] >= 48 && res[k] <= 57) { |
||||||
|
digits.add(k); |
||||||
|
} |
||||||
|
} |
||||||
|
if (digits.isEmpty()) { |
||||||
|
i--; |
||||||
|
continue; |
||||||
|
} |
||||||
|
int pos = this.rand(digits.size()); |
||||||
|
int was = res[digits.get(pos)]; |
||||||
|
int now = was; |
||||||
|
while (now == was) { |
||||||
|
now = this.rand(10) + 48; |
||||||
|
} |
||||||
|
res[digits.get(pos)] = (byte) now; |
||||||
|
} else if (x == 14) { |
||||||
|
// Splice another input.
|
||||||
|
if (res.length < 4 || this.inputs.size() < 2) { |
||||||
|
i--; |
||||||
|
continue; |
||||||
|
} |
||||||
|
byte[] other = this.inputs.get(this.rand(this.inputs.size())); |
||||||
|
if (other.length < 4) { |
||||||
|
i--; |
||||||
|
continue; |
||||||
|
} |
||||||
|
// Find common prefix and suffix.
|
||||||
|
int idx0 = 0; |
||||||
|
while (idx0 < res.length && idx0 < other.length && res[idx0] == other[idx0]) { |
||||||
|
idx0++; |
||||||
|
} |
||||||
|
int idx1 = 0; |
||||||
|
while (idx1 < res.length |
||||||
|
&& idx1 < other.length |
||||||
|
&& res[res.length - idx1 - 1] == other[other.length - idx1 - 1]) { |
||||||
|
idx1++; |
||||||
|
} |
||||||
|
int diff = Math.min(res.length - idx0 - idx1, other.length - idx0 - idx1); |
||||||
|
if (diff < 4) { |
||||||
|
i--; |
||||||
|
continue; |
||||||
|
} |
||||||
|
copy(other, idx0, res, idx0, Math.min(other.length, idx0 + this.rand(diff - 2) + 1) - idx0); |
||||||
|
} else if (x == 15) { |
||||||
|
// Insert a part of another input.
|
||||||
|
if (res.length < 4 || this.inputs.size() < 2) { |
||||||
|
i--; |
||||||
|
continue; |
||||||
|
} |
||||||
|
byte[] other = this.inputs.get(this.rand(this.inputs.size())); |
||||||
|
if (other.length < 4) { |
||||||
|
i--; |
||||||
|
continue; |
||||||
|
} |
||||||
|
int pos0 = this.rand(res.length + 1); |
||||||
|
int pos1 = this.rand(other.length - 2); |
||||||
|
int n = this.chooseLen(other.length - pos1 - 2) + 2; |
||||||
|
res = concatZeros(res, n); |
||||||
|
copy(res, pos0, res, pos0 + n); |
||||||
|
if (n >= 0) System.arraycopy(other, pos1, res, pos0, n); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (res.length > this.maxInputSize) { |
||||||
|
res = Arrays.copyOfRange(res, 0, this.maxInputSize); |
||||||
|
} |
||||||
|
return res; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,31 @@ |
|||||||
|
/* |
||||||
|
* Copyright contributors to Hyperledger Besu. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||||
|
* the License. You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||||
|
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||||
|
* specific language governing permissions and limitations under the License. |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: Apache-2.0 |
||||||
|
*/ |
||||||
|
package org.hyperledger.besu.testfuzz.javafuzz; |
||||||
|
|
||||||
|
/** |
||||||
|
* Adapted from <a |
||||||
|
* href="https://gitlab.com/gitlab-org/security-products/analyzers/fuzzers/javafuzz">...</a> because |
||||||
|
* I wanted it to be a functional interface
|
||||||
|
*/ |
||||||
|
@FunctionalInterface |
||||||
|
public interface FuzzTarget { |
||||||
|
|
||||||
|
/** |
||||||
|
* The target to fuzz |
||||||
|
* |
||||||
|
* @param data data proviced by the fuzzer |
||||||
|
*/ |
||||||
|
void fuzz(byte[] data); |
||||||
|
} |
@ -0,0 +1,14 @@ |
|||||||
|
## Credits & Acknowledgments |
||||||
|
|
||||||
|
These classes were ported from [Javafuzz](https://gitlab.com/gitlab-org/security-products/analyzers/fuzzers/javafuzz/). |
||||||
|
Javafuzz is a port of [fuzzitdev/jsfuzz](https://gitlab.com/gitlab-org/security-products/analyzers/fuzzers/jsfuzz). |
||||||
|
|
||||||
|
Which in turn based based on [go-fuzz](https://github.com/dvyukov/go-fuzz) originally developed by [Dmitry Vyukov's](https://twitter.com/dvyukov). |
||||||
|
Which is in turn heavily based on [Michal Zalewski](https://twitter.com/lcamtuf) [AFL](http://lcamtuf.coredump.cx/afl/). |
||||||
|
|
||||||
|
## Changes |
||||||
|
|
||||||
|
* Increased max binary size to 48k+1 |
||||||
|
* ported AbstractFuzzTarget to a functional interface FuzzTarget |
||||||
|
* Fixed some incompatibilities with JaCoCo |
||||||
|
* Besu style required changes (formatting, final params, etc.) |
Loading…
Reference in new issue