Skip to content

tokens: typed.go ASN.1 decoders accept trailing bytes and have no fuzz target - #2212

Draft
Effi-S wants to merge 1 commit into
mainfrom
fix-2189
Draft

tokens: typed.go ASN.1 decoders accept trailing bytes and have no fuzz target#2212
Effi-S wants to merge 1 commit into
mainfrom
fix-2189

Conversation

@Effi-S

@Effi-S Effi-S commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Fixes #2189

Summary

typed.go's ASN.1 decoders discard asn1.Unmarshal's rest return value, so any number of trailing bytes appended after a validly-encoded token or metadata payload are silently accepted rather than rejected — the classic ASN.1 malleability pattern: two distinct byte strings decode to the same value. Neither decoder has a FuzzXxx target, which AGENTS.md mandates for any exported function that parses untrusted/attacker-controlled bytes; every sibling ASN.1 decoder in the repo (token/core/common/encoding/asn1, token/core/fabtoken/..., token/core/zkatdlog/...) already has one.

Where

token/services/tokens/typed.goUnmarshalTypedToken (~line 31) and UnmarshalTypedMetadata (~line 65) each call asn1.Unmarshal(raw, &typed) and check only the returned error, ignoring the rest []byte return that reports unconsumed trailing bytes.

Impact

A byte string X and X + arbitrary trailer are treated as identical tokens/metadata by every caller of these two functions. Depending on where the raw bytes originate (any wire format that carries a typed token/metadata payload), this is a substrate for hash/signature-mismatch or replay-style confusion between two representations that "look the same" to this decoder but differ byte-for-byte.

Reproduction

valid, _ := tokens.WrapWithType(7, driver.Token("payload"))
withTrailer := append(append([]byte{}, valid...), 0x00, 0x01, 0x02)

want, _ := tokens.UnmarshalTypedToken(valid)
got, err := tokens.UnmarshalTypedToken(withTrailer)

require.NoError(t, err, "trailing bytes after a valid encoding should be rejected, not silently accepted")
require.Equal(t, want, got, "a valid encoding and the same encoding with an arbitrary trailer decode to the same TypedToken")
=== RUN   TestUnmarshalTypedToken_AcceptsTrailingBytes
--- PASS: TestUnmarshalTypedToken_AcceptsTrailingBytes (0.00s)
=== RUN   TestUnmarshalTypedMetadata_AcceptsTrailingBytes
--- PASS: TestUnmarshalTypedMetadata_AcceptsTrailingBytes (0.00s)
PASS

Both tests pass — i.e., the trailing-byte acceptance is confirmed present on current main. (A FuzzUnmarshalTypedTokenNoPanic/FuzzUnmarshalTypedMetadataNoPanic pair, seeded with valid/empty/truncated/valid+trailer inputs, also ran clean for 20s with no panics — the decoders don't crash on malformed input, they just accept more than they should.)

Severity

Medium — a real ASN.1 malleability bug with no panic/crash component, but no fuzz coverage either, on functions that parse externally-sourced bytes.

@Effi-S Effi-S added this to the Q3/26 milestone Aug 13, 2026
@Effi-S Effi-S self-assigned this Aug 13, 2026
Signed-off-by: Effi-S <effi.szt@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

tokens: typed.go ASN.1 decoders accept trailing bytes and have no fuzz target

1 participant