diff --git a/router/java/src/net/i2p/data/i2np/TunnelDataMessage.java b/router/java/src/net/i2p/data/i2np/TunnelDataMessage.java index a55efc2b1fd40292ed79e00c3e1b609125d8b85c..70876ea64c1aab5776990e4dea9866640e477216 100644 --- a/router/java/src/net/i2p/data/i2np/TunnelDataMessage.java +++ b/router/java/src/net/i2p/data/i2np/TunnelDataMessage.java @@ -33,7 +33,7 @@ public class TunnelDataMessage extends I2NPMessageImpl { /** if we can't deliver a tunnel message in 10s, fuck it */ private static final int EXPIRATION_PERIOD = 10*1000; - private static final ByteCache _cache = ByteCache.getInstance(512, DATA_SIZE); + private static final ByteCache _cache; /** * When true, it means this tunnelDataMessage is being used as part of a tunnel * processing pipeline, where the byte array is acquired during the TunnelDataMessage's @@ -42,9 +42,63 @@ public class TunnelDataMessage extends I2NPMessageImpl { * handler's cache, etc), until it is finally released back into the cache when written * to the next peer (or explicitly by the fragment handler's completion). * Setting this to false just increases memory churn + * + * Well, this is tricky to get right and avoid data corruption, + * here's an example after checks were put in: + * + * + 10:57:05.197 CRIT [NTCP read 1 ] 2p.data.i2np.TunnelDataMessage: TDM boom + net.i2p.data.i2np.I2NPMessageException: TDM data buf use after free + at net.i2p.data.i2np.TunnelDataMessage.writeMessageBody(TunnelDataMessage.java:124) + at net.i2p.data.i2np.I2NPMessageImpl.toByteArray(I2NPMessageImpl.java:217) + at net.i2p.router.transport.ntcp.NTCPConnection.bufferedPrepare(NTCPConnection.java:678) + at net.i2p.router.transport.ntcp.NTCPConnection.send(NTCPConnection.java:293) + at net.i2p.router.transport.ntcp.NTCPTransport.outboundMessageReady(NTCPTransport.java:185) + at net.i2p.router.transport.TransportImpl.send(TransportImpl.java:357) + at net.i2p.router.transport.GetBidsJob.getBids(GetBidsJob.java:80) + at net.i2p.router.transport.CommSystemFacadeImpl.processMessage(CommSystemFacadeImpl.java:129) + at net.i2p.router.OutNetMessagePool.add(OutNetMessagePool.java:61) + at net.i2p.router.transport.TransportImpl.afterSend(TransportImpl.java:252) + at net.i2p.router.transport.TransportImpl.afterSend(TransportImpl.java:163) + at net.i2p.router.transport.udp.UDPTransport.failed(UDPTransport.java:1314) + at net.i2p.router.transport.udp.PeerState.add(PeerState.java:1064) + at net.i2p.router.transport.udp.OutboundMessageFragments.add(OutboundMessageFragments.java:146) + at net.i2p.router.transport.udp.UDPTransport.send(UDPTransport.java:1098) + at net.i2p.router.transport.GetBidsJob.getBids(GetBidsJob.java:80) + at net.i2p.router.transport.CommSystemFacadeImpl.processMessage(CommSystemFacadeImpl.java:129) + at net.i2p.router.OutNetMessagePool.add(OutNetMessagePool.java:61) + at net.i2p.router.tunnel.TunnelParticipant.send(TunnelParticipant.java:172) + at net.i2p.router.tunnel.TunnelParticipant.dispatch(TunnelParticipant.java:86) + at net.i2p.router.tunnel.TunnelDispatcher.dispatch(TunnelDispatcher.java:351) + at net.i2p.router.InNetMessagePool.doShortCircuitTunnelData(InNetMessagePool.java:306) + at net.i2p.router.InNetMessagePool.shortCircuitTunnelData(InNetMessagePool.java:291) + at net.i2p.router.InNetMessagePool.add(InNetMessagePool.java:160) + at net.i2p.router.transport.TransportManager.messageReceived(TransportManager.java:462) + at net.i2p.router.transport.TransportImpl.messageReceived(TransportImpl.java:416) + at net.i2p.router.transport.ntcp.NTCPConnection$ReadState.receiveLastBlock(NTCPConnection.java:1285) + at net.i2p.router.transport.ntcp.NTCPConnection$ReadState.receiveSubsequent(NTCPConnection.java:1248) + at net.i2p.router.transport.ntcp.NTCPConnection$ReadState.receiveBlock(NTCPConnection.java:1205) + at net.i2p.router.transport.ntcp.NTCPConnection.recvUnencryptedI2NP(NTCPConnection.java:1035) + at net.i2p.router.transport.ntcp.NTCPConnection.recvEncryptedI2NP(NTCPConnection.java:1018) + at net.i2p.router.transport.ntcp.Reader.processRead(Reader.java:167) + at net.i2p.router.transport.ntcp.Reader.access$400(Reader.java:17) + at net.i2p.router.transport.ntcp.Reader$Runner.run(Reader.java:106) + at java.lang.Thread.run(Thread.java:619) + at net.i2p.util.I2PThread.run(I2PThread.java:71) + * */ private static final boolean PIPELINED_CACHE = true; + static { + if (PIPELINED_CACHE) + _cache = ByteCache.getInstance(512, DATA_SIZE); + else + _cache = null; + } + + /** For use-after-free checks. Always false if PIPELINED_CACHE is false. */ + private boolean _hadCache; + public TunnelDataMessage(I2PAppContext context) { super(context); _log = context.logManager().getLog(TunnelDataMessage.class); @@ -63,7 +117,15 @@ public class TunnelDataMessage extends I2NPMessageImpl { _tunnelId = id.getTunnelId(); } - public byte[] getData() { return _data; } + public byte[] getData() { + if (_hadCache && _dataBuf == null) { + RuntimeException e = new RuntimeException("TDM data buf use after free"); + _log.error("TDM boom", e); + throw e; + } + return _data; + } + public void setData(byte data[]) { if ( (data == null) || (data.length <= 0) ) throw new IllegalArgumentException("Empty tunnel payload?"); @@ -86,6 +148,7 @@ public class TunnelDataMessage extends I2NPMessageImpl { if (PIPELINED_CACHE) { _dataBuf = _cache.acquire(); _data = _dataBuf.getData(); + _hadCache = true; } else { _data = new byte[DATA_SIZE]; } @@ -101,12 +164,24 @@ public class TunnelDataMessage extends I2NPMessageImpl { if (_data.length <= 0) throw new I2NPMessageException("Not enough data to write out (data.length=" + _data.length + ")"); + if (_hadCache && _dataBuf == null) { + I2NPMessageException e = new I2NPMessageException("TDM data buf use after free"); + _log.error("TDM boom", e); + throw e; + } + DataHelper.toLong(out, curIndex, 4, _tunnelId); curIndex += 4; System.arraycopy(_data, 0, out, curIndex, DATA_SIZE); curIndex += _data.length; - if (PIPELINED_CACHE) - _cache.release(_dataBuf); + + // We can use from the cache, we just can't release to the cache, due to the bug + // noted above. In effect, this means that transmitted TDMs don't get their + // dataBufs released - but received TDMs do (via FragmentHandler) + //if (_hadCache) { + // _cache.release(_dataBuf); + // _dataBuf = null; + //} return curIndex; }