8000
Skip to content

panic: runtime error: slice bounds out of range in (*Hive).GetValueData, resident data length not validated #25

Description

@kajaaz

Package: github.com/mandiant/gopacket/pkg/registry
Affected file: pkg/registry/hive.go
Commit tested: 5d927b8

Summary

(*Hive).GetValueData can panic with a slice-bounds-out-of-range error when parsing a registry hive whose VKRecord.DataLen encodes a resident value with a length greater than 4. Because DataLen is read verbatim from hive bytes with no prior range check, any caller that parses an attacker-supplied hive file is exposed to a denial-of-service crash.

Root cause

The Windows Registry format uses bit 31 of VKRecord.DataLen as a "resident" flag: when set, the actual data (up to 4 bytes) is stored inline in DataOffset rather than in a separate cell. The relevant code in GetValueData is:

// Data is stored in the DataOffset field itself (up to 4 bytes)
data := make([]byte, 4)
binary.LittleEndian.PutUint32(data, vk.DataOffset)
return data[:dataLen], nil

The comment documents the invariant ("up to 4 bytes"), but no guard enforces it. dataLen is derived as vk.DataLen & 0x7FFFFFFF, and vk.DataLen is populated by parseVK directly from raw hive bytes:

binary.Read(r, binary.LittleEndian, &vk.DataLen)  // no range check

If the hive contains DataLen = 0x80000005 (resident flag set, lower 31 bits = 5), then dataLen becomes 5 and data[:5] panics on the 4-byte slice.

Steps to reproduce

package main

import (
    "fmt"
    "encoding/binary"
    "github.com/mandiant/gopacket/pkg/registry"
)

func main() {
    // Minimal valid hive: regf magic + rootOffset at offset 36
    hive := make([]byte, 4096+64)
    copy(hive, []byte{0x72, 0x65, 0x67, 0x66}) // "regf"

    h, err := registry.Open(hive)
    if err != nil {
        panic(err)
    }

    vk := &registry.VKRecord{
        Signature:  0x6B76,
        DataLen:    0x80000005, // isResident=true, dataLen=5 --> data[:5] on a 4-byte slice
        DataOffset: 0x01020304,
        DataType:   1,
    }

    data, err := h.GetValueData(vk) // panic: runtime error: slice bounds out of range [:5] with capacity 4
    fmt.Println(data, err)
}

Panic output:

panic: runtime error: slice bounds out of range [:5] with capacity 4

goroutine 1 [running]:
github.com/mandiant/gopacket/pkg/registry.(*Hive).GetValueData(0x37f9430a0000?, 0x37f943092f10)
	/tmp/gopath1113421067/pkg/mod/github.com/mandiant/gopacket@v0.0.0-20260424163850-5d927b8e6b8d/pkg/registry/hive.go:258 +0xb0
main.main()
	/tmp/sandbox3190411761/prog.go:26 +0x9f

How the bug was found

This bug was discovered using Zorya, a concolic execution engine for ELF binaries. Zorya combines a GDB-based concrete execution snapshot with P-code symbolic execution and the Z3 SMT solver to find inputs that drive a program to a crash site.

A test harness was written that called the resident-data path of GetValueData directly, passing dataLen and dataOffset as arguments:

//go:noinline
func exerciseResident(dataLen uint32, dataOffset uint32) {
    data := make([]byte, 4)
    data[0] = byte(dataOffset)
    // ...
    result := data[:dataLen] // target: slice bounds panic
    fmt.Printf("resident ok len=%d\n", len(result))
}

Zorya was pointed at this function in --mode function, which snapshotted the process at the function entry, promoted dataLen and dataOffset to symbolic Z3 variables, and ran concolic analysis:

zorya path/to/bin \
  --mode function 0x4a9560 \
  --lang go --compiler gc \
  --thread-scheduling main-only \
  --arg "72656766000000000000000000000000000000000000000000000000000000000000000000000000" \
  --negate-path-exploration

(0x4a9560 is the address of main.exerciseResident in the compiled harness binary; --arg is a minimal valid regf header that lets the harness reach the function without error.)

Within 44 seconds it produced the following witness:

[*] SATISFIABLE STATE FOUND
Timestamp: 2026-05-11 08:02:00 UTC
Elapsed since start: 44.661s
Instruction Address: 0x4a9574
Panic Address:       0x4a95cb
Opcode:              CBRANCH
Detection method:    Exploring the not taken path with Overlay Execution

The program can panic if its inputs are the following:
  - The input 'dataLen' must be 5 (unsigned: 5; signed: 5)

The CBRANCH oracle detected that the slice bounds check at 0x4a9574 branches to a runtime.panicSliceB call site (0x4a95cb) when dataLen > 4, and Z3 solved the single constraint dataLen ≥ 5 to produce the minimal witness dataLen = 5.

Impact

Any application that uses this package to parse registry hives from untrusted sources (forensics pipelines, EDR agents, malware analysis tooling) can be crashed by providing a single malformed VK record with DataLen = 0x80000005. The crash is deterministic and requires no special privileges.

Suggested fix

Add a bounds check before the slice expression:

if isResident {
    if dataLen > 4 {
        return nil, fmt.Errorf("resident data length %d exceeds maximum of 4 bytes", dataLen)
    }
    data := make([]byte, 4)
    binary.LittleEndian.PutUint32(data, vk.DataOffset)
    return data[:dataLen], nil
}

This is consistent with the existing comment ("up to 4 bytes") and matches the Windows Registry format specification, which states that inline resident values are limited to 4 bytes.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

    0