From 6cef4ccbb195a116557fe21c122f78ee2a2d0958 Mon Sep 17 00:00:00 2001 From: zzz Date: Fri, 7 Feb 2025 12:18:52 -0500 Subject: [PATCH] Data: Check key order when parsing RI mappings and fail-fast if out-of-order before the signature check --- core/java/src/net/i2p/data/DataHelper.java | 28 ++++++++++++++++++- .../net/i2p/data/router/RouterAddress.java | 4 ++- .../src/net/i2p/data/router/RouterInfo.java | 4 ++- 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/core/java/src/net/i2p/data/DataHelper.java b/core/java/src/net/i2p/data/DataHelper.java index 0177096f8..35eb51fde 100644 --- a/core/java/src/net/i2p/data/DataHelper.java +++ b/core/java/src/net/i2p/data/DataHelper.java @@ -134,7 +134,8 @@ public class DataHelper { * is repeated until there are no more bytes (not characters!) left as defined by the * first two byte integer. * - * As of 0.9.18, throws DataFormatException on duplicate key + * As of 0.9.18, throws DataFormatException on duplicate key + * Does NOT enforce key ordering. * * @param rawStream stream to read the mapping from * @throws DataFormatException if the format is invalid @@ -152,6 +153,7 @@ public class DataHelper { * Ditto, load into an existing properties * * As of 0.9.18, throws DataFormatException on duplicate key + * Does NOT enforce key ordering. * * @param props The Properties to load into. * As of 0.9.38, if null, a new OrderedProperties will be created. @@ -163,6 +165,26 @@ public class DataHelper { * @since 0.8.13 */ public static Properties readProperties(InputStream rawStream, Properties props) + throws DataFormatException, IOException { + return readProperties(rawStream, props, false); + } + + /** + * Ditto, load into an existing properties + * + * As of 0.9.18, throws DataFormatException on duplicate key + * + * @param props The Properties to load into. + * As of 0.9.38, if null, a new OrderedProperties will be created. + * @param rawStream stream to read the mapping from + * @param enforceOrder if true, throw DataFormatException if keys are not ordered + * @throws DataFormatException if the format is invalid + * @throws IOException if there is a problem reading the data + * @return the parameter props, or (as of 0.9.38) a new OrderedProperties if props is null, + * and an immutable EmptyProperties if empty. + * @since 0.9.66 + */ + public static Properties readProperties(InputStream rawStream, Properties props, boolean enforceOrder) throws DataFormatException, IOException { int size = (int) readLong(rawStream, 2); if (size == 0) { @@ -174,11 +196,15 @@ public class DataHelper { // full read guaranteed read(rawStream, data); ByteArrayInputStream in = new ByteArrayInputStream(data); + String prev = ""; while (in.available() > 0) { String key = readString(in); String cached = _propertiesKeyCache.get(key); if (cached != null) key = cached; + if (enforceOrder && key.compareTo(prev) <= 0) + throw new DataFormatException("option " + key + " out of order"); + prev = key; int b = in.read(); if (b != '=') throw new DataFormatException("Bad key"); diff --git a/router/java/src/net/i2p/data/router/RouterAddress.java b/router/java/src/net/i2p/data/router/RouterAddress.java index 21ab3c9f3..02ac8278c 100644 --- a/router/java/src/net/i2p/data/router/RouterAddress.java +++ b/router/java/src/net/i2p/data/router/RouterAddress.java @@ -292,7 +292,9 @@ public class RouterAddress extends DataStructureImpl { _transportStyle = "NTCP"; else if (_transportStyle.equals("SSU2")) _transportStyle = "SSU2"; - DataHelper.readProperties(in, _options); + // enforce mapping order so bad ones will fail-fast + // before the signature check + DataHelper.readProperties(in, _options, true); } /** diff --git a/router/java/src/net/i2p/data/router/RouterInfo.java b/router/java/src/net/i2p/data/router/RouterInfo.java index d8dae8d64..49cebff4d 100644 --- a/router/java/src/net/i2p/data/router/RouterInfo.java +++ b/router/java/src/net/i2p/data/router/RouterInfo.java @@ -606,7 +606,9 @@ public class RouterInfo extends DatabaseEntry { _peers.add(peerIdentityHash); } } - DataHelper.readProperties(din, _options); + // enforce mapping order so bad ones will fail-fast + // before the signature check + DataHelper.readProperties(din, _options, true); _signature = new Signature(type); _signature.readBytes(in);