Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 144 additions & 0 deletions pkg/file/joiner/bounds_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
// Copyright 2026 The Swarm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package joiner_test

import (
"context"
"encoding/base64"
"encoding/hex"
"errors"
"io"
"os"
"path/filepath"
"testing"
"time"

"github.com/ethersphere/bee/v2/pkg/file/joiner"
"github.com/ethersphere/bee/v2/pkg/file/redundancy"
"github.com/ethersphere/bee/v2/pkg/storage/inmemchunkstore"
"github.com/ethersphere/bee/v2/pkg/swarm"
"github.com/ethersphere/langos"
)

// TestJoinerBounds reproduces the joiner panic and timeout against the
// actual malformed sub-reference of the tree, read the way the /bzz
// file-serving path reads it (through langos look-ahead) rather than by
// sequentially reading the root.
//
// Chunk1 is the tree root. TestJoinerCrashTest joins Chunk1 directly, but the
// root advertises a span (352) smaller than its own chunk data, so the joiner
// treats it as a single leaf, copies 352 bytes and stops without ever
// descending into the tree - hence no panic there.
//
// The /bzz endpoint instead resolves Chunk1 as a manifest and ends up serving
// the child reference Chunk2, whose header advertises a span (524288 bytes) far
// larger than the data actually present in the tree. Serving it wraps the
// joiner in langos, which reads from offset 0 while peeking the next buffer at
// offset smallFileBufferSize.
//
// Root Cause Details:
// 1. In TestJoinerBounds/langos crash (with langos), the peek's ReadAt at offset 262144
// descended into the first child chunk Chunk1. The parent's layout expected Chunk1's
// subtree to cover a section size of 438272 bytes, but Chunk1 is actually a leaf
// chunk of only 4096 bytes. Because the joiner did not validate that the child's
// actual span matches the parent's layout expectation, it descended into Chunk1
// with off=262144, cur=0. Since Chunk1's span (4096) <= len(data) (4096), it was
// treated as a leaf, calculating dataOffsetStart = off - cur = 262144, and
// sliced the data out of bounds, crashing the node.
// 2. In TestJoinerBounds/timeout (without langos), reading sequentially from offset 0
// successfully read the first 4096 bytes from Chunk1. The next read at offset 4096
// descended into Chunk1 again (expecting it to cover up to 438272 bytes). This
// calculated dataOffsetStart = 4096, which resulted in copying 0 bytes and
// returning 0, nil. io.Copy kept retrying indefinitely, causing the hang.
// 3. Note that langos itself functions correctly. Since the root chunk Chunk2
// advertises a span of 524288 bytes, langos is entirely justified in issuing a
// read/peek at offset 262144 (which is less than the advertised size). The joiner,
// as an io.Reader/io.ReaderAt implementation, must return an error when queried
// past the actual tree boundaries rather than panicking or hanging.
//
// The joiner enforces bounds check limits on leaf chunk reads, returning
// ErrMalformedTrie immediately if the offset is invalid.
func TestJoinerBounds(t *testing.T) {
t.Run("langos crash", func(t *testing.T) {
t.Parallel()
testJoinerBug(t, true)
})

t.Run("timeout", func(t *testing.T) {
t.Parallel()
testJoinerBug(t, false)
})
}

func testJoinerBug(t *testing.T, useLangos bool) {
t.Helper()

ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

store := inmemchunkstore.New()
addrs := []string{
"324881a5e0980e21eec6ab4348767a322260b4d477198aeb2500d9d492a99518", // Chunk1
"7c512937c1aac8d772cff0d071cbdcda15748c9d422904007342a30dd58ef05d", // Chunk2
"147a4a13002cfe9033b04bbf64b4de8091645930fa58f170fa146b0a7e0d7695", // Chunk3
"6650507776a544842c632ed1dde92f8a456a88495308349f147472de3c1620f8", // Chunk4
"8504f2a107ca940beafc4ce2f6c9a9f0968c62a5b5893ff0e4e1e2983048d276", // Chunk5
"95378adf59567d5db28e970fc60d2dcf0568f11804fd8aa27d2ee7c9eac24100", // Chunk6
}

for _, addrStr := range addrs {
b64Data, err := os.ReadFile(filepath.Join("testdata/bounds", addrStr+".b64"))
if err != nil {
t.Fatal(err)
}
data, err := base64.StdEncoding.DecodeString(string(b64Data))
if err != nil {
t.Fatal(err)
}
addr, err := hex.DecodeString(addrStr)
if err != nil {
t.Fatal(err)
}
if err := store.Put(ctx, swarm.NewChunk(swarm.NewAddress(addr), data)); err != nil {
t.Fatal(err)
}
}

// Join the malformed child reference directly, as /bzz does after resolving
// the manifest at Chunk1.
ref := swarm.MustParseHexAddress("7c512937c1aac8d772cff0d071cbdcda15748c9d422904007342a30dd58ef05d")
reader, _, err := joiner.New(ctx, store, store, ref, redundancy.DefaultDownloadLevel)
if err != nil {
t.Fatal(err)
}

if useLangos {
// smallFileBufferSize mirrors the lookahead buffer size that pkg/api uses when
// serving sub-10MB files through langos (see lookaheadBufferSize in bzz.go).
// langos peeks one buffer ahead, i.e. while serving from offset 0 it also issues
// a ReadAt at this offset.
const smallFileBufferSize = 8 * 32 * 1024 // 262144

// Serve it exactly like the API does: wrap the joiner in langos and read it
// from the start. langos' look-ahead peek issues the panicking ReadAt at
// offset smallFileBufferSize. Once the joiner rejects such malformed trees
// this should instead return an error here.
lr := langos.NewBufferedLangos(reader, smallFileBufferSize)
if _, err := io.Copy(io.Discard, lr); err == nil {
t.Fatal("expected error, got nil")
} else if !errors.Is(err, joiner.ErrMalformedTrie) {
t.Fatalf("expected ErrMalformedTrie, got: %v", err)
}
} else {
// Sequentially read the entire file. Without the leaf bounds check,
// this path would hang indefinitely (reproducing the timeout) because
// it kept retrying to read 0 bytes from the leaf chunk.
if _, err := io.Copy(io.Discard, reader); err == nil {
t.Fatal("expected error, got nil")
} else if !errors.Is(err, joiner.ErrMalformedTrie) {
t.Fatalf("expected ErrMalformedTrie, got: %v", err)
}
}
}
21 changes: 19 additions & 2 deletions pkg/file/joiner/joiner.go
Original file line number Diff line number Diff line change
Expand Up @@ -231,15 +231,32 @@ func (j *joiner) readAtOffset(
parity int,
eg *errgroup.Group,
) {
dataLen := int64(len(data))
// we are at a leaf data chunk
if subTrieSize <= int64(len(data)) {
if subTrieSize <= dataLen {
dataOffsetStart := off - cur
// Ensure that the read offset is within the bounds of this leaf chunk.
// A malformed tree might advertise a larger span, leading to an out-of-bounds start offset.
if dataOffsetStart < 0 || dataOffsetStart >= dataLen {
eg.Go(func() error {
return ErrMalformedTrie
})
return
}
dataOffsetEnd := dataOffsetStart + bytesToRead

if lenDataToCopy := int64(len(data)) - dataOffsetStart; bytesToRead > lenDataToCopy {
if lenDataToCopy := dataLen - dataOffsetStart; bytesToRead > lenDataToCopy {
dataOffsetEnd = dataOffsetStart + lenDataToCopy
}

// Guard against slicing out-of-bounds if the computed end offset is invalid.
if dataOffsetEnd < dataOffsetStart || dataOffsetEnd > dataLen {
eg.Go(func() error {
return ErrMalformedTrie
})
return
}

bs := data[dataOffsetStart:dataOffsetEnd]
n := copy(b[bufferOffset:bufferOffset+int64(len(bs))], bs)
atomic.AddInt64(bytesRead, int64(n))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFdos7an21bSHRq/9A1Bzr/INEj+2NfpsG7A07Bz8o8gZlBQd3alRIQsYy7R3ekvikVqiElTCDSfFHRy3jwWIPgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
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
Loading
Loading