CVE-2023-25568: Allocation of Resources Without Limits or Throttling (CWE-770)
value: Boxo, formerly known as go-libipfs, is a library for building IPFS applications and implementations. In versions 0.4.0 and 0.5.0, if an attacker is able allocate arbitrary many bytes in the Bitswap server, those allocations are lasting even if the connection is closed. This affects users accepting untrusted connections with the Bitswap server and also affects users using the old API stubs at `github.com/ipfs/go-libipfs/bitswap` because users then transitively import `github.com/ipfs/go-libipfs/bitswap/server`. Boxo versions 0.6.0 and 0.4.1 contain a patch for this issue. As a workaround, those who are using the stub object at `github.com/ipfs/go-libipfs/bitswap` not taking advantage of the features provided by the server can refactor their code to use the new split API that will allow them to run in a client only mode: `github.com/ipfs/go-libipfs/bitswap/client`.
CWE description: The product allocates a reusable resource or group of resources on behalf of an actor without imposing any restrictions on the size or number of resources that can be allocated, in violation of the intended security policy for that actor.
CVSS v3 base score: 7.5
Published: 2023-05-10T14:15Z
Repository: https://github.com/ipfs/boxo
Language: Go
Files changed: bitswap/server/internal/decision/engine_test.go
Commit date: 2023-02-17 18:06:35 +0100
Commit message: bitswap/server/internal/decision: add more non flaky tests

Diff: diff --git a/bitswap/server/internal/decision/engine_test.go b/bitswap/server/internal/decision/engine_test.go
index 9fd3b1b0..56dfcf60 100644
--- a/bitswap/server/internal/decision/engine_test.go
+++ b/bitswap/server/internal/decision/engine_test.go
@@ -1,12 +1,14 @@
 package decision
 
 import (
 	"bytes"
 	"context"
+	"encoding/binary"
 	"errors"
 	"fmt"
+	"math/rand"
 	"strings"
 	"sync"
 	"testing"
 	"time"
 
@@ -21,10 +23,11 @@ import (
 	blocks "github.com/ipfs/go-libipfs/blocks"
 	"github.com/ipfs/go-libipfs/internal/test"
 	process "github.com/jbenet/goprocess"
 	peer "github.com/libp2p/go-libp2p/core/peer"
 	libp2ptest "github.com/libp2p/go-libp2p/core/test"
+	mh "github.com/multiformats/go-multihash"
 )
 
 type peerTag struct {
 	done  chan struct{}
 	peers map[peer.ID]int
@@ -1049,10 +1052,16 @@ func TestWantlistForPeer(t *testing.T) {
 
 	entries = e.WantlistForPeer(partner)
 	if len(entries) != 4 {
 		t.Fatal("expected wantlist to contain all wants from parter")
 	}
+
+	e.PeerDisconnected(partner)
+	entries = e.WantlistForPeer(partner)
+	if len(entries) != 0 {
+		t.Fatal("expected wantlist to be empty after disconnect")
+	}
 }
 
 func TestTaskComparator(t *testing.T) {
 	test.Flaky(t)
 
@@ -1627,5 +1636,129 @@ func stringsComplement(set, subset []string) []string {
 			complement = append(complement, letter)
 		}
 	}
 	return complement
 }
+
+func TestWantlistDoesNotGrowPastLimit(t *testing.T) {
+	ctx, cancel := context.WithCancel(context.Background())
+	defer cancel()
+
+	const limit = 32
+	warsaw := newTestEngine(ctx, "warsaw", WithMaxQueuedWantlistEntriesPerPeer(limit))
+	riga := newTestEngine(ctx, "riga")
+
+	// Send in two messages to test reslicing.
+	for i := 2; i != 0; i-- {
+		m := message.New(false)
+		for j := limit * 3 / 4; j != 0; j-- {
+			m.AddEntry(blocks.NewBlock([]byte(fmt.Sprint(i, j))).Cid(), 0, pb.Message_Wantlist_Block, true)
+		}
+		warsaw.Engine.MessageReceived(ctx, riga.Peer, m)
+	}
+
+	if warsaw.Peer == riga.Peer {
+		t.Fatal("Sanity Check: Peers have same Key!")
+	}
+
+	wl := warsaw.Engine.WantlistForPeer(riga.Peer)
+	if len(wl) != limit {
+		t.Fatal("wantlist does not match limit", len(wl))
+	}
+}
+
+func TestWantlistGrowsToLimit(t *testing.T) {
+	ctx, cancel := context.WithCancel(context.Background())
+	defer cancel()
+
+	const limit = 32
+	warsaw := newTestEngine(ctx, "warsaw", WithMaxQueuedWantlistEntriesPerPeer(limit))
+	riga := newTestEngine(ctx, "riga")
+
+	// Send in two messages to test reslicing.
+	m := message.New(false)
+	for j := limit; j != 0; j-- {
+		m.AddEntry(blocks.NewBlock([]byte(fmt.Sprint(j))).Cid(), 0, pb.Message_Wantlist_Block, true)
+	}
+	warsaw.Engine.MessageReceived(ctx, riga.Peer, m)
+
+	if warsaw.Peer == riga.Peer {
+		t.Fatal("Sanity Check: Peers have same Key!")
+	}
+
+	wl := warsaw.Engine.WantlistForPeer(riga.Peer)
+	if len(wl) != limit {
+		t.Fatal("wantlist does not match limit", len(wl))
+	}
+}
+
+func TestIgnoresCidsAboveLimit(t *testing.T) {
+	ctx, cancel := context.WithCancel(context.Background())
+	defer cancel()
+
+	const cidLimit = 64
+	warsaw := newTestEngine(ctx, "warsaw", WithMaxCidSize(cidLimit))
+	riga := newTestEngine(ctx, "riga")
+
+	// Send in two messages to test reslicing.
+	m := message.New(true)
+
+	m.AddEntry(blocks.NewBlock([]byte("Hæ")).Cid(), 0, pb.Message_Wantlist_Block, true)
+
+	var hash mh.Multihash
+	hash = binary.AppendUvarint(hash, mh.BLAKE3)
+	hash = binary.AppendUvarint(hash, cidLimit)
+	startOfDigest := len(hash)
+	hash = append(hash, make(mh.Multihash, cidLimit)...)
+	rand.Read(hash[startOfDigest:])
+	m.AddEntry(cid.NewCidV1(cid.Raw, hash), 0, pb.Message_Wantlist_Block, true)
+
+	warsaw.Engine.MessageReceived(ctx, riga.Peer, m)
+
+	if warsaw.Peer == riga.Peer {
+		t.Fatal("Sanity Check: Peers have same Key!")
+	}
+
+	wl := warsaw.Engine.WantlistForPeer(riga.Peer)
+	if len(wl) != 1 {
+		t.Fatal("wantlist add a CID too big")
+	}
+}
+
+func TestKillConnectionForInlineCid(t *testing.T) {
+	ctx, cancel := context.WithCancel(context.Background())
+	defer cancel()
+
+	warsaw := newTestEngine(ctx, "warsaw")
+	riga := newTestEngine(ctx, "riga")
+
+	if warsaw.Peer == riga.Peer {
+		t.Fatal("Sanity Check: Peers have same Key!")
+	}
+
+	// Send in two messages to test reslicing.
+	m := message.New(true)
+
+	m.AddEntry(blocks.NewBlock([]byte("Hæ")).Cid(), 0, pb.Message_Wantlist_Block, true)
+
+	var hash mh.Multihash
+	hash = binary.AppendUvarint(hash, mh.IDENTITY)
+	const digestSize = 32
+	hash = binary.AppendUvarint(hash, digestSize)
+	startOfDigest := len(hash)
+	hash = append(hash, make(mh.Multihash, digestSize)...)
+	rand.Read(hash[startOfDigest:])
+	m.AddEntry(cid.NewCidV1(cid.Raw, hash), 0, pb.Message_Wantlist_Block, true)
+
+	if !warsaw.Engine.MessageReceived(ctx, riga.Peer, m) {
+		t.Fatal("connection was not killed when receiving inline in cancel")
+	}
+
+	m.Reset(true)
+
+	m.AddEntry(blocks.NewBlock([]byte("Hæ")).Cid(), 0, pb.Message_Wantlist_Block, true)
+	m.Cancel(cid.NewCidV1(cid.Raw, hash))
+
+	if !warsaw.Engine.MessageReceived(ctx, riga.Peer, m) {
+		t.Fatal("connection was not killed when receiving inline in cancel")
+	}
+}

Fixed code: 	"encoding/binary"
	"math/rand"
	mh "github.com/multiformats/go-multihash"

	e.PeerDisconnected(partner)
	entries = e.WantlistForPeer(partner)
	if len(entries) != 0 {
		t.Fatal("expected wantlist to be empty after disconnect")
	}

func TestWantlistDoesNotGrowPastLimit(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	const limit = 32
	warsaw := newTestEngine(ctx, "warsaw", WithMaxQueuedWantlistEntriesPerPeer(limit))
	riga := newTestEngine(ctx, "riga")

	// Send in two messages to test reslicing.
	for i := 2; i != 0; i-- {
		m := message.New(false)
		for j := limit * 3 / 4; j != 0; j-- {
			m.AddEntry(blocks.NewBlock([]byte(fmt.Sprint(i, j))).Cid(), 0, pb.Message_Wantlist_Block, true)
		}
		warsaw.Engine.MessageReceived(ctx, riga.Peer, m)
	}

	if warsaw.Peer == riga.Peer {
		t.Fatal("Sanity Check: Peers have same Key!")
	}

	wl := warsaw.Engine.WantlistForPeer(riga.Peer)
	if len(wl) != limit {
		t.Fatal("wantlist does not match limit", len(wl))
	}
}

func TestWantlistGrowsToLimit(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	const limit = 32
	warsaw := newTestEngine(ctx, "warsaw", WithMaxQueuedWantlistEntriesPerPeer(limit))
	riga := newTestEngine(ctx, "riga")

	// Send in two messages to test reslicing.
	m := message.New(false)
	for j := limit; j != 0; j-- {
		m.AddEntry(blocks.NewBlock([]byte(fmt.Sprint(j))).Cid(), 0, pb.Message_Wantlist_Block, true)
	}
	warsaw.Engine.MessageReceived(ctx, riga.Peer, m)

	if warsaw.Peer == riga.Peer {
		t.Fatal("Sanity Check: Peers have same Key!")
	}

	wl := warsaw.Engine.WantlistForPeer(riga.Peer)
	if len(wl) != limit {
		t.Fatal("wantlist does not match limit", len(wl))
	}
}

func TestIgnoresCidsAboveLimit(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	const cidLimit = 64
	warsaw := newTestEngine(ctx, "warsaw", WithMaxCidSize(cidLimit))
	riga := newTestEngine(ctx, "riga")

	// Send in two messages to test reslicing.
	m := message.New(true)

	m.AddEntry(blocks.NewBlock([]byte("Hæ")).Cid(), 0, pb.Message_Wantlist_Block, true)

	var hash mh.Multihash
	hash = binary.AppendUvarint(hash, mh.BLAKE3)
	hash = binary.AppendUvarint(hash, cidLimit)
	startOfDigest := len(hash)
	hash = append(hash, make(mh.Multihash, cidLimit)...)
	rand.Read(hash[startOfDigest:])
	m.AddEntry(cid.NewCidV1(cid.Raw, hash), 0, pb.Message_Wantlist_Block, true)

	warsaw.Engine.MessageReceived(ctx, riga.Peer, m)

	if warsaw.Peer == riga.Peer {
		t.Fatal("Sanity Check: Peers have same Key!")
	}

	wl := warsaw.Engine.WantlistForPeer(riga.Peer)
	if len(wl) != 1 {
		t.Fatal("wantlist add a CID too big")
	}
}

func TestKillConnectionForInlineCid(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	warsaw := newTestEngine(ctx, "warsaw")
	riga := newTestEngine(ctx, "riga")

	if warsaw.Peer == riga.Peer {
		t.Fatal("Sanity Check: Peers have same Key!")
	}

	// Send in two messages to test reslicing.
	m := message.New(true)

	m.AddEntry(blocks.NewBlock([]byte("Hæ")).Cid(), 0, pb.Message_Wantlist_Block, true)

	var hash mh.Multihash
	hash = binary.AppendUvarint(hash, mh.IDENTITY)
	const digestSize = 32
	hash = binary.AppendUvarint(hash, digestSize)
	startOfDigest := len(hash)
	hash = append(hash, make(mh.Multihash, digestSize)...)
	rand.Read(hash[startOfDigest:])
	m.AddEntry(cid.NewCidV1(cid.Raw, hash), 0, pb.Message_Wantlist_Block, true)

	if !warsaw.Engine.MessageReceived(ctx, riga.Peer, m) {
		t.Fatal("connection was not killed when receiving inline in cancel")
	}

	m.Reset(true)

	m.AddEntry(blocks.NewBlock([]byte("Hæ")).Cid(), 0, pb.Message_Wantlist_Block, true)
	m.Cancel(cid.NewCidV1(cid.Raw, hash))

	if !warsaw.Engine.MessageReceived(ctx, riga.Peer, m) {
		t.Fatal("connection was not killed when receiving inline in cancel")
	}
}