connect: switch to using io_write_partial instead of io_write.
What changed, and why it matters
This change refactors how Core Lightning's connection daemon sends encrypted messages to peers. Previously, the daemon wrote entire encrypted messages in one go using io_write. Now it uses io_write_partial, which writes at most 1460 bytes at a time, tracking how much has been sent and continuing until the full message is transmitted. The stated goal is to create more uniform packet sizes on the wire. The patch itself is a defensive refactoring; there is no direct evidence in the commit or supplied references that it fixes a known exploitable vulnerability.
Treat as a routine defensive refactor. Review for correctness of partial-write state machine, especially around connection close, error handling, and memory freeing of encrypted_peer_out when writes are interrupted. No immediate security response is indicated by the available evidence.
Security signals we found
Refactor of network output path from atomic write to partial/bounded writes
New MAX_MESSAGE_SIZE constant (1460 bytes) used to cap individual write() calls
Addition of offset/sent tracking fields for encrypted outbound buffer
No explicit security bug or vulnerability described in commit message
Evidence from the diff
The patch replaces the single-shot io_write() of the encrypted peer message buffer with a bounded io_write_partial() loop. It introduces encrypted_peer_out, encrypted_peer_out_off, and encrypted_peer_out_sent fields in struct peer to track partial writes, and caps each write to MAX_MESSAGE_SIZE (1460 bytes). The write_to_peer() continuation now increments the offset and re-arms the write plan if the buffer is not fully drained before freeing the buffer and moving to the next queued message. This is a behavioral change in network output pacing, not a cryptographic or protocol change.
Changed components
connectd/connectd.cconnectd/connectd.hconnectd/multiplex.cInspect captured patch +34 / −12
diff --git a/connectd/connectd.c b/connectd/connectd.c
index 44f81b6c..792b472f 100644
--- a/connectd/connectd.c
+++ b/connectd/connectd.c
@@ -127,7 +127,9 @@ static struct peer *new_peer(struct daemon *daemon,
peer->cs = *cs;
peer->subds = tal_arr(peer, struct subd *, 0);
peer->peer_in = NULL;
- peer->sent_to_peer = NULL;
+ peer->encrypted_peer_out = NULL;
+ peer->encrypted_peer_out_off = 0;
+ peer->encrypted_peer_out_sent = 0;
peer->urgent = false;
peer->draining_state = NOT_DRAINING;
peer->peer_in_lastmsg = -1;
diff --git a/connectd/connectd.h b/connectd/connectd.h
index 41a86cd1..451abf7a 100644
--- a/connectd/connectd.h
+++ b/connectd/connectd.h
@@ -88,8 +88,10 @@ struct peer {
/* Output buffer. */
struct msg_queue *peer_outq;
- /* Peer sent buffer (for freeing after sending) */
- const u8 *sent_to_peer;
+ /* Encrypted peer sending buffer */
+ const u8 *encrypted_peer_out;
+ size_t encrypted_peer_out_off;
+ size_t encrypted_peer_out_sent;
/* We stream from the gossip_store for them, when idle */
struct gossip_state gs;
diff --git a/connectd/multiplex.c b/connectd/multiplex.c
index 40abb508..b19bffe9 100644
--- a/connectd/multiplex.c
+++ b/connectd/multiplex.c
@@ -26,6 +26,9 @@
#include <wire/peer_wire.h>
#include <wire/wire_io.h>
+/* Maximum write(), to create uniform size packets. */
+#define MAX_MESSAGE_SIZE 1460
+
struct subd {
/* Owner: we are in peer->subds[] */
struct peer *peer;
@@ -490,6 +493,19 @@ static struct io_plan *msg_out_dev_disconnect(struct peer *peer, const u8 *msg)
abort();
}
+/* (Continue) writing the encrypted_peer_out array */
+static struct io_plan *write_encrypted_to_peer(struct peer *peer)
+{
+ size_t max = tal_bytelen(peer->encrypted_peer_out) - peer->encrypted_peer_out_off;
+ if (max > MAX_MESSAGE_SIZE)
+ max = MAX_MESSAGE_SIZE;
+ return io_write_partial(peer->to_peer,
+ peer->encrypted_peer_out + peer->encrypted_peer_out_off,
+ max,
+ &peer->encrypted_peer_out_sent,
+ write_to_peer, peer);
+}
+
static struct io_plan *encrypt_and_send(struct peer *peer, const u8 *msg TAKES)
{
int type = fromwire_peektype(msg);
@@ -498,19 +514,15 @@ static struct io_plan *encrypt_and_send(struct peer *peer, const u8 *msg TAKES)
/* Special message type directing us to process batch items. */
if (type == WIRE_PROTOCOL_BATCH_ELEMENT) {
- peer->sent_to_peer = process_batch_elements(peer, msg);
- if (!peer->sent_to_peer)
+ peer->encrypted_peer_out = process_batch_elements(peer, msg);
+ if (!peer->encrypted_peer_out)
return io_close(peer->to_peer);
}
else {
- peer->sent_to_peer = cryptomsg_encrypt_msg(peer, &peer->cs, msg);
+ peer->encrypted_peer_out = cryptomsg_encrypt_msg(peer, &peer->cs, msg);
}
- /* We free this and the encrypted version in next write_to_peer */
- return io_write(peer->to_peer,
- peer->sent_to_peer,
- tal_bytelen(peer->sent_to_peer),
- write_to_peer, peer);
+ return write_encrypted_to_peer(peer);
}
/* Kicks off write_to_peer() to look for more gossip to send from store */
@@ -1123,8 +1135,14 @@ static struct io_plan *write_to_peer(struct io_conn *peer_conn,
assert(peer->to_peer == peer_conn);
+ /* Write any remainder. */
+ peer->encrypted_peer_out_off += peer->encrypted_peer_out_sent;
+ if (peer->encrypted_peer_out_off < tal_bytelen(peer->encrypted_peer_out))
+ return write_encrypted_to_peer(peer);
+
/* Free last sent one (if any) */
- peer->sent_to_peer = tal_free(peer->sent_to_peer);
+ peer->encrypted_peer_out = tal_free(peer->encrypted_peer_out);
+ peer->encrypted_peer_out_off = 0;
/* Pop tail of send queue (or gossip) */
msg = next_msg_for_peer(peer);
Why this scored 33/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.