Skip to content

BUG: segfault with using quicreuse.ListenQUIC #3345

@derrandz

Description

@derrandz

Version: v0.42.1-0.20250702212416-7b7c3ed4ce0

Stack trace:

panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x28 pc=0x9abe95]

goroutine 1 [running]:
github.com/libp2p/go-libp2p/p2p/transport/quicreuse.(*refcountedTransport).Listen(0x13e6618?, 0x0?, 0xc000000001?)
        /home/ddz/go/pkg/mod/github.com/libp2p/[email protected]/p2p/transport/quicreuse/reuse.go:149 +0x15
github.com/libp2p/go-libp2p/p2p/transport/quicreuse.newQuicListener({0x13f3b30, 0xc000487140}, 0xc000139680)
        /home/ddz/go/pkg/mod/github.com/libp2p/[email protected]/p2p/transport/quicreuse/listener.go:74 +0x264
github.com/libp2p/go-libp2p/p2p/transport/quicreuse.(*ConnManager).ListenQUICAndAssociate(0xc0003f8a50, {0x0, 0x0}, {0xc0004a4200?, 0x1?, 0x13f36d0?}, 0xc0004a83c0, 0x12cbd28)
        /home/ddz/go/pkg/mod/github.com/libp2p/[email protected]/p2p/transport/quicreuse/connmgr.go:231 +0x3ec
github.com/libp2p/go-libp2p/p2p/transport/quicreuse.(*ConnManager).ListenQUIC(...)
        /home/ddz/go/pkg/mod/github.com/libp2p/[email protected]/p2p/transport/quicreuse/connmgr.go:202
main.main.func1({0xc3, 0x85, 0xf, 0x39, 0x3e, 0x3a, 0xa2, 0x76, 0x19, 0xb1, ...}, ...)
        /home/ddz/Lab/nunet/go-libp2p-duplicate-quic-bug/main.go:128 +0x317
reflect.Value.call({0xe96840?, 0x12cbd20?, 0x416f14?}, {0x1039b73, 0x4}, {0xc0004a7860, 0x2, 0x500010000000003?})
        /snap/go/10907/src/reflect/value.go:584 +0xca6
reflect.Value.Call({0xe96840?, 0x12cbd20?, 0x0?}, {0xc0004a7860?, 0xc00029d1d8?, 0x471b93?})
        /snap/go/10907/src/reflect/value.go:368 +0xb9
go.uber.org/fx.paramTagsAnnotation.build.func1({0xc0004a7860?, 0x2?, 0x2?})
        /home/ddz/go/pkg/mod/go.uber.org/[email protected]/annotated.go:258 +0x54
reflect.Value.call({0xe96840?, 0xc00049ce40?, 0x30?}, {0x1039b73, 0x4}, {0xc0004a73e0, 0x2, 0x10?})
        /snap/go/10907/src/reflect/value.go:584 +0xca6
reflect.Value.Call({0xe96840?, 0xc00049ce40?, 0x4179a5?}, {0xc0004a73e0?, 0xf345c0?, 0xc0004a7801?})
        /snap/go/10907/src/reflect/value.go:368 +0xb9
go.uber.org/dig.defaultInvoker({0xe96840?, 0xc00049ce40?, 0xc00029deb0?}, {0xc0004a73e0?, 0x2?, 0x13fe770?})
        /home/ddz/go/pkg/mod/go.uber.org/[email protected]/container.go:257 +0x25
go.uber.org/dig.(*constructorNode).Call(0xc000485a00, {0x13fe770, 0xc00015b600})
        /home/ddz/go/pkg/mod/go.uber.org/[email protected]/constructor.go:198 +0x472
go.uber.org/dig.paramSingle.Build({{0x0, 0x0}, 0x0, {0x1403340, 0xfd6020}}, {0x13fe770, 0xc00015b600})
        /home/ddz/go/pkg/mod/go.uber.org/[email protected]/param.go:288 +0x34d
go.uber.org/dig.paramList.BuildList({{0x1403340, 0xea2c40}, {0xc0003a3440, 0x3, 0x3}}, {0x13fe770, 0xc00015b600})
        /home/ddz/go/pkg/mod/go.uber.org/[email protected]/param.go:151 +0xad
go.uber.org/dig.(*constructorNode).Call(0xc0001172b0, {0x13fe770, 0xc00015b600})
        /home/ddz/go/pkg/mod/go.uber.org/[email protected]/constructor.go:160 +0x137
go.uber.org/dig.paramSingle.Build({{0x0, 0x0}, 0x0, {0x1403340, 0x1021140}}, {0x13fe770, 0xc00015b600})
        /home/ddz/go/pkg/mod/go.uber.org/[email protected]/param.go:288 +0x34d
go.uber.org/dig.paramObjectField.Build(...)
        /home/ddz/go/pkg/mod/go.uber.org/[email protected]/param.go:485
go.uber.org/dig.paramObject.Build({{0x1403340, 0xc000486f00}, {0xc000137b80, 0x2, 0x2}, {0x0, 0x0, 0x0}}, {0x13fe770, 0xc00015b600})
        /home/ddz/go/pkg/mod/go.uber.org/[email protected]/param.go:413 +0x5d2
go.uber.org/dig.paramList.BuildList({{0x1403340, 0xc000137b30}, {0xc0004ae4e0, 0x1, 0x1}}, {0x13fe770, 0xc00015b600})
        /home/ddz/go/pkg/mod/go.uber.org/[email protected]/param.go:151 +0xad
go.uber.org/dig.(*Scope).Invoke(0xc00015b600, {0xc000137b30, 0xc0004a6cf0}, {0x0, 0x0, 0x470c01?})
        /home/ddz/go/pkg/mod/go.uber.org/[email protected]/invoke.go:123 +0x313
go.uber.org/dig.(*Container).Invoke(0xc0003f88f0?, {0xc000137b30?, 0xc0004a6cf0?}, {0x0?, 0x20?, 0xc00029e9f8?})
        /home/ddz/go/pkg/mod/go.uber.org/[email protected]/invoke.go:83 +0x25
go.uber.org/fx.runInvoke({0x776a10c89998, 0xc00011c4d8}, {{0xfdc9c0, 0xc00015b4a0}, {0xc0003a0b40, 0x6, 0x7}})
        /home/ddz/go/pkg/mod/go.uber.org/[email protected]/invoke.go:107 +0x129
go.uber.org/fx.(*module).invoke(0xc000116dd0, {{0xfdc9c0, 0xc00015b4a0}, {0xc0003a0b40, 0x6, 0x7}})
        /home/ddz/go/pkg/mod/go.uber.org/[email protected]/module.go:335 +0x145
go.uber.org/fx.(*module).invokeAll(0xc000116dd0)
        /home/ddz/go/pkg/mod/go.uber.org/[email protected]/module.go:321 +0xda
go.uber.org/fx.New({0xc0003ae008, 0x24, 0x7?})
        /home/ddz/go/pkg/mod/go.uber.org/[email protected]/app.go:507 +0x8b8
github.com/libp2p/go-libp2p/config.(*Config).NewNode(0xc000164508)
        /home/ddz/go/pkg/mod/github.com/libp2p/[email protected]/config/config.go:610 +0x13d6
github.com/libp2p/go-libp2p.NewWithoutDefaults({0xc0003961c0, 0xf, 0x1c})
        /home/ddz/go/pkg/mod/github.com/libp2p/[email protected]/libp2p.go:67 +0x65
github.com/libp2p/go-libp2p.New(...)
        /home/ddz/go/pkg/mod/github.com/libp2p/[email protected]/libp2p.go:53
main.main()
        /home/ddz/Lab/nunet/go-libp2p-duplicate-quic-bug/main.go:199 +0xf6d
exit status 2

Script to reproduce:


package main

import (
	"context"
	"crypto/rand"
	"crypto/tls"
	"fmt"
	"log"
	"net"
	"time"

	"github.com/libp2p/go-libp2p"
	dht "github.com/libp2p/go-libp2p-kad-dht"
	"github.com/libp2p/go-libp2p/core/crypto"
	"github.com/libp2p/go-libp2p/core/host"
	"github.com/libp2p/go-libp2p/core/peer"
	"github.com/libp2p/go-libp2p/core/protocol"
	"github.com/libp2p/go-libp2p/core/routing"
	"github.com/libp2p/go-libp2p/p2p/host/autorelay"
	"github.com/libp2p/go-libp2p/p2p/host/peerstore/pstoremem"
	rcmgr "github.com/libp2p/go-libp2p/p2p/host/resource-manager"
	"github.com/libp2p/go-libp2p/p2p/net/connmgr"
	"github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/relay"
	"github.com/libp2p/go-libp2p/p2p/security/noise"
	libp2ptls "github.com/libp2p/go-libp2p/p2p/security/tls"
	libp2pquic "github.com/libp2p/go-libp2p/p2p/transport/quic"
	"github.com/libp2p/go-libp2p/p2p/transport/quicreuse"
	"github.com/libp2p/go-libp2p/p2p/transport/tcp"
	ws "github.com/libp2p/go-libp2p/p2p/transport/websocket"
	webtransport "github.com/libp2p/go-libp2p/p2p/transport/webtransport"
	ma "github.com/multiformats/go-multiaddr"
	"github.com/quic-go/quic-go"
)

func main() {

	// Generate a private key for testing
	privKey, _, err := crypto.GenerateEd25519Key(rand.Reader)
	if err != nil {
		log.Fatalf("Failed to generate private key: %v", err)
	}

	// Create the libp2p config with duplicate QUIC listen addresses
	listenAddresses := []string{
		"/ip4/0.0.0.0/tcp/0",
		"/ip4/0.0.0.0/udp/1234/quic-v1", // First QUIC address
		// "/ip4/0.0.0.0/udp/1234/quic-v1", // Duplicate QUIC address - this triggers the bug
	}

	// Create connection manager
	connmgr, err := connmgr.NewConnManager(
		100,
		400,
		connmgr.WithGracePeriod(10*time.Second),
	)
	if err != nil {
		log.Fatalf("Failed to create connection manager: %v", err)
	}

	// Create peerstore
	ps, err := pstoremem.NewPeerstore()
	if err != nil {
		log.Fatalf("Failed to create peerstore: %v", err)
	}

	// Set up resource manager
	mem := int64(1024 * 1024 * 1024) // 1GB
	fds := 512

	limits := rcmgr.DefaultLimits
	limits.SystemBaseLimit.ConnsInbound = 512
	limits.SystemBaseLimit.ConnsOutbound = 512
	limits.SystemBaseLimit.Conns = 1024
	limits.SystemBaseLimit.StreamsInbound = 8192
	limits.SystemBaseLimit.StreamsOutbound = 8192
	limits.SystemBaseLimit.Streams = 16384
	scaled := limits.Scale(mem, fds)

	mgr, err := rcmgr.NewResourceManager(rcmgr.NewFixedLimiter(scaled))
	if err != nil {
		log.Fatalf("Failed to create resource manager: %v", err)
	}

	// Create QUIC reuse function that demonstrates the bug
	newReuse := func(statelessResetKey quic.StatelessResetKey, tokenGeneratorKey quic.TokenGeneratorKey) (*quicreuse.ConnManager, error) {
		udpConn, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IPv4zero, Port: 1234})
		if err != nil {
			return nil, fmt.Errorf("failed to create UDP listener: %w", err)
		}

		reuse, err := quicreuse.NewConnManager(statelessResetKey, tokenGeneratorKey)
		if err != nil {
			return nil, fmt.Errorf("failed to create reuse: %w", err)
		}

		// This is where the bug should occur - trying to lend the same transport twice
		trDone, err := reuse.LendTransport("udp4", nil, udpConn)
		if err != nil {
			return nil, fmt.Errorf("failed to add transport to reuse: %w", err)
		}

		go func() {
			<-trDone
			fmt.Println("closing UDP connection")
			udpConn.Close()
		}()

		_, err = reuse.ListenQUIC(
			ma.StringCast(fmt.Sprintf("/ip4/0.0.0.0/udp/%d/quic-v1", 1234)),
			&tls.Config{NextProtos: []string{"raw"}},
			func(*quic.Conn, uint64) bool { return false },
		)
		if err != nil {
			return nil, fmt.Errorf("failed to listen quic: %w", err)
		}

		return reuse, nil
	}

	// Create libp2p options
	var libp2pOpts []libp2p.Option
	dhtOpts := []dht.Option{
		dht.ProtocolPrefix(protocol.ID("/nunet")),
		dht.Mode(dht.ModeAutoServer),
	}

	libp2pOpts = append(libp2pOpts,
		libp2p.ListenAddrStrings(listenAddresses...),
		libp2p.ResourceManager(mgr),
		libp2p.Identity(privKey),
		libp2p.Routing(func(h host.Host) (routing.PeerRouting, error) {
			idht, err := dht.New(context.Background(), h, dhtOpts...)
			return idht, err
		}),
		libp2p.Peerstore(ps),
		libp2p.Security(libp2ptls.ID, libp2ptls.New),
		libp2p.Security(noise.ID, noise.New),
		libp2p.ChainOptions(
			libp2p.Transport(tcp.NewTCPTransport),
			libp2p.Transport(libp2pquic.NewTransport),
			libp2p.Transport(webtransport.New),
			libp2p.Transport(ws.New),
		),
		libp2p.ConnectionManager(connmgr),
		libp2p.EnableRelay(),
		libp2p.EnableHolePunching(),
		libp2p.EnableRelayService(
			relay.WithLimit(&relay.RelayLimit{
				Duration: 5 * time.Minute,
				Data:     1 << 21, // 2 MiB
			}),
		),
		libp2p.EnableAutoRelayWithPeerSource(
			func(ctx context.Context, num int) <-chan peer.AddrInfo {
				r := make(chan peer.AddrInfo)
				go func() {
					defer close(r)
					for i := 0; i < num; i++ {
						select {
						case <-ctx.Done():
							return
						default:
							// No peers to provide
						}
					}
				}()
				return r
			},
			autorelay.WithBootDelay(time.Minute),
			autorelay.WithBackoff(30*time.Second),
			autorelay.WithMinCandidates(2),
			autorelay.WithMaxCandidates(3),
			autorelay.WithNumRelays(2),
		),
		libp2p.QUICReuse(newReuse),
	)

	// This is where the bug should occur
	host, err := libp2p.New(libp2pOpts...)
	if err != nil {
		fmt.Printf("Error creating libp2p host: %v\n", err)
		fmt.Println("This confirms the bug is reproducible with duplicate QUIC addresses!")
		return
	}

	// Clean up
	if host != nil {
		host.Close()
	}
}

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