onionmessage: drop onion messages cycling back to the sending peer#10754
Conversation
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request introduces a security hardening measure for onion message routing. By detecting and blocking messages that would be immediately routed back to the originating peer, the system prevents trivial routing loops and potential traffic amplification attacks. The change ensures that the routing logic validates the next hop against the sender's identity before proceeding with the forward action. Highlights
🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. Footnotes
|
🟡 PR Severity: MEDIUM
🟡 Medium (2 files)
🟢 Low / Excluded (1 file)
AnalysisThis PR makes additions to the No severity bump is warranted: only 2 non-test files are changed (threshold: >20), and only 31 non-test lines are added (threshold: >500). To override, add a |
There was a problem hiding this comment.
Code Review
This pull request introduces logic to detect and block onion message cycles by preventing messages from being forwarded back to the peer that sent them. It includes the addition of the ErrSamePeerCycle error and a new test case, TestOnionPeerActorSamePeerCycle, which validates the cycle detection for both direct node IDs and SCID-resolved paths. The review feedback suggests optimizing the serialization of the next node ID to avoid redundant operations and updating the logging format to adhere to the repository's structured logging style guide.
| var nextNodeIDBytes [33]byte | ||
| copy( | ||
| nextNodeIDBytes[:], | ||
| fwdAction.nextNodeID.SerializeCompressed(), | ||
| ) |
There was a problem hiding this comment.
| log.WarnS(logCtx, | ||
| "Dropping cyclic onion message", | ||
| ErrSamePeerCycle, | ||
| lnutils.LogPubKey( | ||
| "next_node_id", | ||
| fwdAction.nextNodeID, | ||
| ), | ||
| ) |
There was a problem hiding this comment.
Adhere to the repository's structured logging style. Use key-value pairs for all attributes (including the error) and keep each attribute on a single line. Structured log lines are exempt from the 80-character limit.
log.WarnS(logCtx, "Dropping cyclic onion message",
slog.Any("err", ErrSamePeerCycle),
lnutils.LogPubKey("next_node_id", fwdAction.nextNodeID))References
- Structured log lines should use key-value pairs and one line per key-value pair for multiple attributes. They are an exception to the 80-character rule. (link)
Block forwarding of an onion message when the resolved next hop is the same peer that delivered it. Such a forward would immediately bounce the message back over the very connection it arrived on, which is never useful and can be abused to amplify traffic against a peer. The check runs after the routing action is resolved, so both direct next-node-ID and SCID-resolved paths are covered. A new `ErrSamePeerCycle` is returned (and logged at warn level) when a cycle is detected.
520b451 to
261babc
Compare
|
Seems like a nice to have edge case but it doesn’t materially change the resource-exhaustion story for onion messages I would say. |
Add a new release-notes-0.21.1.md file based on the template, introduce a Robustness subsection under Technical and Architectural Updates, and record the onion-message same-peer cycle drop from lightningnetwork#10754.
No, this is just a tiny improvement, that aligns forwarding behavior of onion messages with our forwarding behavior for HTLCs. But don't underestimate the power of cycles in routes. The Lockdown Attack depends on them: https://eprint.iacr.org/2019/1149 This is just a way to mitigate cycles of length two. |
ziggie1984
left a comment
There was a problem hiding this comment.
LGTM, minor nits (by gemini)
| case <-h.sender.sent: | ||
| require.FailNow(t, "message should not have "+ | ||
| "been forwarded back to the sending "+ | ||
| "peer") |
There was a problem hiding this comment.
Nit: we usually have a blank line before the next case statement
|
|
||
| // Block same-peer cycles: do not forward a message back to | ||
| // the peer that sent it. | ||
| routingAction.WhenLeft(func(fwdAction forwardAction) { |
There was a problem hiding this comment.
Minor readability nit: since this only applies to the forward branch, you could fold the same-peer check into the forwarding path instead of doing a separate WhenLeft(...) pass and then
converting nextNodeID to [33]byte again below. Current code is fine, but this would keep the forward-case validation and execution together.
var payload *lnwire.OnionMessagePayload
routingAction.WhenLeft(func(fwdAction forwardAction) {
var nextNodeIDBytes [33]byte
copy(nextNodeIDBytes[:], fwdAction.nextNodeID.SerializeCompressed())
if nextNodeIDBytes == a.peerPubKey {
err = ErrSamePeerCycle
return
}
log.DebugS(logCtx, "Forwarding onion message",
lnutils.LogPubKey("next_node_id", fwdAction.nextNodeID),
)
nextMsg := lnwire.NewOnionMessage(
fwdAction.nextPathKey,
fwdAction.nextPacket,
)
sendErr := a.peerSender.SendToPeer(nextNodeIDBytes, nextMsg)
if sendErr != nil {
log.ErrorS(logCtx, "Failed to forward onion message", sendErr)
}
payload = fwdAction.payload
})
routingAction.WhenRight(func(dlvrAction deliverAction) {
log.DebugS(logCtx, "Delivering onion message to self")
payload = dlvrAction.payload
})
if err != nil {
return fn.Err[*Response](err)
}
Record the onion-message same-peer cycle drop from lightningnetwork#10754 under a new Robustness subsection in the 0.21.0 release notes.
84a16e7 to
84ec3cb
Compare
|
Successfully created backport PR for |
…21.x-branch [v0.21.x-branch] Backport #10754: onionmessage: drop onion messages cycling back to the sending peer
Block forwarding of an onion message when the resolved next hop is the same peer that delivered it. Such a forward would immediately bounce the message back over the connection it arrived on, which is never useful and can be abused to amplify traffic against a peer (or to have us unknowingly participate in a trivial loop between two malicious neighbours).
The check runs after the routing action has been resolved, so it catches both the direct next-node-ID case and the SCID-resolved case. When a cycle is detected a new sentinel
ErrSamePeerCycleis returned and the drop is logged at warn level.Note on HTLC cycle hardening
It may be worth applying a similar hardening to HTLC forwarding. The current HTLC cycle check prevents forwarding back over the same channel, but it would still allow a cycle if the return hop goes over a parallel channel to the same peer. LND supports multiple channels per peer, so a peer-level check (in addition to the existing channel-level one) would be a natural next step.