Merge pull request #10469 from Roasbeef/lnwire-timestamp-validation
lnwire: enforce non-zero timestamp in gossip messages
This commit is contained in:
3 files changed
+115
No files matched your search
@@ -2502,6 +2502,22 @@ func (d *AuthenticatedGossiper) handleNodeAnnouncement(ctx context.Context,
|
|||||||
"node=%x, source=%x", nMsg.peer, timestamp, nodeAnn.NodeID,
|
"node=%x, source=%x", nMsg.peer, timestamp, nodeAnn.NodeID,
|
||||||
nMsg.source.SerializeCompressed())
|
nMsg.source.SerializeCompressed())
|
||||||
|
|
||||||
|
// Although not explicitly required by BOLT 7 for node announcements
|
||||||
|
// (unlike channel updates), we still enforce non-zero timestamps as a
|
||||||
|
// sanity check. A timestamp of zero is likely indicative of a bug or
|
||||||
|
// uninitialized message.
|
||||||
|
if nodeAnn.Timestamp == 0 {
|
||||||
|
err := fmt.Errorf("rejecting node announcement with zero "+
|
||||||
|
"timestamp for node %x", nodeAnn.NodeID)
|
||||||
|
|
||||||
|
log.Warnf("Rejecting node announcement from peer=%v: %v",
|
||||||
|
nMsg.peer, err)
|
||||||
|
|
||||||
|
nMsg.err <- err
|
||||||
|
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
// We'll quickly ask the router if it already has a newer update for
|
// We'll quickly ask the router if it already has a newer update for
|
||||||
// this node so we can skip validating signatures if not required.
|
// this node so we can skip validating signatures if not required.
|
||||||
if d.cfg.Graph.IsStaleNode(ctx, nodeAnn.NodeID, timestamp) {
|
if d.cfg.Graph.IsStaleNode(ctx, nodeAnn.NodeID, timestamp) {
|
||||||
@@ -3056,6 +3072,27 @@ func (d *AuthenticatedGossiper) handleChanUpdate(ctx context.Context,
|
|||||||
// quickly reject it.
|
// quickly reject it.
|
||||||
timestamp := time.Unix(int64(upd.Timestamp), 0)
|
timestamp := time.Unix(int64(upd.Timestamp), 0)
|
||||||
|
|
||||||
|
// Per BOLT 7, the timestamp MUST be greater than 0.
|
||||||
|
if upd.Timestamp == 0 {
|
||||||
|
err := fmt.Errorf("rejecting channel update with zero "+
|
||||||
|
"timestamp for short_chan_id(%v)", shortChanID)
|
||||||
|
|
||||||
|
// Only increase ban score for remote peers.
|
||||||
|
if nMsg.isRemote {
|
||||||
|
log.Warnf("Increasing ban score for peer=%v: %v",
|
||||||
|
nMsg.peer, err)
|
||||||
|
|
||||||
|
dcErr := d.handleBadPeer(nMsg.peer)
|
||||||
|
if dcErr != nil {
|
||||||
|
err = dcErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nMsg.err <- err
|
||||||
|
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
// Fetch the SCID we should be using to lock the channelMtx and make
|
// Fetch the SCID we should be using to lock the channelMtx and make
|
||||||
// graph queries with.
|
// graph queries with.
|
||||||
graphScid, err := d.cfg.FindBaseByAlias(upd.ShortChannelID)
|
graphScid, err := d.cfg.FindBaseByAlias(upd.ShortChannelID)
|
||||||
|
|||||||
@@ -2930,6 +2930,78 @@ func TestExtraDataNodeAnnouncementValidation(t *testing.T) {
|
|||||||
require.NoError(t, err, "unable to process announcement")
|
require.NoError(t, err, "unable to process announcement")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestZeroTimestampNodeAnnouncementRejection tests that a NodeAnnouncement with
|
||||||
|
// a zero timestamp is rejected per BOLT 7.
|
||||||
|
func TestZeroTimestampNodeAnnouncementRejection(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
ctx := t.Context()
|
||||||
|
|
||||||
|
tCtx, err := createTestCtx(t, 0, false)
|
||||||
|
require.NoError(t, err, "can't create context")
|
||||||
|
|
||||||
|
remotePeer := &mockPeer{
|
||||||
|
remoteKeyPriv1.PubKey(), nil, nil, atomic.Bool{},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a node announcement with a zero timestamp.
|
||||||
|
nodeAnn, err := createNodeAnnouncement(remoteKeyPriv1, 0)
|
||||||
|
require.NoError(t, err, "can't create node announcement")
|
||||||
|
|
||||||
|
// Processing the announcement should fail with a zero timestamp error.
|
||||||
|
select {
|
||||||
|
case err = <-tCtx.gossiper.ProcessRemoteAnnouncement(
|
||||||
|
ctx, nodeAnn, remotePeer,
|
||||||
|
):
|
||||||
|
case <-time.After(2 * time.Second):
|
||||||
|
t.Fatal("did not process remote announcement")
|
||||||
|
}
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Contains(t, err.Error(), "zero timestamp")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestZeroTimestampChannelUpdateRejection tests that a ChannelUpdate with a
|
||||||
|
// zero timestamp is rejected per BOLT 7.
|
||||||
|
func TestZeroTimestampChannelUpdateRejection(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
ctx := t.Context()
|
||||||
|
|
||||||
|
tCtx, err := createTestCtx(t, 0, false)
|
||||||
|
require.NoError(t, err, "can't create context")
|
||||||
|
|
||||||
|
remotePeer := &mockPeer{
|
||||||
|
remoteKeyPriv1.PubKey(), nil, nil, atomic.Bool{},
|
||||||
|
}
|
||||||
|
|
||||||
|
// First, we need to process a channel announcement so that the channel
|
||||||
|
// update has a valid channel to refer to.
|
||||||
|
chanAnn, err := tCtx.createRemoteChannelAnnouncement(0)
|
||||||
|
require.NoError(t, err, "unable to create chan ann")
|
||||||
|
|
||||||
|
select {
|
||||||
|
case err = <-tCtx.gossiper.ProcessRemoteAnnouncement(
|
||||||
|
ctx, chanAnn, remotePeer,
|
||||||
|
):
|
||||||
|
case <-time.After(2 * time.Second):
|
||||||
|
t.Fatal("did not process remote announcement")
|
||||||
|
}
|
||||||
|
require.NoError(t, err, "unable to process chan ann")
|
||||||
|
|
||||||
|
// Now create a channel update with a zero timestamp.
|
||||||
|
chanUpdAnn, err := createUpdateAnnouncement(0, 0, remoteKeyPriv1, 0)
|
||||||
|
require.NoError(t, err, "unable to create chan update")
|
||||||
|
|
||||||
|
// Processing the update should fail with a zero timestamp error.
|
||||||
|
select {
|
||||||
|
case err = <-tCtx.gossiper.ProcessRemoteAnnouncement(
|
||||||
|
ctx, chanUpdAnn, remotePeer,
|
||||||
|
):
|
||||||
|
case <-time.After(2 * time.Second):
|
||||||
|
t.Fatal("did not process remote announcement")
|
||||||
|
}
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Contains(t, err.Error(), "zero timestamp")
|
||||||
|
}
|
||||||
|
|
||||||
// assertBroadcast checks that num messages are being broadcasted from the
|
// assertBroadcast checks that num messages are being broadcasted from the
|
||||||
// gossiper. The broadcasted messages are returned.
|
// gossiper. The broadcasted messages are returned.
|
||||||
func assertBroadcast(t *testing.T, ctx *testCtx, num int) []lnwire.Message {
|
func assertBroadcast(t *testing.T, ctx *testCtx, num int) []lnwire.Message {
|
||||||
|
|||||||
@@ -107,6 +107,12 @@
|
|||||||
# Technical and Architectural Updates
|
# Technical and Architectural Updates
|
||||||
## BOLT Spec Updates
|
## BOLT Spec Updates
|
||||||
|
|
||||||
|
* [Enforce non-zero timestamps](https://github.com/lightningnetwork/lnd/pull/10469)
|
||||||
|
for `channel_update` (as required by BOLT 7) and `node_announcement` messages.
|
||||||
|
Gossip messages with zero timestamps are now rejected. For `channel_update`
|
||||||
|
messages, remote peers sending such invalid messages will have their ban score
|
||||||
|
incremented.
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
## Database
|
## Database
|
||||||
|
|||||||
Reference in new issue
Block a user