Repository navigation
bug: Decoder panics on malformed indefinite-length map when decoding array into time.Time (v2.9.0) #809
Description
What version of fxamacker/cbor are you using?
github.com/fxamacker/cbor/v2 v2.9.0
Does this issue reproduce with the latest release?
No. On the latest release v2.9.3 the same 5-byte input no longer panics and returns:
cbor: cannot unmarshal array into Go struct field main.Record.d of type time.Time
The panic is reproducible on v2.9.0. It looks related to the fix in PR #803 / #802. This report includes a minimized, deterministic PoC, because the earlier crash files in #802 did not reproduce.
What OS and CPU architecture are you using (go env)?
go env Output
$ go env set AR=ar set CC=gcc set CGO_CFLAGS=-O2 -g set CGO_CPPFLAGS= set CGO_CXXFLAGS=-O2 -g set CGO_ENABLED=0 set CGO_FFLAGS=-O2 -g set CGO_LDFLAGS=-O2 -g set CXX=g++ set GCCGO=gccgo set GO111MODULE= set GOAMD64=v1 set GOARCH=amd64 set GOAUTH=netrc set GOBIN= set GOCACHE=C:\Users\dearborrn\AppData\Local\go-build set GOCACHEPROG= set GODEBUG= set GOENV=C:\Users\dearborrn\AppData\Roaming\go\env set GOEXE=.exe set GOEXPERIMENT= set GOFIPS140=off set GOFLAGS= set GOGCCFLAGS=-m64 -fno-caret-diagnostics -Qunused-arguments -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=/tmp/go-build -gno-record-gcc-switches set GOHOSTARCH=amd64 set GOHOSTOS=windows set GOINSECURE= set GOMOD=C:\Users\dearborrn\Desktop\oss\oss-fuzz-master\projects\kuber_api\go.mod set GOMODCACHE=C:\Users\dearborrn\go\pkg\mod set GONOPROXY= set GONOSUMDB= set GOOS=windows set GOPATH=C:\Users\dearborrn\go set GOPRIVATE= set GOPROXY=https://proxy.golang.org,direct set GOROOT=C:\Program Files\Go set GOSUMDB=sum.golang.org set GOTELEMETRY=local set GOTELEMETRYDIR=C:\Users\dearborrn\AppData\Roaming\go\telemetry set GOTMPDIR= set GOTOOLCHAIN=auto set GOTOOLDIR=C:\Program Files\Go\pkg\tool\windows_amd64 set GOVCS= set GOVERSION=go1.25.4 set GOWORK= set PKG_CONFIG=pkg-config
What did you do?
Found by coverage-guided fuzzing of DecMode.Unmarshal() (target FuzzDecModeUnmarshalComplex) against untrusted CBOR.
Minimized crashing input (5 bytes):
bf 61 64 80 ff
CBOR diagnostic:
| Offset | Byte | Meaning |
|---|---|---|
| 0 | bf |
start indefinite-length map |
| 1 | 61 |
text string, length 1 |
| 2 | 64 |
'd' (struct field name) |
| 3 | 80 |
empty array (invalid time.Time value) |
| 4 | ff |
break |
This is an indefinite-length map { "d": [] } with a break marker.
Standalone reproducer (go.mod pin github.com/fxamacker/cbor/v2 v2.9.0):
package main
import (
"fmt"
"time"
cbor "github.com/fxamacker/cbor/v2"
)
type Record struct {
Time time.Time `cbor:"d"`
}
func main() {
// indefinite-length map, text-string key "d", empty array, break
data := []byte{0xbf, 0x61, 'd', 0x80, 0xff}
var v Record
if err := cbor.Unmarshal(data, &v); err != nil {
fmt.Println("err:", err)
return
}
fmt.Println("decoded:", v)
}go run .Default decoder options are enough. No special DecOptions are required. A single time.Time field is enough; nested/recursive structs are not needed.
The same panic also occurs with a byte-string field name (bf 41 64 80 ff) if FieldNameByteStringAllowed is enabled.
A definite-length map a1 61 64 80 ({"d": []}) does not panic on v2.9.0: it correctly returns UnmarshalTypeError.
What did you expect to see?
Malformed / type-invalid CBOR should be rejected with an error, for example:
cbor: cannot unmarshal array into Go struct field main.Record.d of type time.Time
The decoder should not panic on untrusted input.
What did you see instead?
Unmarshal panics with an out-of-bounds access:
panic: runtime error: index out of range [5] with length 5
github.com/fxamacker/cbor/v2.(*decoder).foundBreak(...)
.../github.com/fxamacker/cbor/v2@v2.9.0/decode.go:3043
github.com/fxamacker/cbor/v2.(*decoder).parseMapToStruct(...)
.../github.com/fxamacker/cbor/v2@v2.9.0/decode.go:2685
github.com/fxamacker/cbor/v2.(*decoder).parseToValue(...)
.../github.com/fxamacker/cbor/v2@v2.9.0/decode.go:1692
github.com/fxamacker/cbor/v2.(*decoder).value(...)
.../github.com/fxamacker/cbor/v2@v2.9.0/decode.go:1399
github.com/fxamacker/cbor/v2.(*decMode).Unmarshal(...)
.../github.com/fxamacker/cbor/v2@v2.9.0/decode.go:1302
If an application unmarshals attacker-controlled CBOR into a struct that contains time.Time and does not recover from panics, this payload terminates the process (DoS).
Root cause
foundBreak() assumes the input is well-formed and does not check bounds:
// foundBreak assumes data is well-formed, and does not perform bounds checking.
func (d *decoder) foundBreak() bool {
if isBreakFlag(d.data[d.off]) {
d.off++
return true
}
return false
}The cursor is left pointing past the buffer because parseToTime() does not skip the current data item when decoding fails for arrays/maps (and for byte strings when ByteStringToTime is forbidden), unless timeTag == DecTagRequired.
Call chain:
Unmarshal
-> value
-> parseToValue // struct / map
-> parseMapToStruct
-> parseToValue // field Time time.Time
-> parseToTime // sees array 0x80
-> returns UnmarshalTypeError WITHOUT d.skip()
-> loop continues, foundBreak() reads d.data[d.off] with off == len(data)
In parseToTime() the default branch for unsupported types (array/map) is:
default:
return time.Time{}, false, &UnmarshalTypeError{CBORType: t.String(), GoType: typeTime.String()}Compare with DecTagRequired, which does call d.skip() before returning. After the error, parseMapToStruct() keeps iterating an indefinite-length map and eventually calls foundBreak() with off past the end of data.
RFC 8949 time values (tag 0 / tag 1) are not required to hit this path. The panic is triggered by an untagged array (or map/byte string under the conditions above) stored in a field whose Go type is time.Time.
Activity
fxamacker commentedon Aug 24, 2026
@hhellbentt Thanks for providing a small reproducer, I'm closing this because it is a duplicate of the issue fixed by PR #803 and it doesn't affect the latest release as mentioned in my comment to you about one week ago.
To summarize, this issue was already fixed and released in cbor v2.9.3.
Also as mentioned in the cbor v2.9.3 release notes, the fuzzing was expanded to detect this class of bugs and cbor v2.9.3 passed the updated fuzzing (I let fuzzing continue running for days after the release was tagged).
Thanks again.