Skip to content

MeshRouter::send tracks delivery before store_forward.store, orphaning a Queued record on QueueFull #245

Description

@forkwright

Finding

In MeshRouter::send, self.delivery.track(id, dest) runs unconditionally before the unreachable path calls self.store_forward.store(...). If the destination is unreachable and store returns Error::QueueFull, the ? propagates the error and returns early — but the delivery tracker already holds a Queued record for id, even though no packet was queued anywhere. The caller receives an error and drops the PacketId, so the orphaned record stays Queued forever.

Evidence

crates/kerykeion/src/router.rs:136 — track runs first, unconditionally:

self.delivery.track(id, dest);

crates/kerykeion/src/router.rs:147 — the store that can fail with ? after the record already exists:

self.store_forward.store(
    NodeNum(dest),
    StoredMessage {

Why this matters

A full store-forward queue (e.g. a long-offline node, or a destination an adversary keeps saturated) makes every subsequent send() to that destination insert a phantom Queued entry into the delivery tracker. Callers that query delivery_status expecting None for an un-sent packet instead see Queued, corrupting delivery accounting. Because nothing resolves these records, the delivery table leaks memory proportional to the QueueFull rejection rate — an adversary-controllable rate under the threat model.

Desired correction

Move self.delivery.track(id, dest) to after the dispatch (outbound.enqueue / store_forward.store) succeeds, or add a self.delivery.remove(id) in the Err branch before returning. Track only once dispatch is confirmed.

Done when: a QueueFull return from send() leaves the delivery tracker unchanged — no entry exists for the rejected packet ID.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions