* [PATCH net 0/2] net/iucv: two fixes in the HiperSockets receive path
@ 2026-10-23 2:48 Bryam Vargas via B4 Relay
2026-10-23 2:48 ` [PATCH net 1/2] net/iucv: clear hs_dev before dropping its reference Bryam Vargas via B4 Relay
2026-10-23 2:48 ` [PATCH net 2/2] net/iucv: do not count the SHT frame against the receive window Bryam Vargas via B4 Relay
0 siblings, 2 replies; 5+ messages in thread
From: Bryam Vargas via B4 Relay @ 2026-10-23 2:48 UTC (permalink / raw)
To: Eric Dumazet, Jakub Kicinski, David S. Miller, Paolo Abeni,
Alexandra Winter, Thorsten Winkler
Cc: Nagamani PV, netdev, Hidayath Khan, linux-s390, Simon Horman,
linux-kernel
Two bugs in the HiperSockets receive path that predate the socket-locking
rework [1]; review and testing of that rework turned them up. Neither
depends on it.
1/2 clears hs_dev before the socket drops its reference on the device and
marks the accesses that race with it: afiucv_hs_rcv() compares hs_dev
under iucv_sk_list.lock while close() clears it under lock_sock().
Hidayath Khan caught the racing pair with KCSAN under z/VM [2] and
suggested the fix. The listener's child now takes its device from the
frame instead of reading the listener's field in softirq. This is an
ordering fix with no known crash.
2/2 stops counting the SHT's empty frame against the receive window (the
peer does not count it) and drops the WARN_ON on the overrun path, which
a peer reaches from the network by sending past its window. Only frames
with data count now; a zero-length data frame, which already ends the
stream for its reader, is no longer counted either. 2/2 applies on its
own and goes to stable.
Based on net f4377bf85887. The same two patches apply on net-next
8df0638138d3, where the test set ran, and on mainline (7.3-rc7); the
SHT case below ran on this base as well. Tested on an s390x guest
under QEMU TCG with KASAN and lockdep on. QEMU has no HiperSockets
adapter and its s390x CPU emulation lacked things this needs: the
adapter is one I wrote, under the real qeth and qdio drivers, the CPU
carries my patches, and there is no IBM Z to compare against. With 2/2
an SHT read while frames wait in backlog_skb_q gives EOF instead of
WARN plus -EFAULT. 1/2 does not change any result in the rework's test
set; its race shows under KCSAN, not here. Logs on request.
[1] https://lore.kernel.org/netdev/20261011-b4-disp-410518f4-v1-1-753bf3258292@proton.me/
[2] https://lore.kernel.org/netdev/b3fea2ca-25ff-42c0-812c-f8cc6bb6667e@linux.ibm.com/
---
Bryam Vargas (2):
net/iucv: clear hs_dev before dropping its reference
net/iucv: do not count the SHT frame against the receive window
net/iucv/af_iucv.c | 39 +++++++++++++++++++++++++++++----------
1 file changed, 29 insertions(+), 10 deletions(-)
---
base-commit: f4377bf85887f57b59abb5479f7660a81ffe4449
change-id: 20261022-b4-disp-b35a9153-ad04dc76713a
Best regards,
--
bryamzxz <hexlabsecurity@proton.me>
^ permalink raw reply [flat|nested] 5+ messages in thread* [PATCH net 1/2] net/iucv: clear hs_dev before dropping its reference 2026-10-23 2:48 [PATCH net 0/2] net/iucv: two fixes in the HiperSockets receive path Bryam Vargas via B4 Relay @ 2026-10-23 2:48 ` Bryam Vargas via B4 Relay 2026-10-27 2:50 ` netdev-bot+sashiko 2026-10-23 2:48 ` [PATCH net 2/2] net/iucv: do not count the SHT frame against the receive window Bryam Vargas via B4 Relay 1 sibling, 1 reply; 5+ messages in thread From: Bryam Vargas via B4 Relay @ 2026-10-23 2:48 UTC (permalink / raw) To: Eric Dumazet, Jakub Kicinski, David S. Miller, Paolo Abeni, Alexandra Winter, Thorsten Winkler Cc: Nagamani PV, netdev, Hidayath Khan, linux-s390, Simon Horman, linux-kernel From: Bryam Vargas <hexlabsecurity@proton.me> iucv_sock_close() drops the socket's reference on its HiperSockets device and only then clears iucv->hs_dev, while afiucv_hs_rcv() compares that field against the ingress device for every socket in the list, under iucv_sk_list.lock and not the socket lock. Between the two it compares a pointer the socket no longer holds; KCSAN reports the plain read against the plain write. The order dates from 800c5eb7b5eb, the compare from 80230a18c164. Nothing user-visible is known from it: the receive path holds the ingress device, so the stale pointer is compared, never dereferenced. Clear the pointer first, through a local, then drop the reference, and mark the lookup's read and the two stores that race with it: the clear in close and the listener's child's, which is in iucv_sk_list from iucv_sock_alloc() on. The child now takes its device and sk_bound_dev_if from the frame the lookup matched, held before it is published, instead of reading the listener's fields: close() clears them under lock_sock(), which the bh_lock_sock() in afiucv_hs_callback_syn() does not exclude, so the callback can pass its IUCV_LISTEN check and then read a field close() has cleared. That a SYN can be accepted while the listener closes is left to the socket-locking rework; this is its KCSAN-visible part. bind()'s hs_dev store runs under the list's write lock and stays plain. sk_bound_dev_if, which getsockopt() reads without the lock, is marked where close(), bind() and the child store it; annotation only. Fixes: 800c5eb7b5eb ("af_iucv: change net_device handling for HS transport") Fixes: 80230a18c164 ("net/iucv: filter frames in afiucv_hs_rcv() by ingress device") Reported-by: Hidayath Khan <hidayath@linux.ibm.com> Closes: https://lore.kernel.org/netdev/b3fea2ca-25ff-42c0-812c-f8cc6bb6667e@linux.ibm.com/ Suggested-by: Hidayath Khan <hidayath@linux.ibm.com> Signed-off-by: Bryam Vargas <hexlabsecurity@proton.me> --- Nagamani PV's "net/iucv: fix races in afiucv_netdev_event()" [1] marks the same store in close(); Alexandra Winter asked him to hold it for the locking series and he agreed [2]. This one goes now on a new, independent KCSAN report, and its close() hunk subsumes his, which marks the store but keeps dev_put() first; once this is applied, the afiucv_netdev_event() read is what remains for his patch. The loop's memcmp()s on the name fields are plain as well, against connect()'s stores under lock_sock(); they were not in Hidayath's report and are not touched here. The child that afiucv_hs_callback_syn() leaks on its error paths (iucv_sock_kill() on a socket that was never closed is a no-op), with the device reference it holds when dev_queue_xmit() fails, is pre-existing; Hidayath flagged it in August [3] and his fix for it is pending. A child the callback enqueues after iucv_sock_cleanup_listen() has drained the accept queue is never accepted or killed either; it now holds the device the frame arrived on, where before it held the listener's device or nothing, depending on which side of the clear it read. Tested on s390x under QEMU TCG (net-next 8df0638138d3, KASAN + PROVE_LOCKING) with an emulated HiperSockets adapter under the real qeth and qdio drivers. The rework's test set, 53 tests, gives the same result with and without this patch: the 8 that fail on net-next are the bugs the rework fixes, and they fail the same way. That setup has no KCSAN and nothing that drives afiucv_hs_rcv()'s lookup against a close() at the right instant. Hidayath's KCSAN report covers the lookup's read and proposed the clear-before-put order through a local; the child's device source comes from reading the code. [1] https://lore.kernel.org/netdev/20260917071706.23831-1-nagamani@linux.ibm.com/ [2] https://lore.kernel.org/netdev/1d9d3cc1-24fb-40e0-a4c7-6fbfbcf4dc37@linux.ibm.com/ [3] https://lore.kernel.org/netdev/5f88daf4-cd8e-4464-b132-ac45be205121@linux.ibm.com/ --- net/iucv/af_iucv.c | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/net/iucv/af_iucv.c b/net/iucv/af_iucv.c index db261ecd19af..501e758c5e68 100644 --- a/net/iucv/af_iucv.c +++ b/net/iucv/af_iucv.c @@ -447,9 +447,16 @@ static void iucv_sock_close(struct sock *sk) } if (iucv->hs_dev) { - dev_put(iucv->hs_dev); - iucv->hs_dev = NULL; - sk->sk_bound_dev_if = 0; + struct net_device *dev = iucv->hs_dev; + + /* afiucv_hs_rcv() matches frames on hs_dev under + * iucv_sk_list.lock, not the socket lock: clear it before the + * reference goes, so the match can no longer find a device + * this socket does not hold. + */ + WRITE_ONCE(iucv->hs_dev, NULL); + WRITE_ONCE(sk->sk_bound_dev_if, 0); + dev_put(dev); } /* mark socket for deletion by iucv_sock_kill() */ @@ -630,7 +637,7 @@ static int iucv_sock_bind(struct socket *sock, struct sockaddr_unsized *addr, __iucv_auto_name(iucv); else memcpy(iucv->src_name, sa->siucv_name, 8); - sk->sk_bound_dev_if = dev->ifindex; + WRITE_ONCE(sk->sk_bound_dev_if, dev->ifindex); iucv->hs_dev = dev; dev_hold(dev); sk->sk_state = IUCV_BOUND; @@ -1912,9 +1919,14 @@ static int afiucv_hs_callback_syn(struct sock *sk, struct sk_buff *skb) memcpy(niucv->dst_user_id, trans_hdr->srcUserID, 8); memcpy(niucv->src_name, iucv->src_name, 8); memcpy(niucv->src_user_id, iucv->src_user_id, 8); - nsk->sk_bound_dev_if = sk->sk_bound_dev_if; - niucv->hs_dev = iucv->hs_dev; - dev_hold(niucv->hs_dev); + /* skb->dev is the device the lookup matched against the listener's + * hs_dev; the listener's own fields are cleared by close() under + * lock_sock(), which this softirq does not hold. nsk is in + * iucv_sk_list already: hold, then publish. + */ + WRITE_ONCE(nsk->sk_bound_dev_if, skb->dev->ifindex); + dev_hold(skb->dev); + WRITE_ONCE(niucv->hs_dev, skb->dev); afiucv_swap_src_dest(skb); trans_hdr->flags = AF_IUCV_FLAG_SYN | AF_IUCV_FLAG_ACK; trans_hdr->window = niucv->msglimit; @@ -2087,7 +2099,11 @@ static int afiucv_hs_rcv(struct sk_buff *skb, struct net_device *dev, sk = NULL; read_lock(&iucv_sk_list.lock); sk_for_each(sk, &iucv_sk_list.head) { - if (iucv_sk(sk)->hs_dev != dev) + /* Cleared before iucv_sock_close() drops the reference: + * whichever value is read is a device the socket still + * holds, or NULL. + */ + if (READ_ONCE(iucv_sk(sk)->hs_dev) != dev) continue; if (trans_hdr->flags == AF_IUCV_FLAG_SYN) { if ((!memcmp(&iucv_sk(sk)->src_name, -- 2.56.0 ^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH net 1/2] net/iucv: clear hs_dev before dropping its reference 2026-10-23 2:48 ` [PATCH net 1/2] net/iucv: clear hs_dev before dropping its reference Bryam Vargas via B4 Relay @ 2026-10-27 2:50 ` netdev-bot+sashiko 0 siblings, 0 replies; 5+ messages in thread From: netdev-bot+sashiko @ 2026-10-27 2:50 UTC (permalink / raw) To: hexlabsecurity Cc: edumazet, kuba, davem, pabeni, wintera, twinkler, nagamani, netdev, hidayath, linux-s390, horms, linux-kernel Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider. Critical: 0 · High: 0 (3) · Medium: 1 · Low: 0 If you believe none of the issues are legitimate, please reply to each issue of Medium severity or higher and explain why the AI is wrong. If any of them are legitimate and you plan to respin, please let patchwork know by including "pw-bot: cr" as a separate line at the end of your reply (one such reply per series is enough). - [Medium] In afiucv_hs_callback_syn(), the child now takes a reference on skb->dev every time, instead of copying the listener's hs_dev. Pre-existing issues: - [High] This problem wasn't introduced by this patch, but afiucv_netdev_event() walks iucv_sk_list with sk_for_each() and holds… - [High] This problem wasn't introduced by this patch, but afiucv_hs_callback_syn() leaks the child socket it allocates on every… - [High] This problem wasn't introduced by this patch, but the SYN callback can publish a child into a listener's accept_q while that… -- net/iucv: clear hs_dev before dropping its reference This patch changes iucv_sock_close() so that iucv->hs_dev is cleared before dev_put(). It also marks the racing hs_dev and sk_bound_dev_if accesses with READ_ONCE()/WRITE_ONCE(). Finally, afiucv_hs_callback_syn() now takes the child's device reference and sk_bound_dev_if from skb->dev instead of from the listener's fields. Closes: https://lore.kernel.org/netdev/b3fea2ca-25ff-42c0-812c-f8cc6bb6667e@linux.ibm.com/ > diff --git a/net/iucv/af_iucv.c b/net/iucv/af_iucv.c > index db261ecd19af1..501e758c5e68a 100644 > --- a/net/iucv/af_iucv.c > +++ b/net/iucv/af_iucv.c > @@ -447,9 +447,16 @@ static void iucv_sock_close(struct sock *sk) > } > > if (iucv->hs_dev) { > - dev_put(iucv->hs_dev); > - iucv->hs_dev = NULL; > - sk->sk_bound_dev_if = 0; > + struct net_device *dev = iucv->hs_dev; > + > + /* afiucv_hs_rcv() matches frames on hs_dev under > + * iucv_sk_list.lock, not the socket lock: clear it before the > + * reference goes, so the match can no longer find a device > + * this socket does not hold. > + */ > + WRITE_ONCE(iucv->hs_dev, NULL); > + WRITE_ONCE(sk->sk_bound_dev_if, 0); > + dev_put(dev); > } [Severity: High] This is a pre-existing issue, but afiucv_netdev_event() also reads this field, and it holds neither iucv_sk_list.lock nor the socket lock: net/iucv/af_iucv.c:afiucv_netdev_event() { ... case NETDEV_GOING_DOWN: sk_for_each(sk, &iucv_sk_list.head) { iucv = iucv_sk(sk); if ((iucv->hs_dev == event_dev) && (sk->sk_state == IUCV_CONNECTED)) { if (event == NETDEV_GOING_DOWN) iucv_send_ctrl(sk, AF_IUCV_FLAG_FIN); ... } iucv_sock_close() runs under lock_sock() only. Can it move the socket out of IUCV_CONNECTED and run this clear after the notifier's check but before its call to iucv_send_ctrl()? iucv_send_ctrl() reads hs_dev again and uses it without a NULL check: blen = sizeof(struct af_iucv_trans_hdr) + LL_RESERVED_SPACE(iucv->hs_dev); The list walk itself also looks unprotected. iucv_sock_link() and iucv_sock_unlink() change the list under write_lock_bh(&iucv_sk_list.lock), and rtnl does not exclude them. Could a concurrent close() followed by iucv_sock_kill() unlink and free the node the notifier is on? Once this patch is applied, the notifier is the only unlocked, unannotated reader of hs_dev left. The notes below the commit message leave it to Nagamani PV's "net/iucv: fix races in afiucv_netdev_event()". [ ... ] > @@ -1912,9 +1919,14 @@ static int afiucv_hs_callback_syn(struct sock *sk, struct sk_buff *skb) > memcpy(niucv->dst_user_id, trans_hdr->srcUserID, 8); > memcpy(niucv->src_name, iucv->src_name, 8); > memcpy(niucv->src_user_id, iucv->src_user_id, 8); > - nsk->sk_bound_dev_if = sk->sk_bound_dev_if; > - niucv->hs_dev = iucv->hs_dev; > - dev_hold(niucv->hs_dev); > + /* skb->dev is the device the lookup matched against the listener's > + * hs_dev; the listener's own fields are cleared by close() under > + * lock_sock(), which this softirq does not hold. nsk is in > + * iucv_sk_list already: hold, then publish. > + */ > + WRITE_ONCE(nsk->sk_bound_dev_if, skb->dev->ifindex); > + dev_hold(skb->dev); > + WRITE_ONCE(niucv->hs_dev, skb->dev); [Severity: Medium] Can this leak a reference on skb->dev when the SYN races with close() of the listener? The callback holds only bh_lock_sock(sk). That does not exclude iucv_sock_close() of the listener, which holds lock_sock(), and the callback never checks sock_owned_by_user(). This sequence looks possible: CPU1 (softirq) afiucv_hs_callback_syn() bh_lock_sock(sk); sk->sk_state == IUCV_LISTEN, check passes CPU2 (process context) iucv_sock_close(listener) iucv_sock_cleanup_listen() drains accept_q WRITE_ONCE(iucv->hs_dev, NULL); dev_put(dev); CPU1 dev_hold(skb->dev); WRITE_ONCE(niucv->hs_dev, skb->dev); dev_queue_xmit(skb); iucv_accept_enqueue(sk, nsk); nsk->sk_state = IUCV_CONNECTED; The child then sits in IUCV_CONNECTED on the accept_q of a listener that has already been drained. Nothing accepts, closes or kills it. Before this patch, the callback read the listener's hs_dev as NULL in this interleaving, so dev_hold(NULL) did nothing and only the struct sock leaked. Now the child holds the ingress device. In the other interleaving, where hs_dev was read before the clear, the old code already leaked the listener's device reference. This change extends the device leak to the case that used to leak only the sock. afiucv_netdev_event() does nothing on NETDEV_UNREGISTER. On NETDEV_GOING_DOWN it only moves the child to IUCV_DISCONN. Would unregister_netdevice() of that HiperSockets device then wait forever in netdev_wait_allrefs()? The notes below the commit message mention this orphaned child, and that it now holds the device the frame arrived on. > afiucv_swap_src_dest(skb); > trans_hdr->flags = AF_IUCV_FLAG_SYN | AF_IUCV_FLAG_ACK; > trans_hdr->window = niucv->msglimit; [Severity: High] This isn't a bug introduced by this patch, but does afiucv_hs_callback_syn() leak the child socket nsk on every refused SYN? iucv_sock_alloc() runs before any of the checks. It clears SOCK_ZAPPED and links nsk into iucv_sk_list: net/iucv/af_iucv.c:iucv_sock_alloc() { ... sock_reset_flag(sk, SOCK_ZAPPED); sk->sk_protocol = proto; sk->sk_state = IUCV_OPEN; iucv_sock_link(&iucv_sk_list, sk); ... } Two branches call iucv_sock_kill(nsk): the refusal branch (listener not in IUCV_LISTEN, or sk_acceptq_is_full()) and the dev_queue_xmit() failure branch. iucv_sock_kill() returns at once for a socket that is not zapped: net/iucv/af_iucv.c:iucv_sock_kill() { if (!sock_flag(sk, SOCK_ZAPPED) || sk->sk_socket) return; ... } So nsk is never unlinked or freed. A HiperSockets peer can keep sending SYNs to a bound socket, or to a listener with a full backlog. Each leaked entry also makes the sk_for_each() walk in afiucv_hs_rcv() longer. On the dev_queue_xmit() failure branch, the leaked child has already done dev_hold(skb->dev) above. Can that reference also block unregister_netdevice() of the ingress device forever? The notes below the commit message say Hidayath Khan reported this in August and that his fix is pending. [Severity: High] This is a pre-existing issue, but can the child be published into the listener's accept_q while the listener is being torn down, and then be handled before it is fully set up? The callback holds only bh_lock_sock(sk), while iucv_sock_close() holds lock_sock(sk). iucv_accept_enqueue() makes the child visible before it sets the child's parent: net/iucv/af_iucv.c:iucv_accept_enqueue() { ... spin_lock_irqsave(&par->accept_q_lock, flags); list_add_tail(&iucv_sk(sk)->accept_q, &par->accept_q); spin_unlock_irqrestore(&par->accept_q_lock, flags); iucv_sk(sk)->parent = parent; sk_acceptq_added(parent); } iucv_sock_cleanup_listen() calls iucv_accept_dequeue(parent, NULL). That function walks accept_q without accept_q_lock and, with newsock == NULL, takes any child whatever its state. It then calls iucv_accept_unlink(): net/iucv/af_iucv.c:iucv_accept_unlink() { ... struct iucv_sock *par = iucv_sk(iucv_sk(sk)->parent); spin_lock_irqsave(&par->accept_q_lock, flags); ... } sk_alloc() zeroes the sock, so parent is still NULL if the store in iucv_accept_enqueue() has not happened yet. Is that a NULL pointer dereference? If the parent store has happened, cleanup goes on to iucv_sock_close() and iucv_sock_kill() the child, which drops both references. The callback then still does: iucv_accept_enqueue(sk, nsk); nsk->sk_state = IUCV_CONNECTED; Would the write to nsk->sk_state then be a use-after-free? The commit message leaves the case of a SYN accepted while the listener closes to the socket-locking rework. [ ... ] -- Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261022-b4-disp-b35a9153-v1-0-d74645876195%40proton.me ^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH net 2/2] net/iucv: do not count the SHT frame against the receive window 2026-10-23 2:48 [PATCH net 0/2] net/iucv: two fixes in the HiperSockets receive path Bryam Vargas via B4 Relay 2026-10-23 2:48 ` [PATCH net 1/2] net/iucv: clear hs_dev before dropping its reference Bryam Vargas via B4 Relay @ 2026-10-23 2:48 ` Bryam Vargas via B4 Relay 2026-10-27 2:50 ` netdev-bot+sashiko 1 sibling, 1 reply; 5+ messages in thread From: Bryam Vargas via B4 Relay @ 2026-10-23 2:48 UTC (permalink / raw) To: Eric Dumazet, Jakub Kicinski, David S. Miller, Paolo Abeni, Alexandra Winter, Thorsten Winkler Cc: Nagamani PV, netdev, Hidayath Khan, linux-s390, Simon Horman, linux-kernel From: Bryam Vargas <hexlabsecurity@proton.me> iucv_sock_recvmsg() counts every HiperSockets frame it consumes in msg_recv, the SHT's empty one included, while the peer counts only what sendmsg() sends against the window it was granted; the SHT goes out through iucv_send_ctrl() uncounted, one per shutdown(SHUT_WR) call. The next window update zeroes msg_recv and takes the surplus with it, but recvmsg() sends none while frames wait in backlog_skb_q, and an SHT read in that state pushes msg_recv past msglimit: WARN_ON(1) fires, the socket is closed and the reader gets -EFAULT instead of EOF. A peer that sends past its window reaches the same WARN from the network; with panic_on_warn that is a remote panic. The SHT frame dates from 82492a355fac, the WARN and the close from 800c5eb7b5eb. The SHT reaches recvmsg() as a zero-length frame, like a zero-length data frame, so test the length: count only frames that still carry data when the read consumes them (rlen; a partial SOCK_STREAM read requeues above this point, so each data frame counts once), and close a socket over the window without the WARN; -EFAULT is kept and nothing is logged. A zero-length data frame sets RCV_SHUTDOWN, after which afiucv_hs_callback_rx() drops every later data frame uncounted; the one slot this patch no longer credits for it is lost like the slots of the frames dropped after it. Fixes: 82492a355fac ("af_iucv: add shutdown for HS transport") Fixes: 800c5eb7b5eb ("af_iucv: change net_device handling for HS transport") Reported-by: Sashiko <netdev-bot+sashiko@kernel.org> Closes: https://lore.kernel.org/netdev/179183431327.434549.446348148837850426@kernel.org/ Cc: stable@vger.kernel.org Signed-off-by: Bryam Vargas <hexlabsecurity@proton.me> --- Reproduced on s390x under QEMU TCG (net-next 8df0638138d3 and net f4377bf85887, same result) with an emulated HiperSockets adapter under the real qeth and qdio drivers: the reader has SO_MSGLIMIT 2 and SO_RCVBUF 2304 (the minimum); the peer sends 1 byte twice, which fills the window, then calls shutdown(SHUT_WR) 32 times, one SHT each. The reader starts once everything has arrived; the receive queue took two frames, the rest wait in backlog_skb_q, and with that queue non-empty recvmsg() sends no window update, so the first SHT read counts 3 against a limit of 2: WARN and -EFAULT without the patch, EOF with it. With one SHT instead of 32 the queue drains, the update goes out after the two reads, and both kernels read EOF. Without the patch, the surplus a window update carries for a counted SHT is credit the peer never spent, and it takes the peer's msg_sent below zero; harmless, since a peer that sent an SHT has SEND_SHUTDOWN and its sendmsg() returns -EPIPE. With the patch no SHT is counted, so no update carries that surplus. A HiperSockets socket stays in IUCV_BOUND from its SYN until the SYN|ACK arrives, and SO_MSGLIMIT is accepted in that state, so msglimit can drop below the window the SYN advertised. Hidayath's test for that [1] runs against the rework's backlog budget and passes on net-next, where the update after every read keeps msg_recv low; measured here with SO_RCVBUF at its minimum, so that frames wait in backlog_skb_q, the same conforming peer reaches this check (net-next: WARN and -EFAULT after two of eight messages; with this patch -EFAULT without the WARN). The rework keeps the advertised window in a field of its own for that, and this patch only takes the WARN out of its way. That iucv_sock_shutdown() sends an SHT on every call is a sender-side oddity left alone here; the receiver has to cope with any peer. [1] https://lore.kernel.org/netdev/51a4a40a-f34b-4d41-b049-69aa53f4dcf5@linux.ibm.com/ --- net/iucv/af_iucv.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/net/iucv/af_iucv.c b/net/iucv/af_iucv.c index 501e758c5e68..a5142081bb6a 100644 --- a/net/iucv/af_iucv.c +++ b/net/iucv/af_iucv.c @@ -1317,10 +1317,13 @@ static int iucv_sock_recvmsg(struct socket *sock, struct msghdr *msg, } consume_skb(skb); - if (iucv->transport == AF_IUCV_TRANS_HIPER) { + /* The peer counts only the data it sends against its window; + * the SHT's empty frame is not among them. Over the window, + * close without a WARN: a peer reaches this from the network. + */ + if (iucv->transport == AF_IUCV_TRANS_HIPER && rlen) { atomic_inc(&iucv->msg_recv); if (atomic_read(&iucv->msg_recv) > iucv->msglimit) { - WARN_ON(1); iucv_sock_close(sk); return -EFAULT; } -- 2.56.0 ^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH net 2/2] net/iucv: do not count the SHT frame against the receive window 2026-10-23 2:48 ` [PATCH net 2/2] net/iucv: do not count the SHT frame against the receive window Bryam Vargas via B4 Relay @ 2026-10-27 2:50 ` netdev-bot+sashiko 0 siblings, 0 replies; 5+ messages in thread From: netdev-bot+sashiko @ 2026-10-27 2:50 UTC (permalink / raw) To: hexlabsecurity Cc: edumazet, kuba, davem, pabeni, wintera, twinkler, nagamani, netdev, hidayath, linux-s390, horms, linux-kernel Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider. Critical: 0 · High: 1 (1) · Medium: 0 (3) · Low: 0 (1) If you believe none of the issues are legitimate, please reply to each issue of Medium severity or higher and explain why the AI is wrong. If any of them are legitimate and you plan to respin, please let patchwork know by including "pw-bot: cr" as a separate line at the end of your reply (one such reply per series is enough). - [High] The patch adds `&& rlen` to the counting branch in iucv_sock_recvmsg() (net/iucv/af_iucv.c:1324). Pre-existing issues: - [High] A peer can grow iucv->backlog_skb_q without limit. - [Medium] A conforming HiperSockets peer can still end up in the `msg_recv > msglimit` close in iucv_sock_recvmsg(). - [Medium] On HiperSockets, a zero-length sendmsg() leaks one slot of the peer's window when a receive credit is piggybacked on it. - [Medium] The classic IUCV transport still has a WARN that a peer can trigger. - [Low] Two unsynchronized writes to sk->sk_shutdown can lose RCV_SHUTDOWN. -- net/iucv: do not count the SHT frame against the receive window This patch changes iucv_sock_recvmsg() so that zero-length HiperSockets frames, such as the SHT frame sent by shutdown(SHUT_WR), no longer count against msg_recv. It also removes the WARN_ON(1) from the over-window path, which still closes the socket and returns -EFAULT. Closes: https://lore.kernel.org/netdev/179183431327.434549.446348148837850426@kernel.org/ > A zero-length data frame sets RCV_SHUTDOWN, after which > afiucv_hs_callback_rx() drops every later data frame uncounted; the one > slot this patch no longer credits for it is lost like the slots of the > frames dropped after it. [Severity: Medium] This is a pre-existing issue, but does a zero-length data frame always arrive at the receiver as an empty data frame? iucv_sock_sendmsg() has no zero-length guard, and it counts the send before the frame goes out: atomic_inc(&iucv->msg_sent); err = afiucv_hs_send(&txmsg, sk, skb, 0); With flags == 0, afiucv_hs_send() piggybacks any pending credit: if (flags == AF_IUCV_FLAG_WIN || !flags) { confirm_recv = atomic_xchg(&iucv->msg_recv, 0); phs_hdr->window = confirm_recv; if (confirm_recv) phs_hdr->flags = phs_hdr->flags | AF_IUCV_FLAG_WIN; On the receiving side, afiucv_hs_rcv() then handles it as a pure window update: case (AF_IUCV_FLAG_WIN): err = afiucv_hs_callback_win(sk, skb); if (skb->len == sizeof(struct af_iucv_trans_hdr)) { consume_skb(skb); break; } The frame never reaches afiucv_hs_callback_rx() or recvmsg(). It does not set RCV_SHUTDOWN, the receiver never credits it back, and the sender's msg_sent stays one too high. If zero-length sends repeat while msg_recv is non-zero, they use up msglimit_peer. Eventually sendmsg() blocks in iucv_sock_wait() or returns -EAGAIN. This patch does not touch that path. The sentence quoted above, though, does not hold when a credit is piggybacked. > diff --git a/net/iucv/af_iucv.c b/net/iucv/af_iucv.c > index 501e758c5e68a..a5142081bb6a6 100644 > --- a/net/iucv/af_iucv.c > +++ b/net/iucv/af_iucv.c > @@ -1317,10 +1317,13 @@ static int iucv_sock_recvmsg(struct socket *sock, struct msghdr *msg, > } > > consume_skb(skb); > - if (iucv->transport == AF_IUCV_TRANS_HIPER) { > + /* The peer counts only the data it sends against its window; > + * the SHT's empty frame is not among them. Over the window, > + * close without a WARN: a peer reaches this from the network. > + */ > + if (iucv->transport == AF_IUCV_TRANS_HIPER && rlen) { [Severity: High] Can an empty frame read after the over-window close reach iucv_send_ctrl() while iucv->hs_dev is NULL? When a data frame pushes msg_recv over msglimit, iucv_sock_close() runs and clears hs_dev: iucv_sock_close() ... skb_queue_purge(&iucv->send_skb_q); skb_queue_purge(&iucv->backlog_skb_q); ... WRITE_ONCE(iucv->hs_dev, NULL); It does not purge sk_receive_queue, and it does not reset msg_recv. The next recvmsg() returns -ECONNRESET through sock_error() in __skb_try_recv_datagram(), and that call also clears sk_err. The recvmsg() after it dequeues whatever is still in sk_receive_queue, even though the socket is IUCV_CLOSED. If that frame is empty (an SHT, for example), rlen == 0 and the new gate skips the over-window return. backlog_skb_q is empty because close purged it, and msg_recv is still above msglimit, so this code runs: if (iucv->transport == AF_IUCV_TRANS_HIPER && atomic_read(&iucv->msg_recv) >= iucv->msglimit / 2) send_win = true; ... if (send_win) { err = iucv_send_ctrl(sk, AF_IUCV_FLAG_WIN); iucv_send_ctrl() then computes: blen = sizeof(struct af_iucv_trans_hdr) + LL_RESERVED_SPACE(iucv->hs_dev); This reads hard_header_len and needed_headroom through a NULL pointer, before afiucv_hs_send() gets a chance to check the device. For example, say the receiver has msglimit 2 and a small SO_RCVBUF, and the peer sends d1, d2, d3 and then SHTs: 1. Reading d1 and d2 moves d3 and S1 into sk_receive_queue. 2. Reading d3 closes the socket. 3. The next read returns -ECONNRESET. 4. The read after that consumes S1. Before this patch, every frame read after the close went through the msg_recv > msglimit branch again and returned -EFAULT. send_win could not be reached on this path. On s390 with relocate_lowcore this would oops. On a default boot, blen would be computed from lowcore bytes and afiucv_hs_send() would fail with -ENODEV. recvmsg() would then move the IUCV_CLOSED socket back to IUCV_DISCONN. Could this return early once the socket is closed, or skip the window update after a close? > atomic_inc(&iucv->msg_recv); > if (atomic_read(&iucv->msg_recv) > iucv->msglimit) { > - WARN_ON(1); [Severity: Medium] This is a pre-existing issue, but the classic IUCV transport still has a WARN that a peer can trigger, of the same kind as the one removed here. alloc_iucv_recv_skb() does: WARN_ONCE(!skb, "alloc of recv iucv skb len=%lu failed with errcode=%d\n", len, err); len comes from the peer's message via iucv_msg_length(), called from iucv_callback_rx() or iucv_process_message_q(). iucv_callback_rx() checks it only against sk_rcvbuf, and iucv_process_message_q() does not check it at all. A message larger than MAX_SKB_FRAGS * PAGE_SIZE plus the linear part makes alloc_skb_with_frags() fail with -EMSGSIZE. A GFP_ATOMIC | GFP_DMA allocation failure under memory pressure has the same effect. With panic_on_warn, a peer can turn this into a panic. Should this WARN_ONCE() be removed as well? > iucv_sock_close(sk); > return -EFAULT; [Severity: High] This isn't a bug introduced by this patch, but this read-time check is the only window enforcement on the receive side. Can a peer that ignores its window grow iucv->backlog_skb_q without limit? afiucv_hs_callback_rx() queues frames like this: if (skb_queue_empty(&iucv->backlog_skb_q)) { if (__sock_queue_rcv_skb(sk, skb)) /* handle rcv queue full */ skb_queue_tail(&iucv->backlog_skb_q, skb); } else skb_queue_tail(&iucv_sk(sk)->backlog_skb_q, skb); Once sk_rcvbuf is full, or once the backlog holds any frames, every later data, SHT or WIN-with-payload frame goes onto backlog_skb_q. That queue has no length or memory limit, and these skbs are not charged to sk_rmem_alloc. msg_recv is only incremented in iucv_sock_recvmsg(). If the reader is idle or slow, kernel memory keeps growing until the reader runs or the system runs out of memory. [Severity: Medium] This is a pre-existing issue, but can a conforming peer still reach this close? The new comment says "a peer reaches this from the network". However, the check compares against the current iucv->msglimit, while the peer is bound by the window advertised in the SYN. afiucv_hs_send() fills that window in as: phs_hdr->window = iucv->msglimit; iucv_sock_connect() sends the SYN and then waits in IUCV_BOUND. A blocking connect drops the socket lock inside iucv_sock_wait(), and a non-blocking connect returns -EAGAIN. In either case iucv_sock_setsockopt() still accepts SO_MSGLIMIT in that state: case IUCV_BOUND: if (val < 1 || val > U16_MAX) rc = -EINVAL; else iucv->msglimit = val; So msglimit can be lowered below the window that was already advertised. While frames wait in backlog_skb_q, recvmsg() sends no WIN, so msg_recv climbs past the lowered limit. A peer that stayed inside its window then gets closed: the reader sees -EFAULT and the consumed frame is lost. The notes below the cut line mention this case and defer it to a rework that stores the advertised window separately. Could the commit message and the new comment also say that a conforming peer can still reach this path? > } [Severity: Low] This is a pre-existing issue, but can the RCV_SHUTDOWN set by the empty-frame read be lost? iucv_sock_recvmsg() runs without lock_sock() and does a plain read-modify-write: if (!rlen) sk->sk_shutdown = sk->sk_shutdown | RCV_SHUTDOWN; iucv_send_ctrl() saves and restores the whole mask. It can be called from a repeated shutdown(SHUT_WR) under lock_sock(), or from a concurrent recvmsg() sending WIN: if (sk->sk_shutdown & SEND_SHUTDOWN) { /* controlling flags should be sent anyway */ shutdown = sk->sk_shutdown; sk->sk_shutdown &= RCV_SHUTDOWN; } ... if (shutdown) sk->sk_shutdown = shutdown; If the SHT read sets RCV_SHUTDOWN between the save and the restore, the restore clears it again. The current read still returns 0, but later reads may block or return -EAGAIN instead of EOF. afiucv_hs_callback_rx() would also start accepting data frames again. -- Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261022-b4-disp-b35a9153-v1-0-d74645876195%40proton.me ^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-10-27 2:50 UTC | newest] Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2026-10-23 2:48 [PATCH net 0/2] net/iucv: two fixes in the HiperSockets receive path Bryam Vargas via B4 Relay 2026-10-23 2:48 ` [PATCH net 1/2] net/iucv: clear hs_dev before dropping its reference Bryam Vargas via B4 Relay 2026-10-27 2:50 ` netdev-bot+sashiko 2026-10-23 2:48 ` [PATCH net 2/2] net/iucv: do not count the SHT frame against the receive window Bryam Vargas via B4 Relay 2026-10-27 2:50 ` netdev-bot+sashiko
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®