From 466e865e91d81cfd65d9df4b89d085aef3455e45 Mon Sep 17 00:00:00 2001 From: PobreGato <315121269+pobregat0@users.noreply.github.com> Date: Mon, 14 Sep 2026 21:43:53 -0400 Subject: [PATCH] LeaseSet2: check declared key length before creating an encryptor A key section in a standard LeaseSet2 carries both a key type and a key length. The length is checked against the buffer, but the encryptor reads a length fixed by the type instead: 256 bytes for ElGamal, 64 for ECIES P256, 32 for X25519. A section that declares ElGamal with a length of zero passes the check and then makes CreateEncryptor read 256 bytes past the end of the message. Key sections are parsed before the signature is verified, so no key material is needed to trigger it. GetCryptoPublicKeyLen returned 32 for ECIES P256, while the key is x and y, 32 bytes each; without fixing that too, the new check would still let a 32 byte P256 section through. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01Awv7FvZTpGFsKFNeNyJaTE --- libi2pd/CryptoKey.h | 2 +- libi2pd/LeaseSet.cpp | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/libi2pd/CryptoKey.h b/libi2pd/CryptoKey.h index 099bdd56..0cd105f0 100644 --- a/libi2pd/CryptoKey.h +++ b/libi2pd/CryptoKey.h @@ -181,7 +181,7 @@ namespace crypto switch (type) { case i2p::data::CRYPTO_KEY_TYPE_ELGAMAL: return 256; - case i2p::data::CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC: return 32; + case i2p::data::CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC: return 64; // x and y, 32 bytes each case i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD: return 32; // ML-KEM hybrid case i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM512_X25519_AEAD: diff --git a/libi2pd/LeaseSet.cpp b/libi2pd/LeaseSet.cpp index 7934abde..dd4eba83 100644 --- a/libi2pd/LeaseSet.cpp +++ b/libi2pd/LeaseSet.cpp @@ -423,7 +423,12 @@ namespace data if (keyType <= i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) // skip PQ keys if not supported #endif { - if ((keyType == m_PreferredEncryptionType || !newEncryptor || keyType > newEncryptionType) && + // encryptionKeyLen is the length the publisher declares, and it was + // checked against the buffer. The encryptor reads a length fixed by + // the key type instead, so a section declaring a short ElGamal key + // makes it read 256 bytes out of a few + if (encryptionKeyLen >= i2p::crypto::GetCryptoPublicKeyLen (keyType) && + (keyType == m_PreferredEncryptionType || !newEncryptor || keyType > newEncryptionType) && (!dest || dest->SupportsEncryptionType (keyType))) { auto encryptor = i2p::data::IdentityEx::CreateEncryptor (keyType, buf + offset);