* [PATCH net] tls: fix TX context confusion in the max payload size setsockopt
@ 2026-08-31 2:56 Jiayuan Chen
2026-09-01 3:19 ` Jakub Kicinski
0 siblings, 1 reply; 3+ messages in thread
From: Jiayuan Chen @ 2026-08-31 2:56 UTC (permalink / raw)
To: netdev
Cc: Jiayuan Chen, John Fastabend, Jakub Kicinski, Sabrina Dubroca,
David S. Miller, Eric Dumazet, Paolo Abeni, Simon Horman,
Wilfred Mallawa, linux-kernel
do_tls_setsockopt_tx_payload_len() refuses to resize records while one is
open, but reaches for the open record through tls_sw_ctx_tx(), an unchecked
cast of ctx->priv_ctx_tx. Under device offload that pointer is a
tls_offload_context_tx, so the check reads a field of the wrong struct: it
returns EBUSY on whatever happens to be there, and never sees the record
that really is open, which lets the limit be lowered mid-record.
Dispatch on tx_conf. Offload was in scope from the start, the same commit
taught tls_push_data() to honour tx_max_payload_len.
Fixes: 82cb5be6ad64 ("net/tls: support setting the maximum payload size")
Signed-off-by: Jiayuan Chen <jiayuan.chen@linux.dev>
---
base on my netdevsim + tls (in progress)
https://lore.kernel.org/netdev/20260728125658.390500-1-jiayuan.chen@linux.dev/
Previous finding:
b17cf742eaad ("tls: device: fix out-of-bounds write in tls_append_frag()")
---
net/tls/tls_main.c | 18 ++++++++++++++++--
1 file changed, 16 insertions(+), 2 deletions(-)
diff --git a/net/tls/tls_main.c b/net/tls/tls_main.c
index fbb274287aa5..e4f288c1fa34 100644
--- a/net/tls/tls_main.c
+++ b/net/tls/tls_main.c
@@ -833,15 +833,29 @@ static int do_tls_setsockopt_no_pad(struct sock *sk, sockptr_t optval,
return rc;
}
+/* priv_ctx_tx holds a different structure on each TX path, so tx_conf has to
+ * say which open record to look at.
+ */
+static bool tls_tx_record_is_open(struct tls_context *ctx)
+{
+ switch (ctx->tx_conf) {
+ case TLS_SW:
+ return !!tls_sw_ctx_tx(ctx)->open_rec;
+ case TLS_HW:
+ return !!tls_offload_ctx_tx(ctx)->open_record;
+ default:
+ return false;
+ }
+}
+
static int do_tls_setsockopt_tx_payload_len(struct sock *sk, sockptr_t optval,
unsigned int optlen)
{
struct tls_context *ctx = tls_get_ctx(sk);
- struct tls_sw_context_tx *sw_ctx = tls_sw_ctx_tx(ctx);
u16 value;
bool tls_13 = ctx->prot_info.version == TLS_1_3_VERSION;
- if (sw_ctx && sw_ctx->open_rec)
+ if (tls_tx_record_is_open(ctx))
return -EBUSY;
if (sockptr_is_null(optval) || optlen != sizeof(value))
--
2.43.0
^ permalink raw reply [flat|nested] 3+ messages in thread* Re: [PATCH net] tls: fix TX context confusion in the max payload size setsockopt 2026-08-31 2:56 [PATCH net] tls: fix TX context confusion in the max payload size setsockopt Jiayuan Chen @ 2026-09-01 3:19 ` Jakub Kicinski 2026-09-01 6:37 ` Jiayuan Chen 0 siblings, 1 reply; 3+ messages in thread From: Jakub Kicinski @ 2026-09-01 3:19 UTC (permalink / raw) To: jiayuan.chen Cc: Jakub Kicinski, netdev, john.fastabend, sd, davem, edumazet, pabeni, horms, wilfred.mallawa, linux-kernel This is an AI-generated review of your patch. The human sending this email has considered the AI review valid, or at least plausible. --- tls: fix TX context confusion in the max payload size setsockopt The patch replaces the unchecked tls_sw_ctx_tx() dereference in do_tls_setsockopt_tx_payload_len() with a new helper, tls_tx_record_is_open(), that dispatches on ctx->tx_conf so the TLS_HW path looks at tls_offload_ctx_tx(ctx)->open_record instead of a field of the wrong structure. The rest of the option handling is unchanged. > diff --git a/net/tls/tls_main.c b/net/tls/tls_main.c > index fbb274287aa5..e4f288c1fa34 100644 > --- a/net/tls/tls_main.c > +++ b/net/tls/tls_main.c > @@ -833,15 +833,29 @@ static int do_tls_setsockopt_no_pad(struct sock *sk, sockptr_t optval, > return rc; > } > > +/* priv_ctx_tx holds a different structure on each TX path, so tx_conf has to > + * say which open record to look at. > + */ > +static bool tls_tx_record_is_open(struct tls_context *ctx) > +{ > + switch (ctx->tx_conf) { > + case TLS_SW: > + return !!tls_sw_ctx_tx(ctx)->open_rec; > + case TLS_HW: > + return !!tls_offload_ctx_tx(ctx)->open_record; > + default: > + return false; > + } > +} > + > static int do_tls_setsockopt_tx_payload_len(struct sock *sk, sockptr_t optval, > unsigned int optlen) > { > struct tls_context *ctx = tls_get_ctx(sk); > - struct tls_sw_context_tx *sw_ctx = tls_sw_ctx_tx(ctx); > u16 value; > bool tls_13 = ctx->prot_info.version == TLS_1_3_VERSION; > > - if (sw_ctx && sw_ctx->open_rec) > + if (tls_tx_record_is_open(ctx)) > return -EBUSY; [Severity: High] For the TLS_HW arm, is open_record enough to tell that no record is being built? do_tls_setsockopt() calls this helper with only the socket lock held: net/tls/tls_main.c:do_tls_setsockopt() { case TLS_TX_MAX_PAYLOAD_LEN: lock_sock(sk); rc = do_tls_setsockopt_tx_payload_len(sk, optval, optlen); release_sock(sk); } but the offload TX path is serialized by ctx->tx_lock, and it drops the socket lock at a point where open_record is already NULL: net/tls/tls_device.c:tls_push_record() { offload_ctx->open_record = NULL; ... return tls_push_sg(sk, ctx, offload_ctx->sg_tx_data, 0, flags); } tls_push_sg() calls tcp_sendmsg_locked(), which for a blocking sendmsg() with a full send buffer waits in sk_stream_wait_memory() and releases the socket lock there. tls_push_data() has a second such wait on the tls_do_allocation() failure path. So can a concurrent setsockopt() see open_record == NULL, take the "not open" branch and publish a smaller ctx->tx_max_payload_len while a sender is parked mid-sendmsg holding tx_lock? If so, does the offload path then keep using the old limit for the rest of that sendmsg? tls_push_data() latches it once, before the loop, and never refreshes it: net/tls/tls_device.c:tls_push_data() { max_open_record_len = tls_ctx->tx_max_payload_len + prot->prepend_size; do { ... copy = min_t(size_t, size, max_open_record_len - record->len); } That would mean setsockopt returns 0 while records larger than the new limit continue to go out, which does not match what Documentation/networking/tls.rst says about TLS_TX_MAX_PAYLOAD_LEN ("the kernel enforces the specified limit on all outgoing TLS records. No plaintext fragment will exceed this size"). There is a follow-on question about the next call. If the sender leaves a record open across the syscall (MSG_MORE with record->num_frags < MAX_SKB_FRAGS - 1), the next tls_push_data() latches the new, smaller max_open_record_len while record->len is still the old, larger value. max_open_record_len is u32 and record->len is int, so does copy = min_t(size_t, size, max_open_record_len - record->len); wrap around here and drop the record-size clamp entirely, letting tls_append_frag() grow the record past the 2^14 plaintext maximum before "record->len >= max_open_record_len" closes it? Note that tls_sw_sendmsg_locked() re-reads the limit on every iteration: net/tls/tls_sw.c:tls_sw_sendmsg_locked() { record_room = tls_ctx->tx_max_payload_len - msg_pl->sg.size; } so the latched offload value looks like the odd one out. Would it be better to serialize the check and the store against the lock that actually protects record building? Since tls_device_sendmsg() takes tx_lock before lock_sock: net/tls/tls_device.c:tls_device_sendmsg() { mutex_lock(&tls_ctx->tx_lock); lock_sock(sk); } tx_lock is the outer lock, so taking it inside the already socket-locked helper is not an option; the acquisition in do_tls_setsockopt() would have to be reordered (or the helper made to wait for in-flight senders, as tls_sk_proto_cleanup() does on sk_write_pending). One smaller point: the plain read of tls_ctx->tx_max_payload_len in tls_push_data() against the plain write in do_tls_setsockopt_tx_payload_len() is an unannotated data race that KCSAN would flag. -- pw-bot: cr ^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: [PATCH net] tls: fix TX context confusion in the max payload size setsockopt 2026-09-01 3:19 ` Jakub Kicinski @ 2026-09-01 6:37 ` Jiayuan Chen 0 siblings, 0 replies; 3+ messages in thread From: Jiayuan Chen @ 2026-09-01 6:37 UTC (permalink / raw) To: Jakub Kicinski Cc: netdev, john.fastabend, sd, davem, edumazet, pabeni, horms, wilfred.mallawa, linux-kernel on 9/1/26 11:19 AM, Jakub Kicinski wrote: > This is an AI-generated review of your patch. The human sending this > email has considered the AI review valid, or at least plausible. > --- > tls: fix TX context confusion in the max payload size setsockopt > > The patch replaces the unchecked tls_sw_ctx_tx() dereference in > do_tls_setsockopt_tx_payload_len() with a new helper, > tls_tx_record_is_open(), that dispatches on ctx->tx_conf so the TLS_HW > path looks at tls_offload_ctx_tx(ctx)->open_record instead of a field of > the wrong structure. The rest of the option handling is unchanged. > >> diff --git a/net/tls/tls_main.c b/net/tls/tls_main.c >> index fbb274287aa5..e4f288c1fa34 100644 >> --- a/net/tls/tls_main.c >> +++ b/net/tls/tls_main.c >> @@ -833,15 +833,29 @@ static int do_tls_setsockopt_no_pad(struct sock *sk, sockptr_t optval, >> return rc; >> } >> >> +/* priv_ctx_tx holds a different structure on each TX path, so tx_conf has to >> + * say which open record to look at. >> + */ >> +static bool tls_tx_record_is_open(struct tls_context *ctx) >> +{ >> + switch (ctx->tx_conf) { >> + case TLS_SW: >> + return !!tls_sw_ctx_tx(ctx)->open_rec; >> + case TLS_HW: >> + return !!tls_offload_ctx_tx(ctx)->open_record; >> + default: >> + return false; >> + } >> +} >> + >> static int do_tls_setsockopt_tx_payload_len(struct sock *sk, sockptr_t optval, >> unsigned int optlen) >> { >> struct tls_context *ctx = tls_get_ctx(sk); >> - struct tls_sw_context_tx *sw_ctx = tls_sw_ctx_tx(ctx); >> u16 value; >> bool tls_13 = ctx->prot_info.version == TLS_1_3_VERSION; >> >> - if (sw_ctx && sw_ctx->open_rec) >> + if (tls_tx_record_is_open(ctx)) >> return -EBUSY; > [Severity: High] > For the TLS_HW arm, is open_record enough to tell that no record is being > built? do_tls_setsockopt() calls this helper with only the socket lock > held: > > net/tls/tls_main.c:do_tls_setsockopt() { > case TLS_TX_MAX_PAYLOAD_LEN: > lock_sock(sk); > rc = do_tls_setsockopt_tx_payload_len(sk, optval, optlen); > release_sock(sk); > } > > but the offload TX path is serialized by ctx->tx_lock, and it drops the > socket lock at a point where open_record is already NULL: > > net/tls/tls_device.c:tls_push_record() { > offload_ctx->open_record = NULL; > ... > return tls_push_sg(sk, ctx, offload_ctx->sg_tx_data, 0, flags); > } > > tls_push_sg() calls tcp_sendmsg_locked(), which for a blocking sendmsg() > with a full send buffer waits in sk_stream_wait_memory() and releases the > socket lock there. tls_push_data() has a second such wait on the > tls_do_allocation() failure path. > > So can a concurrent setsockopt() see open_record == NULL, take the "not > open" branch and publish a smaller ctx->tx_max_payload_len while a sender > is parked mid-sendmsg holding tx_lock? Yes, this window exists, but it seems to be a pre-existing issue. I think I can fix it by taking tx_lock. > If so, does the offload path then keep using the old limit for the rest of > that sendmsg? tls_push_data() latches it once, before the loop, and never > refreshes it: > > net/tls/tls_device.c:tls_push_data() { > max_open_record_len = tls_ctx->tx_max_payload_len + > prot->prepend_size; > do { > ... > copy = min_t(size_t, size, max_open_record_len - record->len); > } > > That would mean setsockopt returns 0 while records larger than the new > limit continue to go out, which does not match what > Documentation/networking/tls.rst says about TLS_TX_MAX_PAYLOAD_LEN ("the > kernel enforces the specified limit on all outgoing TLS records. No > plaintext fragment will exceed this size"). > > There is a follow-on question about the next call. If the sender leaves a > record open across the syscall (MSG_MORE with record->num_frags < > MAX_SKB_FRAGS - 1), the next tls_push_data() latches the new, smaller > max_open_record_len while record->len is still the old, larger value. > max_open_record_len is u32 and record->len is int, so does > > copy = min_t(size_t, size, max_open_record_len - record->len); > > wrap around here and drop the record-size clamp entirely, letting > tls_append_frag() grow the record past the 2^14 plaintext maximum before > "record->len >= max_open_record_len" closes it? > > Note that tls_sw_sendmsg_locked() re-reads the limit on every iteration: > > net/tls/tls_sw.c:tls_sw_sendmsg_locked() { > record_room = tls_ctx->tx_max_payload_len - msg_pl->sg.size; > } > > so the latched offload value looks like the odd one out. > > Would it be better to serialize the check and the store against the lock > that actually protects record building? Since tls_device_sendmsg() takes > tx_lock before lock_sock: > > net/tls/tls_device.c:tls_device_sendmsg() { > mutex_lock(&tls_ctx->tx_lock); > lock_sock(sk); > } > > tx_lock is the outer lock, so taking it inside the already socket-locked > helper is not an option; the acquisition in do_tls_setsockopt() would have > to be reordered (or the helper made to wait for in-flight senders, as > tls_sk_proto_cleanup() does on sk_write_pending). > > One smaller point: the plain read of tls_ctx->tx_max_payload_len in > tls_push_data() against the plain write in > do_tls_setsockopt_tx_payload_len() is an unannotated data race that KCSAN > would flag. I don't think so: all tls_push_data() callers hold the socket lock, and the write is under the socket lock too, so the accesses are serialized. ^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-09-01 6:37 UTC | newest] Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2026-08-31 2:56 [PATCH net] tls: fix TX context confusion in the max payload size setsockopt Jiayuan Chen 2026-09-01 3:19 ` Jakub Kicinski 2026-09-01 6:37 ` Jiayuan Chen
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®