The GarlicRust vulnerability, a.k.a CVE 2017-17066, is a major info-leak vulnerability in C++ implementations of the I2P router. The vulnerability was found in i2pd and kovri, as part of the Monero bug bounty program.

Invisible Internet Protocol

I2P stands for “Invisible Internet Protocol” and was designed to provide strong privacy protections for communication over the Internet. In short, the protocol is used to construct an anonymous peer-to-peer network, sometimes called “darknet”. The protocol uses a routing scheme called Garlic Routing, a variant of Onion Routing that combines multiple messages together. The inner messages are called “cloves” or “garlic cloves”.

While the I2P protocol is known mainly due to it’s Java implementation, there are 2 relatively popular C++ implementations of the protocol: i2pd and kovri. Both projects share a common code base since kovri was forked from i2pd following developer disagreements (more material can be found here). Kovri is in pre-alpha and is developed under the Monero project to be used by the Monero cryptocurrency, and i2pd already has release versions (latest version is 2.17).

GarlicRust

The GarlicRust vulnerability is a Heartbleed-style vulnerability in the code that handles incoming garlic cloves. (Side note: I will only show code snippets from kovri since the vulnerability is logical and works on both i2pd and kovri.) After a garlic message is decrypted, the router parses the inner cloves according to their “delivery type”, here is the relevant code:

void GarlicDestination::HandleGarlicPayload( std::uint8_t* buf, std::size_t len, std::shared_ptr<kovri::core::InboundTunnel> from) { const std::uint8_t* buf1 = buf; std::size_t num_cloves = buf[0]; LOG(debug) << "GarlicDestination: " << num_cloves << " cloves"; buf++; for (std::size_t i(0); i < num_cloves; i++) { // delivery instructions std::uint8_t flag = buf[0]; buf++; // flag if (flag & 0x80) { // encrypted? // TODO(unassigned): implement LOG(debug) << "GarlicDestination: clove encrypted"; buf += 32; } GarlicDeliveryType delivery_type = (GarlicDeliveryType)((flag >> 5) & 0x03); switch (delivery_type) { case eGarlicDeliveryTypeLocal: LOG(debug) << "GarlicDestination: Garlic type local"; HandleI2NPMessage(buf, len, from); break; case eGarlicDeliveryTypeDestination: LOG(debug) << "GarlicDestination: Garlic type destination"; buf += 32; // destination. check it later or for multiple destinations HandleI2NPMessage(buf, len, from); break; case eGarlicDeliveryTypeTunnel: { LOG(debug) << "GarlicDestination: Garlic type tunnel"; // gateway_hash and gateway_tunnel sequence is reverted std::uint8_t* gateway_hash = buf; buf += 32; std::uint32_t gateway_tunnel = bufbe32toh(buf); buf += 4; std::shared_ptr<kovri::core::OutboundTunnel> tunnel; if (from && from->GetTunnelPool()) tunnel = from->GetTunnelPool()->GetNextOutboundTunnel(); // EI-DBG : We read the inner length field (16 bit) using // EI-DBG : kovri::core::GetI2NPMessageLength() and we use it // EI-DBG : to construct the outgoing message WITHOUT any verification. if (tunnel) { // we have send it through an outbound tunnel auto msg = CreateI2NPMessage(buf, kovri::core::GetI2NPMessageLength(buf), from); tunnel->SendTunnelDataMsg(gateway_hash, gateway_tunnel, msg); } else { LOG(debug) << "GarlicDestination: no outbound tunnels available for garlic clove"; } break; } case eGarlicDeliveryTypeRouter: LOG(warning) << "GarlicDestination: Garlic type router not supported"; buf += 32; break; default: LOG(error) << "GarlicDestination: unknown garlic delivery type " << static_cast<int>(delivery_type); } buf += kovri::core::GetI2NPMessageLength(buf); // I2NP buf += 4; // CloveID buf += 8; // Date buf += 3; // Certificate // EI-DBG : This check is done AFTER the outgoing message was sent if (buf - buf1 > static_cast<int>(len)) { LOG(error) << "GarlicDestination: clove is too long"; break; } } }

Important notes:

The length field is only checked after the outgoing message is sent, and the only response is logging and not something that will shutdown the router The garlic clove can potentially arrive from ANY i2p router on the network The message is allocated on the heap, causing the outgoing message to include massive amounts of sensitive memory (keys, old messages, etc.)

Implications

The attack was demonstrated in a test network, using a “victim” kovri client and a modified “attacker” kovri client. In the logs from this test run (attached here) one can clearly see the debug trace of the leaked information before it is sent by the victim, and the leaked information as it was received on the attacker’s side. The featured image is a screenshot that was taken from a different exploitation round.

The demonstration shows that the GarlicRust vulnerability can be exploited to leak sensitive memory data from any victim C++ i2p router. The exploit is logical and triggers no memory errors, and so it can be used repeatedly and without worries. An attacker can use this vulnerability in an attempt to read session keys, private keys, old messages or any other valuable assets that is stored on the target’s heap.

This vulnerability poses a major threat to the anonymity of the users in the Invisible Internet Protocol network.

Patching Status

I2pd acknowledged and patched the vulnerability several hours after I contacted them ( an impressive response time) and the fix is included in the new released version – version 2.17.

The vulnerability was found as I tried my luck in the Monero hackerOne bug bounty program, a program that includes kovri, and so the issue was submitted using the HackerOne platform. Kovri is pre-alpha and has no official releases, the patch was committed to it’s master branch on Github.

Conclusion

GarlicRust is a generic & massive logical info-leak vulnerability that threatens the anonymity of users in the I2P network. An attacker that exploits this vulnerability on a C++ router is oblivious to the router’s implementation \ version, making the attack highly attractive from the attacker’s point of view. It is highly recommended to update the C++ kovri / i2pd clients to a patched version.

Edit: Since some readers already asked me, I attach this update. I will gladly accept any donation, and I am very thankful that some readers found my work worthy of a reward. Here is my Monero wallet address (originally opened for the bug bounty – see my next post – “tales of a bug bounty” ):

49q5B492UgjJMPQ3j2Zs246LzMmVvdNxoj3VGnBCQf2pAKt2rmQ6fag8jwFG7QUfmT1iDVmnmNVAUKNvNeYhYQxhCXeYRRE

Edit: I closed my Monero wallet, do not sent anything to it.

Share this: Twitter

Facebook

Like this: Like Loading... Related