Skip to content

bug: Panic (slice bounds out of range) in onionBtS / onion3BtS transcoders on short input #288

@kajaaz

Description

@kajaaz

Repository: multiformats/go-multiaddr
File: protocols.go (onion / onion3 transcoder)
Severity: Medium
Affects: libp2p, IPFS, go-ethereum devp2p, Filecoin, Celestia, Arbitrum, and every Go project using go-multiaddr for P2P networking

What happened?

onionBtS and onion3BtS access fixed byte offsets in the input slice without checking len(b) first:

func onionBtS(b []byte) (string, error) {
    host := b[:10]                            // ← panic if len(b) < 10
    port := binary.BigEndian.Uint16(b[10:12]) // ← panic if len(b) < 12
    return fmt.Sprintf("%s:%d", base32.StdEncoding.EncodeToString(host), port), nil
}

func onion3BtS(b []byte) (string, error) {
    host := b[:35]                            // ← panic if len(b) < 35
    port := binary.BigEndian.Uint16(b[35:37]) // ← panic if len(b) < 37
    return fmt.Sprintf("%s:%d", base32.StdEncoding.EncodeToString(host), port), nil
}

The normal NewMultiaddrBytes code path validates lengths before calling these transcoders, but multiaddr.Cast(untrustedBytes) bypasses that validation. Calling .String() on a Cast-ed multiaddr with a short ONION/ONION3 payload then reaches the transcoder and panics.

Expected behavior

onionBtS and onion3BtS should return an error (not panic) when the input slice is shorter than the required fixed size.

Actual behavior

panic: runtime error: slice bounds out of range [:35] with length 2

Steps to reproduce

package main

import (
    ma "github.com/multiformats/go-multiaddr"
    "fmt"
)

func main() {
    // 2-byte payload claiming to be ONION3 — too short
    malicious := []byte{
        0x00, 0x25,             // varint for ONION3 protocol code
        0x00, 0x01,             // 2-byte payload (need 37)
    }
    m := ma.Cast(malicious)     // bypasses length validation
    fmt.Println(m.String())     // panics: slice bounds out of range [:35] with length 2
}
goroutine 1 [running]:
github.com/multiformats/go-multiaddr.onion3BtS(...)
    protocols.go:...
panic: runtime error: slice bounds out of range [:35] with length 2
exit status 2

Suggested fix

Add a length guard at the top of each transcoder:

func onionBtS(b []byte) (string, error) {
    if len(b) < 12 {
        return "", fmt.Errorf("onion address too short: %d bytes, want 12", len(b))
    }
    host := b[:10]
    port := binary.BigEndian.Uint16(b[10:12])
    return fmt.Sprintf("%s:%d", base32.StdEncoding.EncodeToString(host), port), nil
}

func onion3BtS(b []byte) (string, error) {
    if len(b) < 37 {
        return "", fmt.Errorf("onion3 address too short: %d bytes, want 37", len(b))
    }
    host := b[:35]
    port := binary.BigEndian.Uint16(b[35:37])
    return fmt.Sprintf("%s:%d", base32.StdEncoding.EncodeToString(host), port), nil
}

Impact

Any application using go-multiaddr that calls Cast() on untrusted bytes (e.g., received from the P2P network), or any codec implementation that builds multiaddrs from deserialized data without replicating the exact validation logic, can be crashed remotely with a single malformed peer address advertisement.

Discovery method : Zorya concolic executor

This bug was found using Zorya, a concolic execution engine for Go binaries. Zorya symbolized the n (claimed payload length) parameter and used Z3 to produce satisfying assignments for all OOB paths in both transcoders.

onion3BtS : function 0x4b9040:

zorya zorya_onion_real \
  --mode function 0x4b9040 \
  --thread-scheduling main-only \
  --lang go \
  --compiler gc \
  --arg "0c00000000000000" \
  --negate-path-exploration

--arg "0c00000000000000" is the little-endian int64 encoding of 12 (a safe seed: full onion address length).

Z3 Witness Opcode Elapsed Behaviour
n = MinInt64 INT_SUB 60 s Signed subtraction wraps → OOB
n = 38 (> 37) CBRANCH 68 s OOB beyond fixed buffer
n = 0 LOAD 90 s b[:35] on empty slice

onionBtS : function 0x4b9280:

zorya zorya_onion_real \
  --mode function 0x4b9280 \
  --thread-scheduling main-only \
  --lang go \
  --compiler gc \
  --arg "0c00000000000000" \
  --negate-path-exploration
Z3 Witness Opcode Elapsed Behaviour
n = MinInt64 INT_SUB 61 s Same signed-subtraction path
n = 38 (> 12) CBRANCH 68 s OOB beyond 12-byte expected
n = 0 LOAD 89 s b[:10] on empty slice

Binary: targets/_third_party/go-multiaddr/src/cmd/zorya_onion/zorya_onion_real

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions