CVE-2023-25568 (Go, published 2023-05-10T14:15Z)
Description: 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`.
Severity: nan (CVSS v3 base score: 7.5)
Weakness: CWE-770 \u2014 Allocation of Resources Without Limits or Throttling
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.
Repository: https://github.com/ipfs/boxo
Files changed:
bitswap/server/internal/decision/engine_test.go
Commit message: bitswap/server/internal/decision: add more non flaky tests


Vulnerable code:

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")
	}
}