json-stream: stop concatenating fd-bearing queue items with prior output-buffer bytes
json_stream_format_queue() drains queued output items into the output buffer and stages their fds in n_output_fds, relying on the downstream sendmsg() to deliver bytes-and-ancillary atomically as one SCM_RIGHTS message. If the output buffer already holds bytes (from a prior fast-path enqueue that hasn't been sent yet or from a partial write), concatenating a new fd-bearing item's JSON into it means the next sendmsg() ships the combined bytes with those fds attached — violating the per-message fd boundary on transports where that boundary is load-bearing. Bail out of the drain loop when we would cross that boundary, so the next write() first sends the buffered bytes with no ancillary, then pulls the fd-bearing item into a clean buffer and ships it on its own sendmsg. This only produces an observable difference for SOCK_SEQPACKET / SOCK_DGRAM consumers: on those transports each sendmsg() is its own datagram with its own SCM_RIGHTS cmsg, so whether we concatenate matters. On AF_UNIX SOCK_STREAM (today's sole consumer shape, used by sd-varlink and the QMP client) the kernel absorbs a preceding non-scm skb forward into the next scm-bearing skb's recv, so per-sendmsg separation is invisible to the receiver anyway — the guard is cheap defensive sender hygiene there, not a behaviour change. It becomes load- bearing the moment a SEQPACKET/DGRAM consumer wires JsonStream up.
This commit is contained in:
committed by
Daan De Meyer
parent
54c124eb6c
commit
5f420abe1a
@@ -990,8 +990,9 @@ static int json_stream_format_queue(JsonStream *s) {
|
||||
|
||||
assert(s);
|
||||
|
||||
/* Drain entries out of the output queue and format them into the output buffer. Stop
|
||||
* if there are unwritten output_fds, since adding more would corrupt the fd boundary. */
|
||||
/* Drain entries out of the output queue and format them into the output buffer.
|
||||
* Stop if there are unwritten output_fds or if the next item carries fds but
|
||||
* the output buffer is non-empty, since adding more would corrupt the fd boundary. */
|
||||
|
||||
while (s->output_queue) {
|
||||
assert(s->n_output_queue > 0);
|
||||
@@ -1000,8 +1001,24 @@ static int json_stream_format_queue(JsonStream *s) {
|
||||
return 0;
|
||||
|
||||
JsonStreamQueueItem *q = s->output_queue;
|
||||
_cleanup_free_ int *array = NULL;
|
||||
|
||||
/* If the next item carries fds but the output buffer still holds bytes from
|
||||
* a prior fast-path enqueue or a partial write, we must not concatenate its
|
||||
* JSON into that same buffer: the subsequent sendmsg() in json_stream_write()
|
||||
* would attach the fds to the combined bytes and break the message-to-fd boundary.
|
||||
* Stop here and let json_stream_write() drain the buffer first; the next write()
|
||||
* call will pull this item into a clean buffer.
|
||||
*
|
||||
* Note: this only produces a difference on SOCK_SEQPACKET / SOCK_DGRAM, where
|
||||
* each sendmsg() is its own datagram with its own SCM_RIGHTS cmsg. On AF_UNIX
|
||||
* SOCK_STREAM the kernel absorbs a preceding non-scm skb forward into the
|
||||
* next scm-bearing skb's recv, so per-sendmsg separation is invisible to the
|
||||
* receiver anyway. Kept as cheap defensive sender hygiene that's necessary
|
||||
* the moment a SEQPACKET/DGRAM consumer wires JsonStream up. */
|
||||
if (q->n_fds > 0 && s->output_buffer_size > 0)
|
||||
return 0;
|
||||
|
||||
_cleanup_free_ int *array = NULL;
|
||||
if (q->n_fds > 0) {
|
||||
array = newdup(int, q->fds, q->n_fds);
|
||||
if (!array)
|
||||
|
||||
Reference in New Issue
Block a user