Skip to content

Commit

Permalink
Merge pull request #9134 from ellemouton/checkPayAddrBeforeDeref
Browse files Browse the repository at this point in the history
routerrpc: check payaddr before using for probing
  • Loading branch information
Roasbeef authored Sep 26, 2024
2 parents 84c91f7 + 8663950 commit 6485a81
Show file tree
Hide file tree
Showing 17 changed files with 107 additions and 73 deletions.
5 changes: 5 additions & 0 deletions docs/release-notes/release-notes-0.19.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@
propagate mission control and debug level config values to the main LND config
struct so that the GetDebugInfo response is accurate.

* [Fix a bug](https://github.com/lightningnetwork/lnd/pull/9134) that would
cause a nil pointer dereference during the probing of a payment request that
does not contain a payment address.

# New Features
## Functional Enhancements
## RPC Additions
Expand Down Expand Up @@ -69,5 +73,6 @@
# Contributors (Alphabetical Order)

* CharlieZKSmith
* Elle Mouton
* Pins
* Ziggie
3 changes: 0 additions & 3 deletions feature/default_sets.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,4 @@ var defaultSetDesc = setDesc{
SetInit: {}, // I
SetNodeAnn: {}, // N
},
lnwire.Bolt11BlindedPathsOptional: {
SetInvoice: {}, // I
},
}
12 changes: 12 additions & 0 deletions invoices/modification_interceptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ func (s *safeCallback) Exec(req HtlcModifyRequest) (*HtlcModifyResponse,
// settle an invoice, enabling a subscribed client to modify certain aspects of
// those HTLCs.
type HtlcModificationInterceptor struct {
started atomic.Bool
stopped atomic.Bool

// callback is the wrapped client callback function that is called when
// an invoice is intercepted. This function gives the client the ability
// to determine how the invoice should be settled.
Expand All @@ -79,6 +82,7 @@ type HtlcModificationInterceptor struct {
func NewHtlcModificationInterceptor() *HtlcModificationInterceptor {
return &HtlcModificationInterceptor{
callback: &safeCallback{},
quit: make(chan struct{}),
}
}

Expand Down Expand Up @@ -163,11 +167,19 @@ func (s *HtlcModificationInterceptor) RegisterInterceptor(

// Start starts the service.
func (s *HtlcModificationInterceptor) Start() error {
if !s.started.CompareAndSwap(false, true) {
return nil
}

return nil
}

// Stop stops the service.
func (s *HtlcModificationInterceptor) Stop() error {
if !s.stopped.CompareAndSwap(false, true) {
return nil
}

close(s.quit)

return nil
Expand Down
7 changes: 4 additions & 3 deletions lnrpc/routerrpc/router_backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
sphinx "github.com/lightningnetwork/lightning-onion"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/feature"
"github.com/lightningnetwork/lnd/fn"
"github.com/lightningnetwork/lnd/htlcswitch"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lntypes"
Expand Down Expand Up @@ -1011,7 +1012,7 @@ func (r *RouterBackend) extractIntentFromSendRequest(
if len(rpcPayReq.PaymentAddr) > 0 {
var addr [32]byte
copy(addr[:], rpcPayReq.PaymentAddr)
payAddr = &addr
payAddr = fn.Some(addr)
}
} else {
err = payIntent.SetPaymentHash(*payReq.PaymentHash)
Expand Down Expand Up @@ -1128,7 +1129,7 @@ func (r *RouterBackend) extractIntentFromSendRequest(
} else {
copy(payAddr[:], rpcPayReq.PaymentAddr)
}
payIntent.PaymentAddr = &payAddr
payIntent.PaymentAddr = fn.Some(payAddr)

// Generate random SetID and root share.
var setID [32]byte
Expand Down Expand Up @@ -1167,7 +1168,7 @@ func (r *RouterBackend) extractIntentFromSendRequest(
var payAddr [32]byte
copy(payAddr[:], rpcPayReq.PaymentAddr)

payIntent.PaymentAddr = &payAddr
payIntent.PaymentAddr = fn.Some(payAddr)
}
}

Expand Down
11 changes: 8 additions & 3 deletions lnrpc/routerrpc/router_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -528,11 +528,16 @@ func (s *Server) probePaymentRequest(ctx context.Context, paymentRequest string,
AmtMsat: amtMsat,
PaymentHash: paymentHash[:],
FeeLimitSat: routeFeeLimitSat,
PaymentAddr: payReq.PaymentAddr[:],
FinalCltvDelta: int32(payReq.MinFinalCLTVExpiry()),
DestFeatures: MarshalFeatures(payReq.Features),
}

// If the payment addresses is specified, then we'll also populate that
// now as well.
payReq.PaymentAddr.WhenSome(func(addr [32]byte) {
copy(probeRequest.PaymentAddr, addr[:])
})

hints := payReq.RouteHints

// If the hints don't indicate an LSP then chances are that our probe
Expand Down Expand Up @@ -1453,12 +1458,12 @@ func (s *Server) BuildRoute(_ context.Context,
outgoingChan = &req.OutgoingChanId
}

var payAddr *[32]byte
var payAddr fn.Option[[32]byte]
if len(req.PaymentAddr) != 0 {
var backingPayAddr [32]byte
copy(backingPayAddr[:], req.PaymentAddr)

payAddr = &backingPayAddr
payAddr = fn.Some(backingPayAddr)
}

if req.FinalCltvDelta == 0 {
Expand Down
2 changes: 1 addition & 1 deletion routing/integrated_routing_context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ func (c *integratedRoutingContext) testPayment(maxParts uint32,
FinalCLTVDelta: uint16(c.finalExpiry),
FeeLimit: lnwire.MaxMilliSatoshi,
Target: c.target.pubkey,
PaymentAddr: &paymentAddr,
PaymentAddr: fn.Some(paymentAddr),
DestFeatures: lnwire.NewFeatureVector(
baseFeatureBits, lnwire.Features,
),
Expand Down
23 changes: 10 additions & 13 deletions routing/pathfind.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ type finalHopParams struct {
cltvDelta uint16

records record.CustomSet
paymentAddr *[32]byte
paymentAddr fn.Option[[32]byte]

// metadata is additional data that is sent along with the payment to
// the payee.
Expand Down Expand Up @@ -226,20 +226,17 @@ func newRoute(sourceVertex route.Vertex,
// If we're attaching a payment addr but the receiver
// doesn't support both TLV and payment addrs, fail.
payAddr := supports(lnwire.PaymentAddrOptional)
if !payAddr && finalHop.paymentAddr != nil {
if !payAddr && finalHop.paymentAddr.IsSome() {
return nil, errors.New("cannot attach " +
"payment addr")
}

// Otherwise attach the mpp record if it exists.
// TODO(halseth): move this to payment life cycle,
// where AMP options are set.
if finalHop.paymentAddr != nil {
mpp = record.NewMPP(
finalHop.totalAmt,
*finalHop.paymentAddr,
)
}
finalHop.paymentAddr.WhenSome(func(addr [32]byte) {
mpp = record.NewMPP(finalHop.totalAmt, addr)
})

metadata = finalHop.metadata

Expand Down Expand Up @@ -452,7 +449,7 @@ type RestrictParams struct {
// PaymentAddr is a random 32-byte value generated by the receiver to
// mitigate probing vectors and payment sniping attacks on overpaid
// invoices.
PaymentAddr *[32]byte
PaymentAddr fn.Option[[32]byte]

// Amp signals to the pathfinder that this payment is an AMP payment
// and therefore it needs to account for additional AMP data in the
Expand Down Expand Up @@ -608,7 +605,7 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
// checking that it supports the features we need. If the caller has a
// payment address to attach, check that our destination feature vector
// supports them.
if r.PaymentAddr != nil &&
if r.PaymentAddr.IsSome() &&
!features.HasFeature(lnwire.PaymentAddrOptional) {

return nil, 0, errNoPaymentAddr
Expand Down Expand Up @@ -1435,9 +1432,9 @@ func lastHopPayloadSize(r *RestrictParams, finalHtlcExpiry int32,
}

var mpp *record.MPP
if r.PaymentAddr != nil {
mpp = record.NewMPP(amount, *r.PaymentAddr)
}
r.PaymentAddr.WhenSome(func(addr [32]byte) {
mpp = record.NewMPP(amount, addr)
})

var amp *record.AMP
if r.Amp != nil {
Expand Down
22 changes: 10 additions & 12 deletions routing/pathfind_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1367,7 +1367,7 @@ func TestNewRoute(t *testing.T) {
// overwrite the final hop's feature vector in the graph.
destFeatures *lnwire.FeatureVector

paymentAddr *[32]byte
paymentAddr fn.Option[[32]byte]

// metadata is the payment metadata to attach to the route.
metadata []byte
Expand Down Expand Up @@ -1446,7 +1446,7 @@ func TestNewRoute(t *testing.T) {
// a fee to receive the payment.
name: "two hop single shot mpp",
destFeatures: tlvPayAddrFeatures,
paymentAddr: &testPaymentAddr,
paymentAddr: fn.Some(testPaymentAddr),
paymentAmount: 100000,
hops: []*models.CachedEdgePolicy{
createHop(0, 1000, 1000000, 10),
Expand Down Expand Up @@ -1911,7 +1911,7 @@ func runDestPaymentAddr(t *testing.T, useCache bool) {
luoji := ctx.keyFromAlias("luoji")

// Add payment address w/o any invoice features.
ctx.restrictParams.PaymentAddr = &[32]byte{1}
ctx.restrictParams.PaymentAddr = fn.Some([32]byte{1})

// Add empty destination features. This should cause us to fail, since
// this overrides anything in the graph.
Expand Down Expand Up @@ -2955,7 +2955,7 @@ func runInboundFees(t *testing.T, useCache bool) {
ctx := newPathFindingTestContext(t, useCache, testChannels, "a")

payAddr := [32]byte{1}
ctx.restrictParams.PaymentAddr = &payAddr
ctx.restrictParams.PaymentAddr = fn.Some(payAddr)
ctx.restrictParams.DestFeatures = tlvPayAddrFeatures

const (
Expand All @@ -2974,7 +2974,7 @@ func runInboundFees(t *testing.T, useCache bool) {
amt: paymentAmt,
cltvDelta: finalHopCLTV,
records: nil,
paymentAddr: &payAddr,
paymentAddr: fn.Some(payAddr),
totalAmt: paymentAmt,
},
nil,
Expand Down Expand Up @@ -3469,7 +3469,7 @@ func TestLastHopPayloadSize(t *testing.T) {
{
name: "Non blinded final hop",
restrictions: &RestrictParams{
PaymentAddr: paymentAddr,
PaymentAddr: fn.Some(*paymentAddr),
DestCustomRecords: customRecords,
Metadata: metadata,
Amp: ampOptions,
Expand Down Expand Up @@ -3501,12 +3501,10 @@ func TestLastHopPayloadSize(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()

var mpp *record.MPP
if tc.restrictions.PaymentAddr != nil {
mpp = record.NewMPP(
tc.amount, *tc.restrictions.PaymentAddr,
)
}
mpp := fn.MapOptionZ(tc.restrictions.PaymentAddr,
func(addr [32]byte) *record.MPP {
return record.NewMPP(tc.amount, addr)
})

// In case it's an AMP payment we use the max AMP record
// size to estimate the final hop size.
Expand Down
2 changes: 1 addition & 1 deletion routing/payment_session.go
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@ func (p *paymentSession) RequestRoute(maxAmt, feeLimit lnwire.MilliSatoshi,
// record. If it has a blinded path though, then we
// can split. Split payments to blinded paths won't have
// MPP records.
if p.payment.PaymentAddr == nil &&
if p.payment.PaymentAddr.IsNone() &&
p.payment.BlindedPathSet == nil {

p.log.Debugf("not splitting because payment " +
Expand Down
11 changes: 6 additions & 5 deletions routing/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -862,7 +862,7 @@ type LightningPayment struct {
// PaymentAddr is the payment address specified by the receiver. This
// field should be a random 32-byte nonce presented in the receiver's
// invoice to prevent probing of the destination.
PaymentAddr *[32]byte
PaymentAddr fn.Option[[32]byte]

// PaymentRequest is an optional payment request that this payment is
// attempting to complete.
Expand Down Expand Up @@ -1063,9 +1063,10 @@ func (r *ChannelRouter) PreparePayment(payment *LightningPayment) (
switch {
// If this is an AMP payment, we'll use the AMP shard tracker.
case payment.amp != nil:
addr := payment.PaymentAddr.UnwrapOr([32]byte{})
shardTracker = amp.NewShardTracker(
payment.amp.RootShare, payment.amp.SetID,
*payment.PaymentAddr, payment.Amount,
payment.amp.RootShare, payment.amp.SetID, addr,
payment.Amount,
)

// Otherwise we'll use the simple tracker that will map each attempt to
Expand Down Expand Up @@ -1367,8 +1368,8 @@ func (e ErrNoChannel) Error() string {
// outgoing channel, use the outgoingChan parameter.
func (r *ChannelRouter) BuildRoute(amt fn.Option[lnwire.MilliSatoshi],
hops []route.Vertex, outgoingChan *uint64, finalCltvDelta int32,
payAddr *[32]byte, firstHopBlob fn.Option[[]byte]) (*route.Route,
error) {
payAddr fn.Option[[32]byte], firstHopBlob fn.Option[[]byte]) (
*route.Route, error) {

log.Tracef("BuildRoute called: hopsCount=%v, amt=%v", len(hops), amt)

Expand Down
18 changes: 10 additions & 8 deletions routing/router_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1653,14 +1653,14 @@ func TestBuildRoute(t *testing.T) {
// Test that we can't build a route when no hops are given.
hops = []route.Vertex{}
_, err = ctx.router.BuildRoute(
noAmt, hops, nil, 40, nil, fn.None[[]byte](),
noAmt, hops, nil, 40, fn.None[[32]byte](), fn.None[[]byte](),
)
require.Error(t, err)

// Create hop list for an unknown destination.
hops := []route.Vertex{ctx.aliases["b"], ctx.aliases["y"]}
_, err = ctx.router.BuildRoute(
noAmt, hops, nil, 40, &payAddr, fn.None[[]byte](),
noAmt, hops, nil, 40, fn.Some(payAddr), fn.None[[]byte](),
)
noChanErr := ErrNoChannel{}
require.ErrorAs(t, err, &noChanErr)
Expand All @@ -1672,7 +1672,8 @@ func TestBuildRoute(t *testing.T) {

// Build the route for the given amount.
rt, err := ctx.router.BuildRoute(
fn.Some(amt), hops, nil, 40, &payAddr, fn.None[[]byte](),
fn.Some(amt), hops, nil, 40, fn.Some(payAddr),
fn.None[[]byte](),
)
require.NoError(t, err)

Expand All @@ -1684,7 +1685,7 @@ func TestBuildRoute(t *testing.T) {

// Build the route for the minimum amount.
rt, err = ctx.router.BuildRoute(
noAmt, hops, nil, 40, &payAddr, fn.None[[]byte](),
noAmt, hops, nil, 40, fn.Some(payAddr), fn.None[[]byte](),
)
require.NoError(t, err)

Expand All @@ -1702,7 +1703,7 @@ func TestBuildRoute(t *testing.T) {
// There is no amount that can pass through both channel 5 and 4.
hops = []route.Vertex{ctx.aliases["e"], ctx.aliases["c"]}
_, err = ctx.router.BuildRoute(
noAmt, hops, nil, 40, nil, fn.None[[]byte](),
noAmt, hops, nil, 40, fn.None[[32]byte](), fn.None[[]byte](),
)
require.Error(t, err)
noChanErr = ErrNoChannel{}
Expand All @@ -1722,7 +1723,7 @@ func TestBuildRoute(t *testing.T) {
// policy of channel 3.
hops = []route.Vertex{ctx.aliases["b"], ctx.aliases["z"]}
rt, err = ctx.router.BuildRoute(
noAmt, hops, nil, 40, &payAddr, fn.None[[]byte](),
noAmt, hops, nil, 40, fn.Some(payAddr), fn.None[[]byte](),
)
require.NoError(t, err)
checkHops(rt, []uint64{1, 8}, payAddr)
Expand All @@ -1736,7 +1737,8 @@ func TestBuildRoute(t *testing.T) {
hops = []route.Vertex{ctx.aliases["d"], ctx.aliases["f"]}
amt = lnwire.NewMSatFromSatoshis(100)
rt, err = ctx.router.BuildRoute(
fn.Some(amt), hops, nil, 40, &payAddr, fn.None[[]byte](),
fn.Some(amt), hops, nil, 40, fn.Some(payAddr),
fn.None[[]byte](),
)
require.NoError(t, err)
checkHops(rt, []uint64{9, 10}, payAddr)
Expand All @@ -1752,7 +1754,7 @@ func TestBuildRoute(t *testing.T) {
// is a third pass through newRoute in which this gets corrected to end
hops = []route.Vertex{ctx.aliases["d"], ctx.aliases["f"]}
rt, err = ctx.router.BuildRoute(
noAmt, hops, nil, 40, &payAddr, fn.None[[]byte](),
noAmt, hops, nil, 40, fn.Some(payAddr), fn.None[[]byte](),
)
require.NoError(t, err)
checkHops(rt, []uint64{9, 10}, payAddr)
Expand Down
Loading

0 comments on commit 6485a81

Please sign in to comment.