Skip to content

Conversation

@github-actions
Copy link

@github-actions github-actions bot commented Jan 7, 2026

Backport of #10462


This commit fixes a critical race condition in MarkChanFullyClosed and
pruneLinkNode where link nodes could be incorrectly deleted despite
having pending channel.

The race occurred because the check for open channels and the link node
deletion happened in separate database transactions:

Thread A: TX1 checks open channels → [] (empty)
Thread A: TX1 commits
Thread B: Opens new channel with same peer
Thread A: TX2 deletes link node (using stale data)
Result: Link node deleted despite pending channel existing

This creates a TOCTOU (time-of-check to time-of-use) vulnerability where
database state changes between reading the channel count and deleting
the node.

Fix for MarkChanFullyClosed:

  • Move link node deletion into the same transaction as the channel
    closing check, making the check-and-delete operation atomic

Fix for pruneLinkNode:

  • Add double-check within the write transaction to verify no channels
    were opened since the caller's initial check
  • Maintains performance by keeping early return for common case
  • Prevents deletion if channels exist at delete time

This ensures the invariant: "link node exists iff channels exist"
is never violated, preventing database corruption and potential
connection issues.

This commit fixes a critical race condition in MarkChanFullyClosed and
pruneLinkNode where link nodes could be incorrectly deleted despite
having pending or open channels.

The race occurred because the check for open channels and the link node
deletion happened in separate database transactions:

  Thread A: TX1 checks open channels → [] (empty)
  Thread A: TX1 commits
  Thread B: Opens new channel with same peer
  Thread A: TX2 deletes link node (using stale data)
  Result: Link node deleted despite pending channel existing

This creates a TOCTOU (time-of-check to time-of-use) vulnerability where
database state changes between reading the channel count and deleting
the node.

Fix for MarkChanFullyClosed:
- Move link node deletion into the same transaction as the channel
  closing check, making the check-and-delete operation atomic

Fix for pruneLinkNode:
- Add double-check within the write transaction to verify no channels
  were opened since the caller's initial check
- Maintains performance by keeping early return for common case
- Prevents deletion if channels exist at delete time

This ensures the invariant: "link node exists iff channels exist"
is never violated, preventing database corruption and potential
connection issues.

(cherry picked from commit 51f3c6f)
We make sure that nodes previously suffering from this error will
have a consitent db view when restarting their node.

(cherry picked from commit d9fb909)
@ziggie1984 ziggie1984 force-pushed the backport-10462-to-v0.20.x-branch branch from 87cb130 to f5527e1 Compare January 7, 2026 08:21
@ziggie1984 ziggie1984 requested a review from yyforyongyu January 7, 2026 08:21
@Roasbeef Roasbeef merged commit 2cb9ff2 into v0.20.x-branch Jan 7, 2026
39 checks passed
@ziggie1984 ziggie1984 deleted the backport-10462-to-v0.20.x-branch branch January 7, 2026 19:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants