TROVE-2026-003 - Why Reserved Open Source CVEs are Pointless in 2026

⚠️⚠️ The CVE discussed here has not been disclosed, so we cannot definitively say if this is the real issue. If this proves to be wrong, I will update to reflect this.

As of March 25, 2026, TROVE-2026-003 is a high severity vulnerability in Tor that is currently undisclosed with the initial issue 404'd.

The thinking is, while the CVE is reserved, it is still listed in the NVD with a severity rating. This would allow users to be aware of the vulnerability and take necessary precautions without revealing details that could be exploited by attackers.

However, in practice, this approach is becoming more out of touch with AI tools that are able to be pointed to a codebase to examine them and the code differences between versions. Although AI is not always perfect, in fact it's probabilistic, there is potiential to use these tools to be ahead of the publicly disclosed information.

Case Study: TROVE-2026-003

In the case of TROVE-2026-003, the Tor project announced the vulnerability as high severity and provided a version upgrade recommendation to fix it. However, with just this knowledge, we can have AI compare two versions of the Tor codebase, 0.4.9.5 and 0.4.9.6, to see what changed.

Current status of the CVE, as of March 25, 2026

Current disclosure of the CVE, as of March 25, 2026

The following is an AI generated analysis:

// BEGIN AI SLOP

Root Cause src/core/or/onion.c

Bounds checks validated handshake_len against MAX_CREATED_LEN (507), which is the max for a raw CREATED2 cell payload. But CREATED2 cells are carried inside RELAY cells, which have a smaller payload due to the 11-byte relay header. The actual safe maximum is RELAY_PAYLOAD_SIZE_MAX - 2 (496). The 11-byte discrepancy allowed a controlled stack buffer overflow via memcpy.

// The Math

Constant Value Derivation
MAX_CREATED_LEN (vulnerable) 507 CELL_PAYLOAD_SIZE - 2 = 509 - 2
RELAY_PAYLOAD_SIZE_MAX - 2 (correct) 496 (509 - 11) - 2 = 498 - 2
Overflow 11 bytes 507 - 496 = sizeof(relay header)

// check_created_cell() — inbound validation

Called when a relay receives a CREATED2 response from a peer. The bounds check used the wrong constant:

Before (0.4.9.5) After (0.4.9.6)
if (cell->handshake_len
    > MAX_CREATED_LEN) {
  /* 507 bytes -- TOO LARGE */
  return -1;
}
if (cell->handshake_len
    > RELAY_PAYLOAD_SIZE_MAX - 2) {
  /* 496 bytes -- correct max */
  return -1;
}

// extended_cell_format() — outbound construction

Serializes a CREATED2 response into an EXTENDED2 relay cell. The same wrong constant appeared here independently, and the memcpy right below it is where the overflow actually happens:

Before (0.4.9.5) After (0.4.9.6)
if (cell->created_cell.handshake_len
    > MAX_CREATED_LEN) {
  return -1;
}

/* If handshake_len is 497-507,
   this overflows the destination
   buffer by 1-11 bytes. */
memcpy(payload_out + 2,
  cell->created_cell.reply,
  cell->created_cell.handshake_len);
if (cell->created_cell.handshake_len
    > RELAY_PAYLOAD_SIZE_MAX - 2) {
  return -1;
}

/* handshake_len is now guaranteed
   <= 496, so this memcpy can
   never exceed the 498-byte
   relay payload buffer. */
memcpy(payload_out + 2,
  cell->created_cell.reply,
  cell->created_cell.handshake_len);

// Takeaways

  • The entire fix is a two-line change — one constant swap in each function — but the impact is a remotely exploitable 11-byte stack overflow reachable by any node in a Tor circuit.
  • The root cause is a layer confusion: MAX_CREATED_LEN describes the max for a standalone CREATED2 cell, but these cells are always encapsulated in RELAY cells with a smaller payload. The constant was correct at the cell layer but wrong at the relay layer.
  • The overflow size (11 bytes) is exactly RELAY_HEADER_SIZE_V0 — the original author forgot to account for the relay header.
  • On x86-64, 11 bytes is enough to overwrite a saved return address (8 bytes), which could enable RCE via ROP chains if stack canaries are bypassed.
  • The same wrong constant was copy-pasted into both locations — both functions independently validate bounds (defense in depth), but the same mistake was duplicated.

// END AI SLOP

········································
« All Posts