Bug Description
State entries registered while decoding partially-failed messages are retained. Mutations to session tables happen before the surrounding readValue / readMessage returns successfully.
Concrete sites:
readValue.TAG_STRING / .Literal (src/protocol.zig:805-806): the string is registered into string_table before the surrounding readValue returns. If the parent message later fails, the string remains in the table and gets ID N. The legitimate sender's next message that expected ID N → its own value now decodes to the attacker's poisoned string.
readKeyRef mode 0 (src/protocol.zig:1033-1043): every literal key is permanently registered, even if the enclosing Map's later entries fail.
readControl.RegisterShape (src/protocol.zig:1307-1325): even if registerWithId returns false, the earlier per-key registrations into key_table have already happened.
putFieldEnum (src/protocol.zig:2012-2028) on PromoteStringFieldToEnum: writes happen before the surrounding parse completes.
Steps to Reproduce
- Two peers A (legit) and B (attacker) share a stateful
SessionEncoder (server pool reusing codec instances, or attacker MITM).
- B sends a malformed message whose prefix contains a
Literal string registration that would acquire id N, but whose tail bytes deliberately fail.
- A's subsequent message references id N (expecting its own previously-registered value). Decoder returns the attacker's string instead.
Expected Behavior
Stage all session-table mutations into a scratch buffer during decode. Commit the staged mutations only after decodeMessage returns successfully and reader.isEof() holds.
Actual Behavior
Mutations land in shared tables before the surrounding decode validates.
Environment
- Project:
recurram-zig
- Files:
src/protocol.zig:805, :1037, :1264, :2012-2028
Additional Context
Severity: High — semantic forgery; breaks any trust derived from decoded string content.
Bug Description
State entries registered while decoding partially-failed messages are retained. Mutations to session tables happen before the surrounding
readValue/readMessagereturns successfully.Concrete sites:
readValue.TAG_STRING / .Literal(src/protocol.zig:805-806): the string is registered intostring_tablebefore the surroundingreadValuereturns. If the parent message later fails, the string remains in the table and gets ID N. The legitimate sender's next message that expected ID N → its own value now decodes to the attacker's poisoned string.readKeyRefmode 0 (src/protocol.zig:1033-1043): every literal key is permanently registered, even if the enclosing Map's later entries fail.readControl.RegisterShape(src/protocol.zig:1307-1325): even ifregisterWithIdreturnsfalse, the earlier per-key registrations intokey_tablehave already happened.putFieldEnum(src/protocol.zig:2012-2028) onPromoteStringFieldToEnum: writes happen before the surrounding parse completes.Steps to Reproduce
SessionEncoder(server pool reusing codec instances, or attacker MITM).Literalstring registration that would acquire id N, but whose tail bytes deliberately fail.Expected Behavior
Stage all session-table mutations into a scratch buffer during decode. Commit the staged mutations only after
decodeMessagereturns successfully andreader.isEof()holds.Actual Behavior
Mutations land in shared tables before the surrounding decode validates.
Environment
recurram-zigsrc/protocol.zig:805, :1037, :1264, :2012-2028Additional Context
Severity: High — semantic forgery; breaks any trust derived from decoded string content.