Cybersecurity News

Unpatched DoS in shamaton/msgpack v2 and v3: Single-Byte Panic via Truncated Fixext Input

Unpatched DoS in shamaton/msgpack v2 and v3: Single-Byte Panic via Truncated Fixext Input

Severity: High (CVSS ~7.5)  |  Affected: github.com/shamaton/msgpack v2.4.0, v3.1.0  |  Status: Unpatched  |  Discovered: February 2026

GitHub Issue #59 - shamaton/msgpack DoS vulnerability

Introduction

github.com/shamaton/msgpack, one of the most widely used MessagePack implementations in the Go ecosystem. The vulnerability affects both the v2 and v3 major versions at their latest releases (v2.4.0 and v3.1.0 respectively) and allows any unauthenticated attacker to crash a Go service with a single-byte payload.

This vulnerability is particularly significant because it represents a regression from CVE-2022-41719, which was a prior patch for panic-on-malformed-input bugs in the same library. The ext/time decoder code path — the source of this new vulnerability — was not covered by the earlier fix, meaning the same class of bug survived through multiple major version rewrites.

Background: The MessagePack Binary Format

MessagePack is a compact binary serialization format designed as a faster, smaller alternative to JSON. It is used extensively in high-performance Go applications for inter-service communication, caching, and data storage. The format encodes values as a sequence of type-tagged bytes where a single format byte determines the type and length of the following data.

The fixext family of format codes is particularly relevant to this vulnerability. Fixext codes encode extension types — user-defined type annotations attached to raw binary data — in a compact fixed-length representation:

  • 0xd4 — fixext 1: 1 type byte + 1 data byte
  • 0xd5 — fixext 2: 1 type byte + 2 data bytes
  • 0xd6 — fixext 4: 1 type byte + 4 data bytes
  • 0xd7 — fixext 8: 1 type byte + 8 data bytes
  • 0xd8 — fixext 16: 1 type byte + 16 data bytes

When a decoder encounters one of these format bytes, it must read the following type byte and the specified number of data bytes before it can interpret the value. A well-implemented decoder validates that enough bytes remain in the buffer before attempting these reads. Failure to perform this check is the exact root cause of the vulnerability described in this report.

The shamaton/msgpack library implements a built-in extension type for Go's time.Time type, registering a custom decoder that handles fixext values with specific type codes. This time decoder sits in the time/decode.go file and is invoked whenever the generic asInterface function encounters an unknown fixext code during deserialization.

About shamaton/msgpack

github.com/shamaton/msgpack is a pure-Go MessagePack library maintained by Daichi Furiya (shamaton). It is available in two actively maintained major versions: v2 (import path github.com/shamaton/msgpack/v2) and the newer v3 (import path github.com/shamaton/msgpack/v3). The library supports both map-mode and array-mode struct encoding, streaming readers and writers, and custom extension type registration. It is used in production Go services as a drop-in for encoding/decoding MessagePack data received from network connections, message queues, and storage backends.

The library previously received a security fix for CVE-2022-41719, which covered multiple panic classes in the Unmarshal path triggered by malformed inputs including slice bounds violations, index out of range panics, and hash-of-unhashable-type panics. That fix (PR #32, merged October 2022) targeted the main decode loop but left the extension type decoder unguarded.

Vulnerability Discovery

The vulnerability was discovered as part of a systematic fuzzing campaign targeting Go libraries with a history of CVEs in parsing or serialization code. The approach uses Go's native fuzzing framework introduced in Go 1.18, which provides coverage-guided mutation fuzzing with zero external dependencies.

The seed corpus included normal MessagePack values covering all major type classes: nil, booleans, integers (all widths), floats (including NaN and Infinity), strings, binary data, arrays, maps, and extension types. Crucially, the fixext format bytes themselves were seeded as inputs to probe truncation handling.

Technical Root Cause Analysis

The vulnerability is located in internal/ext/decode.go at line 33, inside the ReadSize1 method of the DecoderCommon struct:

func (d *DecoderCommon) ReadSize1(index int) int8 {
    return int8((*d)[index+1])  // line 33: reads buf[index+1] without bounds check
}

This method reads the byte immediately following a given index — intended to read the type code byte that follows a fixext format byte. The call chain that triggers the vulnerability is:

  1. msgpack.Unmarshal([]byte{0xd6}, &out) — caller passes single-byte input
  2. decoding.Decode(...) — top-level decoder entry point
  3. decoder.decode(...) — dispatch to type-specific decoder
  4. decoder.asInterface(...) — generic interface{} decoder at interface.go:167
  5. timeDecoder.IsType(data, cursor) — time extension type check at time/decode.go:31
  6. DecoderCommon.ReadSize1(cursor) — reads data[cursor+1] at ext/decode.go:33

When the input is a single byte 0xd6 (fixext 4 format byte), the buffer has length 1 and the cursor is at index 0. ReadSize1(0) attempts to read data[1], which is out of bounds. Go's runtime panics with:

panic: runtime error: index out of range [1] with length 1

goroutine N [running]:
github.com/shamaton/msgpack/v2/internal/ext.(*DecoderCommon).ReadSize1(...)
        ext/decode.go:33
github.com/shamaton/msgpack/v2/internal/decoding/time.(*timeDecoder).IsType(...)
        time/decode.go:31
github.com/shamaton/msgpack/v2/internal/decoding.(*decoder).asInterface(...)
        interface.go:167

The same code — including the identical file path and line number — is present in v3.1.0, confirming both major versions share the vulnerable ext decoder implementation.

Proof of Concept

All five fixext format bytes trigger the same panic:

  • 0xd4 (fixext 1)
  • 0xd5 (fixext 2)
  • 0xd6 (fixext 4) — minimal crasher
  • 0xd7 (fixext 8)
  • 0xd8 (fixext 16)

The v3 variant uses the same input:

package main

import "github.com/shamaton/msgpack/v3"

func main() {
    var out interface{}
    msgpack.Unmarshal([]byte{0xd6}, &out) // panic
}

All four decode functions are affected: Unmarshal, UnmarshalAsMap, UnmarshalAsArray, and the Marshal path when processing round-tripped values.

Impact Assessment

Any Go service that accepts MessagePack-encoded data from untrusted sources and uses github.com/shamaton/msgpack for deserialization is vulnerable to a denial-of-service attack. The attack requires sending a single byte — the minimum possible payload — making it trivially exploitable from any network interface that accepts MessagePack input.

In a typical microservices architecture, this could be exploited by: sending a crafted message to an internal RPC endpoint, publishing a malformed event to a message queue consumed by a Go service, or injecting a corrupted cache entry into a system that deserializes cached values using this library.

The resulting unrecovered panic in Go terminates the goroutine and, if not protected by a top-level recover(), crashes the entire process. Services using this library without explicit panic recovery around their deserialization layer are immediately vulnerable to process termination.

CVSS 3.1 Base Score: 7.5 (High)
Vector: AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

Relation to CVE-2022-41719

CVE-2022-41719 was assigned in 2022 for three distinct panic classes in shamaton/msgpack v2: slice bounds out of range, index out of range, and hash of unhashable type. The fix (PR #32) addressed these by returning errors instead of panicking in the main decode loop. However, the ext/time decoder — a separate subsystem responsible for decoding extension type values — was not audited as part of that fix. This represents an incomplete security patch, a common occurrence when a fix addresses specific reported symptoms rather than systematically auditing all related code paths. The new vulnerability follows the same pattern (missing bounds check before buffer read) but in a code path that was overlooked during the original remediation.

Suggested Fix

The fix requires adding a bounds check in ReadSize1 (and analogously in ReadSize2, ReadSize4, ReadSize8) before accessing subsequent bytes:

func (d *DecoderCommon) ReadSize1(index int) (int8, error) {
    if index+1 >= len(*d) {
        return 0, fmt.Errorf("msgpack: unexpected end of data")
    }
    return int8((*d)[index+1]), nil
}

Alternatively, the IsType method in the time decoder should validate remaining buffer length before calling ReadSize1.

Disclosure Timeline

  • February 19, 2026 — Vulnerability discovered via fuzzing
  • February 19, 2026 — Issue filed at github.com/shamaton/msgpack/issues/59
  • February 19, 2026 — Reported to Go vulnerability database (x/vulndb)
  • March 5, 2026 — Public disclosure deadline (14 days)

References