diff --git a/bramble-api/src/main/java/org/briarproject/bramble/util/StringUtils.java b/bramble-api/src/main/java/org/briarproject/bramble/util/StringUtils.java index b6d082098e0fada88f2712414d224d25325187db..b13fb6030665d260ad7b4cf24b38028035619d89 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/util/StringUtils.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/util/StringUtils.java @@ -47,7 +47,7 @@ public class StringUtils { try { return s.getBytes("UTF-8"); } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); + throw new AssertionError(e); } } @@ -63,7 +63,7 @@ public class StringUtils { try { return decoder.decode(buffer).toString(); } catch (CharacterCodingException e) { - throw new RuntimeException(e); + throw new AssertionError(e); } } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/data/BdfReaderImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/data/BdfReaderImpl.java index d73e5609bdac4b712f23c7fd753b6abfee1fde53..788826a575f4a671c12dc840e293e8da9fdb2c43 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/data/BdfReaderImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/data/BdfReaderImpl.java @@ -29,6 +29,7 @@ import static org.briarproject.bramble.data.Types.STRING_16; import static org.briarproject.bramble.data.Types.STRING_32; import static org.briarproject.bramble.data.Types.STRING_8; import static org.briarproject.bramble.data.Types.TRUE; +import static org.briarproject.bramble.util.StringUtils.fromUtf8; @NotThreadSafe @NotNullByDefault @@ -253,7 +254,7 @@ class BdfReaderImpl implements BdfReader { if (length < 0 || length > maxBufferSize) throw new FormatException(); if (length == 0) return ""; readIntoBuffer(length); - return new String(buf, 0, length, "UTF-8"); + return fromUtf8(buf, 0, length); } private int readStringLength() throws IOException { diff --git a/bramble-core/src/test/java/org/briarproject/bramble/data/BdfReaderImplFuzzingTest.java b/bramble-core/src/test/java/org/briarproject/bramble/data/BdfReaderImplFuzzingTest.java new file mode 100644 index 0000000000000000000000000000000000000000..852e66853c91b2fb22aa7b8d3773be6f76ec02cf --- /dev/null +++ b/bramble-core/src/test/java/org/briarproject/bramble/data/BdfReaderImplFuzzingTest.java @@ -0,0 +1,41 @@ +package org.briarproject.bramble.data; + +import org.briarproject.bramble.test.BrambleTestCase; +import org.junit.Before; +import org.junit.Test; + +import java.io.ByteArrayInputStream; +import java.util.Random; + +import static org.briarproject.bramble.api.data.BdfReader.DEFAULT_MAX_BUFFER_SIZE; +import static org.briarproject.bramble.api.data.BdfReader.DEFAULT_NESTED_LIMIT; +import static org.briarproject.bramble.test.TestUtils.isOptionalTestEnabled; +import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeTrue; + +public class BdfReaderImplFuzzingTest extends BrambleTestCase { + + @Before + public void setUp() { + assumeTrue(isOptionalTestEnabled(BdfReaderImplFuzzingTest.class)); + } + + @Test + public void testStringFuzzing() throws Exception { + Random random = new Random(); + byte[] buf = new byte[22]; + ByteArrayInputStream in = new ByteArrayInputStream(buf); + for (int i = 0; i < 100_000_000; i++) { + random.nextBytes(buf); + buf[0] = 0x41; // String with 1-byte length + buf[1] = 0x14; // Length 20 bytes + in.reset(); + BdfReaderImpl r = new BdfReaderImpl(in, DEFAULT_NESTED_LIMIT, + DEFAULT_MAX_BUFFER_SIZE); + int length = r.readString().length(); + assertTrue(length >= 0); + assertTrue(length <= 20); + assertTrue(r.eof()); + } + } +} diff --git a/bramble-java/src/main/java/org/briarproject/bramble/plugin/modem/ModemImpl.java b/bramble-java/src/main/java/org/briarproject/bramble/plugin/modem/ModemImpl.java index c5c9cd710c2a92e1e698c6676b562471be6aa9b5..3bf2867cf6ef806eab82aba4d2a1440862cc4a1d 100644 --- a/bramble-java/src/main/java/org/briarproject/bramble/plugin/modem/ModemImpl.java +++ b/bramble-java/src/main/java/org/briarproject/bramble/plugin/modem/ModemImpl.java @@ -10,6 +10,10 @@ import org.briarproject.bramble.api.system.Clock; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.charset.CharacterCodingException; +import java.nio.charset.Charset; +import java.nio.charset.CharsetDecoder; import java.util.concurrent.Executor; import java.util.concurrent.Semaphore; import java.util.concurrent.locks.Condition; @@ -22,6 +26,7 @@ import javax.annotation.Nullable; import jssc.SerialPortEvent; import jssc.SerialPortEventListener; +import static java.nio.charset.CodingErrorAction.IGNORE; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.logging.Level.INFO; import static java.util.logging.Level.WARNING; @@ -35,6 +40,8 @@ class ModemImpl implements Modem, WriteHandler, SerialPortEventListener { private static final Logger LOG = Logger.getLogger(ModemImpl.class.getName()); + + private static final Charset US_ASCII = Charset.forName("US-ASCII"); private static final int MAX_LINE_LENGTH = 256; private static final int[] BAUD_RATES = { 256000, 128000, 115200, 57600, 38400, 19200, 14400, 9600, 4800, 1200 @@ -106,7 +113,7 @@ class ModemImpl implements Modem, WriteHandler, SerialPortEventListener { throw e; } // Wait for the event thread to receive "OK" - boolean success = false; + boolean success; try { lock.lock(); try { @@ -353,8 +360,7 @@ class ModemImpl implements Modem, WriteHandler, SerialPortEventListener { for (int i = 0; i < b.length; i++) { line[lineLen] = b[i]; if (b[i] == '\n') { - // FIXME: Use CharsetDecoder to catch invalid ASCII - String s = new String(line, 0, lineLen, "US-ASCII").trim(); + String s = toAscii(line, lineLen).trim(); lineLen = 0; if (LOG.isLoggable(INFO)) LOG.info("Modem status: " + s); if (s.startsWith("CONNECT")) { @@ -436,7 +442,7 @@ class ModemImpl implements Modem, WriteHandler, SerialPortEventListener { throw e; } // Wait for the event thread to receive "CONNECT" - boolean success = false; + boolean success; try { lock.lock(); try { @@ -461,4 +467,16 @@ class ModemImpl implements Modem, WriteHandler, SerialPortEventListener { stateChange.release(); } } + + private String toAscii(byte[] bytes, int len) { + CharsetDecoder decoder = US_ASCII.newDecoder(); + decoder.onMalformedInput(IGNORE); + decoder.onUnmappableCharacter(IGNORE); + ByteBuffer buffer = ByteBuffer.wrap(bytes, 0, len); + try { + return decoder.decode(buffer).toString(); + } catch (CharacterCodingException e) { + throw new AssertionError(e); + } + } }