diff --git a/libi2pd/Datagram.cpp b/libi2pd/Datagram.cpp index 12420a8b..50da9818 100644 --- a/libi2pd/Datagram.cpp +++ b/libi2pd/Datagram.cpp @@ -243,9 +243,9 @@ namespace datagram return; } } - if (offset > len) + if (offset > len || signatureLen > len - offset) { - LogPrint (eLogWarning, "Datagram: datagram2 is too short ", len, " expected ", offset); + LogPrint (eLogWarning, "Datagram: datagram2 is too short ", len, " expected ", offset + signatureLen); return; } if (!verified) @@ -261,6 +261,11 @@ namespace datagram } signatureLen = transientVerifier->GetSignatureLen (); } + if (offset > len || signatureLen > len - offset) + { + LogPrint (eLogWarning, "Datagram: datagram2 is too short ", len, " expected ", offset + signatureLen); + return; + } std::vector signedData (len + 32 - identityLen - signatureLen); memcpy (signedData.data (), identity.GetIdentHash (), 32); memcpy (signedData.data () + 32, buf + identityLen, signedData.size () - 32); diff --git a/libi2pd/NTCP2.cpp b/libi2pd/NTCP2.cpp index 5d403aec..efbc29f2 100644 --- a/libi2pd/NTCP2.cpp +++ b/libi2pd/NTCP2.cpp @@ -1338,6 +1338,11 @@ namespace transport LogPrint (eLogError, "NTCP2: I2NP block is too long ", size); break; } + if (size < 9) + { + LogPrint (eLogError, "NTCP2: I2NP block is too short ", size); + break; + } auto nextMsg = (frame[offset] == eI2NPTunnelData) ? NewI2NPTunnelMessage (true) : NewI2NPMessage (size); nextMsg->len = nextMsg->offset + size + 7; // 7 more bytes for full I2NP header if (nextMsg->len <= nextMsg->maxLen) diff --git a/libi2pd/NetDb.cpp b/libi2pd/NetDb.cpp index 88d3ad67..01fb4aeb 100644 --- a/libi2pd/NetDb.cpp +++ b/libi2pd/NetDb.cpp @@ -804,7 +804,7 @@ namespace data void NetDb::HandleDatabaseStoreMsg (std::shared_ptr m) { const uint8_t * buf = m->GetPayload (); - size_t len = m->GetSize (); + size_t len = m->GetPayloadLength (); if (len < DATABASE_STORE_HEADER_SIZE) { LogPrint (eLogError, "NetDb: Database store msg is too short ", len, ". Dropped"); @@ -964,6 +964,12 @@ namespace data LogPrint (eLogDebug, "NetDb: DatabaseLookup for ", key, " received flags=", (int)flag); uint8_t lookupType = flag & DATABASE_LOOKUP_TYPE_FLAGS_MASK; + size_t minLen = 65 + ((flag & DATABASE_LOOKUP_DELIVERY_FLAG) ? 4 : 0) + 2; + if (msg->GetPayloadLength () < minLen) + { + LogPrint (eLogWarning, "NetDb: DatabaseLookup message is too short for its flags"); + return; + } const uint8_t * excluded = buf + 65; uint32_t replyTunnelID = 0; if (flag & DATABASE_LOOKUP_DELIVERY_FLAG) //reply to tunnel @@ -1061,27 +1067,34 @@ namespace data // encryption might be used though tunnel only if (flag & (DATABASE_LOOKUP_ENCRYPTION_FLAG | DATABASE_LOOKUP_ECIES_FLAG)) // encrypted reply requested { - const uint8_t * sessionKey = excluded; - const uint8_t numTags = excluded[32]; - if (numTags) + // session key (32) + numTags (1), then the tag itself: 8 bytes for ECIES, 32 for ElGamal + size_t tagLen = (flag & DATABASE_LOOKUP_ECIES_FLAG) ? 8 : 32; + if ((size_t)(excluded - buf) + 33 + tagLen > msg->GetPayloadLength ()) + LogPrint (eLogWarning, "NetDb: DatabaseLookup reply key block is truncated"); + else { - if (flag & DATABASE_LOOKUP_ECIES_FLAG) + const uint8_t * sessionKey = excluded; + const uint8_t numTags = excluded[32]; + if (numTags) { - uint64_t tag; - memcpy (&tag, excluded + 33, 8); - replyMsg = i2p::garlic::WrapECIESX25519Message (replyMsg, sessionKey, tag); + if (flag & DATABASE_LOOKUP_ECIES_FLAG) + { + uint64_t tag; + memcpy (&tag, excluded + 33, 8); + replyMsg = i2p::garlic::WrapECIESX25519Message (replyMsg, sessionKey, tag); + } + else + { + const i2p::garlic::SessionTag sessionTag(excluded + 33); // take first tag + i2p::garlic::ElGamalAESSession garlic (sessionKey, sessionTag); + replyMsg = garlic.WrapSingleMessage (replyMsg); + } + if (!replyMsg) + LogPrint (eLogError, "NetDb: Failed to wrap message"); } else - { - const i2p::garlic::SessionTag sessionTag(excluded + 33); // take first tag - i2p::garlic::ElGamalAESSession garlic (sessionKey, sessionTag); - replyMsg = garlic.WrapSingleMessage (replyMsg); - } - if (!replyMsg) - LogPrint (eLogError, "NetDb: Failed to wrap message"); + LogPrint(eLogWarning, "NetDb: Encrypted reply requested but no tags provided"); } - else - LogPrint(eLogWarning, "NetDb: Encrypted reply requested but no tags provided"); } bool direct = true; if (!i2p::transport::transports.IsConnected (replyIdent)) diff --git a/libi2pd/RouterInfo.cpp b/libi2pd/RouterInfo.cpp index 206daf82..43fbc6ca 100644 --- a/libi2pd/RouterInfo.cpp +++ b/libi2pd/RouterInfo.cpp @@ -324,7 +324,7 @@ namespace data } else if (address->transportStyle == eTransportSSU2) { - if (value[0] >= '3' && value[0] <= '4') // only 3,4 allowed, TODO: support multiple values + if (value.size () == 1 && value[0] >= '3' && value[0] <= '4') // only 3,4 allowed, TODO: support multiple values address->v = value[0] - '0'; } } diff --git a/libi2pd/SSU2Session.cpp b/libi2pd/SSU2Session.cpp index d2e888b5..e613594c 100644 --- a/libi2pd/SSU2Session.cpp +++ b/libi2pd/SSU2Session.cpp @@ -1707,9 +1707,9 @@ namespace transport uint32_t SSU2Session::SendData (const uint8_t * buf, size_t len, uint8_t flags) { - if (len < 8) + if (len < 8 || len + 16 > SSU2_MAX_PACKET_SIZE) { - LogPrint (eLogWarning, "SSU2: Data message payload is too short ", (int)len); + LogPrint (eLogWarning, "SSU2: Data message payload length ", (int)len, " is out of range"); return 0; } Header header; @@ -1721,7 +1721,11 @@ namespace transport uint8_t nonce[12]; CreateNonce (m_SendPacketNum, nonce); uint8_t payload[SSU2_MAX_PACKET_SIZE]; - m_Server.AEADChaCha20Poly1305Encrypt (buf, len, header.buf, 16, m_KeyDataSend, nonce, payload, SSU2_MAX_PACKET_SIZE); + if (!m_Server.AEADChaCha20Poly1305Encrypt (buf, len, header.buf, 16, m_KeyDataSend, nonce, payload, SSU2_MAX_PACKET_SIZE)) + { + LogPrint (eLogError, "SSU2: Data message AEAD encryption failed"); + return 0; + } header.ll[0] ^= CreateHeaderMask (m_Address->i, payload + (len - 8)); header.ll[1] ^= CreateHeaderMask (m_KeyDataSend + 32, payload + (len + 4)); m_Server.Send (header.buf, 16, payload, len + 16, m_RemoteEndpoint); @@ -1804,6 +1808,12 @@ namespace transport case eSSU2BlkI2NPMessage: { LogPrint (eLogDebug, "SSU2: I2NP message"); + if (size < 9) + { + LogPrint (eLogWarning, "SSU2: I2NP message block size ", size, " is too short"); + m_IsInvalidMessage = true; + break; + } auto nextMsg = (buf[offset] == eI2NPTunnelData) ? NewI2NPTunnelMessage (true) : NewI2NPShortMessage (); if (nextMsg->offset + size + 7 > nextMsg->maxLen) // 7 more bytes for full I2NP header { @@ -2157,6 +2167,12 @@ namespace transport void SSU2Session::HandleFirstFragment (const uint8_t * buf, size_t len) { + if (len < 9) + { + LogPrint (eLogWarning, "SSU2: First fragment size ", len, " is too short"); + m_IsInvalidMessage = true; + return; + } auto msg = (buf[0] == eI2NPTunnelData) ? NewI2NPTunnelMessage (true) : NewI2NPShortMessage (); if (msg->offset + len + 7 > msg->maxLen) { @@ -2434,13 +2450,19 @@ namespace transport if (relaySession && relaySession->IsEstablished ()) { // we are Bob, message from Charlie + size_t maxPayloadSize = relaySession->GetMaxPayloadSize (); + if (len + 3 > maxPayloadSize) + { + LogPrint (eLogWarning, "SSU2: RelayResponse block size ", len, " is too long"); + return; + } auto packet = m_Server.GetSentPacketsPool ().AcquireShared (); uint8_t * payload = packet->payload; payload[0] = eSSU2BlkRelayResponse; htobe16buf (payload + 1, len); memcpy (payload + 3, buf, len); // forward to Alice as is packet->payloadSize = len + 3; - packet->payloadSize += CreatePaddingBlock (payload + packet->payloadSize, m_MaxPayloadSize - packet->payloadSize); + packet->payloadSize += CreatePaddingBlock (payload + packet->payloadSize, maxPayloadSize - packet->payloadSize); uint32_t packetNum = relaySession->SendData (packet->payload, packet->payloadSize); if (m_RemoteVersion >= SSU2_MIN_RELAY_RESPONSE_RESEND_VERSION) { diff --git a/libi2pd/Streaming.cpp b/libi2pd/Streaming.cpp index f4b61a2a..dcb0bf08 100644 --- a/libi2pd/Streaming.cpp +++ b/libi2pd/Streaming.cpp @@ -1008,7 +1008,7 @@ namespace stream else if (numMsgs > m_NumPacketsToSend) numMsgs = m_NumPacketsToSend; - if (!m_RemoteLeaseSet) m_RemoteLeaseSet = m_LocalDestination.GetOwner ()->FindLeaseSet (m_RemoteIdentity->GetIdentHash ()); + if (!m_RemoteLeaseSet && m_RemoteIdentity) m_RemoteLeaseSet = m_LocalDestination.GetOwner ()->FindLeaseSet (m_RemoteIdentity->GetIdentHash ()); if (m_RemoteLeaseSet) { if (!m_RoutingSession) @@ -1082,7 +1082,7 @@ namespace stream { // initial packet m_Status = eStreamStatusOpen; - if (!m_RemoteLeaseSet) m_RemoteLeaseSet = m_LocalDestination.GetOwner ()->FindLeaseSet (m_RemoteIdentity->GetIdentHash ()); + if (!m_RemoteLeaseSet && m_RemoteIdentity) m_RemoteLeaseSet = m_LocalDestination.GetOwner ()->FindLeaseSet (m_RemoteIdentity->GetIdentHash ()); if (m_RemoteLeaseSet) { m_RoutingSession = m_LocalDestination.GetOwner ()->GetRoutingSession (m_RemoteLeaseSet, true, !m_IsIncoming); @@ -1972,6 +1972,13 @@ namespace stream bool isLeaseChanged = true; if (!m_RemoteLeaseSet || m_RemoteLeaseSet->IsExpired ()) { + if (!m_RemoteIdentity) + { + // no remote identity established yet, nothing to look up + m_Status = eStreamStatusClosed; + AsyncClose (); + return; + } auto remoteLeaseSet = m_LocalDestination.GetOwner ()->FindLeaseSet (m_RemoteIdentity->GetIdentHash ()); if (!remoteLeaseSet) {