JuliusBrussee/caveman · error

cacheengine: segment exceeds framing limit

Error message

cacheengine: segment exceeds framing limit

What it means

Thrown by stablePrefix when len(segment.Name) or len(segment.Content) exceeds math.MaxUint32, because the 8-byte frame header encodes each length as a big-endian uint32. On 64-bit platforms this is practically unreachable (it requires a > 4 GiB name or content in a single segment) and is a defensive guard.

Source

Thrown at cacheengine/engine.go:342

	var prefix []byte
	var stable []Segment
	seenNames := map[string]bool{}
	for _, segment := range segments {
		if !segment.Stable || !segment.Cacheable {
			break
		}
		if !validIdentity(segment.Name, 1024, false) || len(segment.Content) == 0 || seenNames[segment.Name] {
			return nil, nil, errors.New("cacheengine: stable segment needs name and content")
		}
		seenNames[segment.Name] = true
		if segment.Tokens < 0 || segment.ExpectedCalls < 0 {
			return nil, nil, errors.New("cacheengine: negative segment measurement")
		}
		if maxBytes < 8 || len(segment.Name) > maxBytes-8 || len(segment.Content) > maxBytes-8-len(segment.Name) || len(prefix) > maxBytes-8-len(segment.Name)-len(segment.Content) {
			return nil, nil, errors.New("cacheengine: stable prefix exceeds configured byte limit")
		}
		if len(segment.Name) > math.MaxUint32 || len(segment.Content) > math.MaxUint32 {
			return nil, nil, errors.New("cacheengine: segment exceeds framing limit")
		}
		prefix = appendFrame(prefix, segment.Name, segment.Content)
		stable = append(stable, segment)
	}
	return prefix, stable, nil
}

func appendFrame(dst []byte, name string, content []byte) []byte {
	var lengths [8]byte
	binary.BigEndian.PutUint32(lengths[:4], uint32(len(name)))
	binary.BigEndian.PutUint32(lengths[4:], uint32(len(content)))
	dst = append(dst, lengths[:]...)
	dst = append(dst, name...)
	return append(dst, content...)
}

func breakpointCandidates(segments []Segment, defaultCalls int, profile Profile) ([]Breakpoint, bool, bool, error) {
	var candidates []Breakpoint

View on GitHub (pinned to 27d5a3981a)

Solutions

  1. Split oversized content into multiple stable segments below the byte limit (the maxBytes check at engine.go:339 will usually fire first anyway)
  2. Check where the segment content is built: an unbounded io.ReadAll is almost certainly the real bug
  3. Add an upper bound when accumulating segment content from readers

Example fix

// before
content, _ := io.ReadAll(resp.Body) // unbounded
seg := cacheengine.Segment{Name: "corpus", Stable: true, Cacheable: true, Content: content}

// after
content, _ := io.ReadAll(io.LimitReader(resp.Body, maxSegmentBytes))
seg := cacheengine.Segment{Name: "corpus", Stable: true, Cacheable: true, Content: content}
Defensive patterns

Strategy: validation

Validate before calling

for _, s := range segs { if uint64(len(s.Name)) > math.MaxUint32 || uint64(len(s.Content)) > math.MaxUint32 { return errors.New("segment too large") } }

Type guard

// n/a

Prevention

When it happens

Trigger: A single stable segment whose Name or Content byte slice is larger than 4294967295 bytes; only possible when streaming/accumulating gigantic buffers into one segment.

Common situations: Essentially never in practice; would indicate a bug where the wrong slice (e.g. an entire file corpus or an unbounded reader's output) is attached as one segment's Content.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15). Data as JSON: /api/errors/2b0a41f17c49bdaa. Report an issue: GitHub.