From a57d375bf1a150d1b31be1a5d4ba9c4462595be7 Mon Sep 17 00:00:00 2001 From: jpk68 Date: Wed, 19 Aug 2026 21:29:25 -0400 Subject: [PATCH] libi2pd: add missing checks and limits --- libi2pd/Datagram.cpp | 1 + libi2pd/Destination.cpp | 11 +++++++++++ libi2pd/NTCP2.cpp | 2 +- libi2pd/NetDbRequests.cpp | 14 +++++++++++++- libi2pd/Reseed.cpp | 12 ++++++++++++ libi2pd/Streaming.cpp | 11 +++++++++-- libi2pd_client/I2PTunnel.cpp | 17 ++++++++++------- libi2pd_client/SAM.cpp | 14 +++++++++++--- 8 files changed, 68 insertions(+), 14 deletions(-) diff --git a/libi2pd/Datagram.cpp b/libi2pd/Datagram.cpp index 174fd993..12420a8b 100644 --- a/libi2pd/Datagram.cpp +++ b/libi2pd/Datagram.cpp @@ -654,6 +654,7 @@ namespace datagram } } + if (!m_RoutingSession) return nullptr; auto path = m_RoutingSession->GetSharedRoutingPath(); if (path && m_RoutingSession->IsRatchets () && m_RoutingSession->CleanupUnconfirmedTags ()) { diff --git a/libi2pd/Destination.cpp b/libi2pd/Destination.cpp index 2dee646c..dd7eddeb 100644 --- a/libi2pd/Destination.cpp +++ b/libi2pd/Destination.cpp @@ -556,8 +556,19 @@ namespace client void LeaseSetDestination::HandleDatabaseSearchReplyMessage (const uint8_t * buf, size_t len) { + if (len < 33) + { + LogPrint (eLogWarning, "Destination: Database search reply is too short, ", len); + return; + } i2p::data::IdentHash key (buf); int num = buf[32]; // num + size_t maxNum = (len - 33)/32; + if ((size_t)num > maxNum) + { + LogPrint (eLogWarning, "Destination: Declared number of peer hashes ", num, " exceeds message size, reduced to ", maxNum); + num = maxNum; + } LogPrint (eLogDebug, "Destination: DatabaseSearchReply for ", key.ToBase64 (), " num=", num); auto it = m_LeaseSetRequests.find (key); if (it != m_LeaseSetRequests.end ()) diff --git a/libi2pd/NTCP2.cpp b/libi2pd/NTCP2.cpp index 43f91133..ef96e4aa 100644 --- a/libi2pd/NTCP2.cpp +++ b/libi2pd/NTCP2.cpp @@ -992,7 +992,7 @@ namespace transport return; } auto size = bufbe16toh (buf->data () + 1); - if (size + 3 > (int)buf->size () || size > i2p::data::MAX_RI_BUFFER_SIZE + 1) + if (size < 1 || size + 3 > (int)buf->size () || size > i2p::data::MAX_RI_BUFFER_SIZE + 1) { LogPrint (eLogError, "NTCP2: Unexpected RouterInfo size ", size, " in SessionConfirmed"); boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ())); diff --git a/libi2pd/NetDbRequests.cpp b/libi2pd/NetDbRequests.cpp index 88ee6f7e..f7036727 100644 --- a/libi2pd/NetDbRequests.cpp +++ b/libi2pd/NetDbRequests.cpp @@ -360,6 +360,12 @@ namespace data void NetDbRequests::HandleDatabaseSearchReplyMsg (std::shared_ptr msg) { const uint8_t * buf = msg->GetPayload (); + size_t payloadLen = msg->GetPayloadLength (); + if (payloadLen < 33) + { + LogPrint (eLogWarning, "NetDbReq: Database search reply is too short, ", payloadLen); + return; + } std::string key; size_t num = buf[32]; // num if (CheckLogLevel (eLogInfo)) @@ -393,7 +399,13 @@ namespace data { LogPrint (eLogWarning, "NetDbReq: Too many peer hashes ", num, " in database search reply, Reduced to ", NETDB_MAX_NUM_SEARCH_REPLY_PEER_HASHES); num = NETDB_MAX_NUM_SEARCH_REPLY_PEER_HASHES; - } + } + size_t maxNum = (payloadLen - 33)/32; + if (num > maxNum) + { + LogPrint (eLogWarning, "NetDbReq: Declared number of peer hashes ", num, " exceeds message size, reduced to ", maxNum); + num = maxNum; + } if (isExploratory && !m_DiscoveredRouterHashes.empty ()) { // request outstanding routers diff --git a/libi2pd/Reseed.cpp b/libi2pd/Reseed.cpp index 54f5e6d2..004a0afc 100644 --- a/libi2pd/Reseed.cpp +++ b/libi2pd/Reseed.cpp @@ -36,6 +36,8 @@ namespace i2p { namespace data { + const uint64_t MAX_RESEED_CONTENT_LENGTH = 128*1024*1024; // 128 MB, sanity cap for SU3 content length + const uint32_t MAX_RESEED_FILE_SIZE = 64*1024*1024; // 64 MB, sanity cap for a single file inside the SU3 zip Reseeder::Reseeder() { @@ -231,6 +233,11 @@ namespace data uint64_t contentLength; s.read ((char *)&contentLength, 8); // content length contentLength = be64toh (contentLength); + if (contentLength > MAX_RESEED_CONTENT_LENGTH) + { + LogPrint (eLogError, "Reseed: SU3 content length too large: ", contentLength); + return 0; + } s.seekg (1, std::ios::cur); // unused uint8_t fileType; s.read ((char *)&fileType, 1); // file type @@ -387,6 +394,11 @@ namespace data LogPrint (eLogWarning, "Reseed: Unexpected size 0. Skipped"); continue; } + if (compressedSize > MAX_RESEED_FILE_SIZE || uncompressedSize > MAX_RESEED_FILE_SIZE) + { + LogPrint (eLogError, "Reseed: SU3 file size too large, compressed=", compressedSize, " uncompressed=", uncompressedSize); + return numFiles; + } uint8_t * compressed = new uint8_t[compressedSize]; s.read ((char *)compressed, compressedSize); diff --git a/libi2pd/Streaming.cpp b/libi2pd/Streaming.cpp index 590ade48..b9782005 100644 --- a/libi2pd/Streaming.cpp +++ b/libi2pd/Streaming.cpp @@ -450,8 +450,9 @@ namespace stream bool Stream::ProcessOptions (uint16_t flags, Packet * packet) { const uint8_t * optionData = packet->GetOptionData (); + size_t optionOffset = optionData - packet->buf; size_t optionSize = packet->GetOptionSize (); - if (optionSize > packet->len) + if (optionOffset > packet->len || optionSize > packet->len - optionOffset) { LogPrint (eLogInfo, "Streaming: Invalid option size ", optionSize, " Discarded"); return false; @@ -626,8 +627,14 @@ namespace stream if (flags & PACKET_FLAG_SIGNATURE_INCLUDED) { + if (!m_TransientVerifier && !m_RemoteIdentity) + { + LogPrint (eLogError, "Streaming: Signature included without remote identity"); + return false; + } auto signatureLen = m_TransientVerifier ? m_TransientVerifier->GetSignatureLen () : m_RemoteIdentity->GetSignatureLen (); - if (signatureLen > packet->GetLength ()) + size_t optionOffset2 = optionData - packet->buf; + if (optionOffset2 > packet->len || signatureLen > packet->len - optionOffset2) { LogPrint (eLogError, "Streaming: Signature too big, ", signatureLen, " bytes"); return false; diff --git a/libi2pd_client/I2PTunnel.cpp b/libi2pd_client/I2PTunnel.cpp index b8fd4604..d9f9966b 100644 --- a/libi2pd_client/I2PTunnel.cpp +++ b/libi2pd_client/I2PTunnel.cpp @@ -628,13 +628,16 @@ namespace client if (!pos) // start of line { pos = line.find (" "); - pos++; - pos = line.find (" ", pos); - pos++; - auto nextpos = line.find (" ", pos); - m_OutPacket << line.substr (0, pos); - m_OutPacket << context.GetAddressBook ().ToAddress (m_From->GetIdentHash ()); - m_OutPacket << line.substr (nextpos) << '\n'; + if (pos != std::string::npos) pos = line.find (" ", pos + 1); + auto nextpos = (pos != std::string::npos) ? line.find (" ", pos + 1) : std::string::npos; + if (pos == std::string::npos || nextpos == std::string::npos) + m_OutPacket << line << '\n'; // malformed USER line, pass through unmodified + else + { + m_OutPacket << line.substr (0, pos + 1); + m_OutPacket << context.GetAddressBook ().ToAddress (m_From->GetIdentHash ()); + m_OutPacket << line.substr (nextpos) << '\n'; + } } else m_OutPacket << line << '\n'; diff --git a/libi2pd_client/SAM.cpp b/libi2pd_client/SAM.cpp index 48c31d86..647d7c00 100644 --- a/libi2pd_client/SAM.cpp +++ b/libi2pd_client/SAM.cpp @@ -1310,9 +1310,17 @@ namespace client { // get remote peer address auto dest = stream->GetRemoteIdentity()->ToBase64 (); - memcpy (newSocket->m_StreamBuffer, dest.c_str (), dest.length ()); - newSocket->m_StreamBuffer[dest.length ()] = '\n'; - newSocket->HandleI2PReceive (boost::system::error_code (),dest.length () + 1); // we send identity like it has been received from stream + if (dest.length () + 1 > SAM_STREAM_BUFFER_SIZE) + { + LogPrint (eLogError, "SAM: Remote identity is too long ", dest.length ()); + newSocket->TerminateClose (); + } + else + { + memcpy (newSocket->m_StreamBuffer, dest.c_str (), dest.length ()); + newSocket->m_StreamBuffer[dest.length ()] = '\n'; + newSocket->HandleI2PReceive (boost::system::error_code (),dest.length () + 1); // we send identity like it has been received from stream + } } else newSocket->I2PReceive ();