net/iucv: serialize the HiperSockets receive path with the socket lock
Subsystems (4)
v2 cover net/iucv: serialize the HiperSockets receive path with the socket lock
v2 of stage 1 of the socket-locking rework [1], answering the reviews of v1 [2]: Jakub's three points [3] (no forward declarations, a trimmed commit message, and the AI review's items answered, here in the changelog), the list's AI review [4] and Hidayath's test rounds on z/VM [5], including his KCSAN report [6].
Changes in v2:
- a SYN|ACK, SYN|FIN or FIN that arrives while a task owns the socket is recorded in the socket, as bits (one SYN reply, the first to arrive, and a FIN), and applied in that order by the owner from release_cb(), not queued: v1's backlog path dropped a pfmemalloc skb, which qeth hands us under memory pressure, and nothing resends a control frame, so one queued there could be lost for good. The backlog still takes data frames in three cases: while the reply to our SYN is pending, behind frames already waiting, and a listener's SYNs, trimmed to their header. v1 charged deferred control frames to a budget; a stray control frame, a repeated SYN from a conforming peer or junk, can no longer push a FIN out of it (AI review)
- the data budget, the msg_recv check in recvmsg() and the window-update threshold read the window advertised in the SYN, kept in a new msglimit_adv, not the live SO_MSGLIMIT: lowering it in IUCV_BOUND no longer loses data (AI review, Hidayath's reproducer)
- a SYN|ACK or SYN|FIN that no socket state accepts is freed in afiucv_hs_rcv(), and a SYN to a socket that does not listen is refused there; a deferred SYN is refused when applied if the listener closed or its accept queue filled meanwhile
- iucv_sock_shutdown() sets sk_shutdown and purges under sk_lock.slock on
HiperSockets: the data frame that bypasses the backlog tests RCV_SHUTDOWN under that lock (Hidayath, KCSAN)
- a reader parked when the peer closed gets EOF, not -ENOTCONN, and poll() reports EPOLLRDHUP: 1/2's wait loop returns EOF on IUCV_DISCONN with nothing left, which also covers the z/VM sever; 2/2, new in v2, sets RCV_SHUTDOWN at the FIN (Hidayath, AI review)
- no forward declaration for afiucv_hs_dispatch(); two remain, for the backlog and release_cb handlers referenced from the proto and iucv_sock_alloc(). They sit in the file's existing block of them, which aa5ec571b93f added to in September; if the IUCV maintainers would rather see the handlers moved below their users, that is fine by me (Jakub)
- the SHT accounting and the hs_dev ordering went to net as fixes of their own [7]; commit message trimmed (Jakub)
Applies on top of [7].
Tested like v1, on s390x under QEMU TCG, net-next 8df0638138d3, KASAN + PROVE_LOCKING + DEBUG_ATOMIC_SLEEP, this time against an emulated HiperSockets adapter under the real qeth and qdio drivers, tx completion included, and an emulated CP for the z/VM transport. Of the 53 default tests bare net-next fails 8 (7 with [7] applied; its 2/2 fixes one), v1 fails one (the parked reader) and this series fails none, with no KASAN or lockdep report. Of 10 kernels each missing
one piece of it, 9 fail a test this series passes; the shutdown spinlock's piece shows only under a probe that widens the window with udelay(), and the one without the filter that frees stray control frames in afiucv_hs_rcv() passes every test, told apart only by the probe's frame count. The opt-in sets (window, strays, shutdown under the probe, z/VM, MTTCG at HZ=100) and the details are below the --- of 1/2.
QEMU is patched too. Hidayath's table, compared with the code, showed that TCG's s390x CPU emulation lacked things this needs; besides the adapter, the QEMU here carries my patches for that, a large set too specific to this setup to go to QEMU as it is. Where this setup and Hidayath's z/VM runs disagree, the hardware is the reference.
Still open, as in v1: afiucv_hs_callback_txnotify() and the z/VM callbacks change sk_state without checking whether a task owns the socket, and so does afiucv_netdev_event(), whose fix Nagamani is holding for this series [8]. backlog_skb_q stays unbounded for a peer that ignores its window, on net-next as here; release_cb() exists now, and Hidayath's proposal to reset the socket on an overrun from it goes on top. The child socket leaked on afiucv_hs_callback_syn()'s refusal paths is pre-existing; Hidayath flagged it in August [9] and his fix for it is pending.
[1] https://lore.kernel.org/all/[email protected]/
[2] https://lore.kernel.org/netdev/[email protected]/
[3] https://lore.kernel.org/netdev/[email protected]/
[4] https://lore.kernel.org/netdev/[email protected]/
[5] https://lore.kernel.org/netdev/[email protected]/
[6] https://lore.kernel.org/netdev/[email protected]/
[7] https://lore.kernel.org/netdev/[email protected]/
[8] https://lore.kernel.org/netdev/[email protected]/
[9] https://lore.kernel.org/netdev/[email protected]/
---
Bryam Vargas (2):
net/iucv: serialize HiperSockets receive with the socket lock net/iucv: return EOF to a reader parked at the HiperSockets peer's FIN
include/net/iucv/af_iucv.h | 5 + net/iucv/af_iucv.c | 564 +++++++++++++++++++++++++++++++++++---------- 2 files changed, 444 insertions(+), 125 deletions(-)
---
base-commit: 8df0638138d3e0344fd1fb36cf2d1ca1cf5028f0
change-id: 20261022-b4-disp-1d26b87a-747c9d4b1953
prerequisite-message-id: <[email protected]>
prerequisite-patch-id: 5ef9b0bd1d5842e07b00218ef6ab3f8dd53431fe
prerequisite-patch-id: ff94c57f1b7a45e7bf1d178b252018fac71c6190
Best regards,
Signature · 1 line
Bryam Vargas <[email protected]>
RFC PATCH v2 1/2 net/iucv: serialize HiperSockets receive with the socket lock
The HiperSockets receive callbacks change sk_state under bh_lock_sock() without checking sock_owned_by_user(), so a FIN or SYN|ACK is applied while a task owns the socket and acts on the state it read. recvmsg() takes no socket lock and sends a due window update in any state, even after the FIN; one that fails keeps its credit.
Take bh_lock_sock() once in afiucv_hs_rcv(). While a task owns the socket, a SYN|ACK, SYN|FIN or FIN is recorded in the socket and applied by the owner from proto->release_cb: nothing resends a control frame, so none may be dropped, as the socket backlog drops a pfmemalloc skb. Data, SHT and WIN frames do not change sk_state, so they go through under sk_lock.slock, reading a state the owner may be changing: a data frame queues or is dropped, a WIN credits the sender, and iucv_sock_destruct() now purges backlog_skb_q for one that queued after close(). They wait in the socket backlog while the reply to our SYN is pending, and behind frames already waiting, counted in frames rather than bytes, up to the window we gave the peer plus one, which a conforming peer does not exceed; the reply is applied before them. A listener's SYNs wait the same way, cut to their header, up to its accept queue plus one; past that, or when the backlog cannot take the skb, they are refused at once, without a child, and so is one the listener cannot take when it is applied: closed, or its accept queue full, meanwhile. A SYN|ACK or SYN|FIN no socket state can use is freed in afiucv_hs_rcv(), a SYN for a socket that does not listen refused there.
The window given to the peer is kept in msglimit_adv: the SYN carries msglimit, and SO_MSGLIMIT may lower msglimit while the reply is awaited. The backlog budget, the receive check in recvmsg() and the window-update threshold read the window advertised, which a conforming peer keeps to. recvmsg() runs under lock_sock() on both transports and sleeps without it; a reader parked when the connection goes sees EOF once nothing is left, as a fresh call does. A due window update goes out only while connected, else the credit is dropped. shutdown() sets RCV_SHUTDOWN and purges under the socket spinlock, where the data path tests it. sendmsg() waits for memory without the lock and fails with EPIPE once sending is shut down. Control frames go out after shutdown(SHUT_WR) without clearing SEND_SHUTDOWN around their allocation; the FIN and the first SHT go out past sk_sndbuf, as qeth keeps sent skbs charged until TX completion.
---
v2:
- control frames that arrive while a task owns the socket (SYN|ACK, SYN|FIN, FIN) are recorded in iucv_sock (hs_pending, the SYN|ACK's
window with it) and applied from iucv_proto.release_cb; the first
reply to our SYN decides, a FIN applies after it. The backlog keeps
data, SHT and WIN frames while the reply to our SYN is pending or
behind frames already waiting, and a listener's SYNs; a SYN that
waits is cut to its header and holds its device for the reply, and
is refused without a child if the listener closed, or its accept
queue filled, meanwhile.
afiucv_hs_backlog_rcv() applies a pending reply before the frame it
runs; a socket closed meanwhile applies nothing (__iucv_sock_close() leaves IUCV_BOUND as it is). The refusal test there repeats the one in afiucv_hs_callback_syn(), which still allocates the child before it tests; moving that allocation, and freeing the child on the refusal
and transmit-failure paths, is the pre-existing leak Hidayath's fix covers, and this patch does not touch that path.
- the rework stays one patch: the lock in afiucv_hs_rcv(), the
record/apply pair and the backlog budget each close a window the
others open; msglimit_adv is the budget's window; recvmsg() under lock_sock() is what makes release_cb() the owner's point.
- iucv_sock_destruct() purges backlog_skb_q: a data frame that races __iucv_sock_close()'s purge under sk_lock.slock is freed with the
socket instead of leaking (pre-existing; the bypass path makes it a question this patch has to answer)
- msglimit_adv records the window advertised in the SYN (on the
listener's child, in the SYN|ACK); the data budget, the msg_recv
check in recvmsg() and the window-update threshold read it. The WRITE_ONCE() on msglimit in setsockopt() is gone with it. (The AI review's item on v1; Hidayath's reproducer, SO_MSGLIMIT lowered in IUCV_BOUND, lost data on v1.)
- afiucv_hs_stray(): unknown flags, and a SYN|ACK or SYN|FIN for a
socket not in IUCV_BOUND, are freed in afiucv_hs_rcv() as NO_SOCKET;
a SYN for a socket that does not listen is refused there without allocating a child
- iucv_sock_shutdown() takes sk_lock.slock around the sk_shutdown set
and the purge on HiperSockets; the z/VM branch is unchanged
- iucv_sock_recv_skb() returns EOF when the socket is IUCV_DISCONN with nothing queued, as the shortcut at the top of recvmsg() does
- afiucv_hs_dispatch() is defined before its callers; the declarations
of afiucv_hs_backlog_rcv() and afiucv_hs_release_cb() stay, with the file's four: iucv_proto and iucv_sock_alloc() point at them, and they dispatch to the SYN handler, which allocates sockets. The old block comment on the no-socket case went with the move; the dispatcher's comment says what a NULL socket means
- the SHT accounting, the WARN_ON on the overrun path and the hs_dev ordering are fixes in net now; the commit message is shorter
v1: https://lore.kernel.org/netdev/[email protected]/
Tested on s390x under QEMU TCG (no Z here), net-next 8df0638138d3, KASAN + PROVE_LOCKING + DEBUG_ATOMIC_SLEEP, as v1 was, with one change in the setup: instead of lo and injected SYN|ACKs, the guest now has two emulated HiperSockets adapters on one LAN under the real qeth and qdio drivers, so every SYN, SYN|ACK, FIN and window update is a frame af_iucv itself sent and received, and tx completion is the adapter's: at once, late (QAOB), or parked while the peer has no receive buffer. The z/VM transport runs against an emulated CP (IUCV CONNECT, SEND, SEVER as the CP Programming Services book describes them). Two vCPUs under -icount for all but one test, MTTCG at HZ=100 for that one. The emulation does what the drivers expect; where they leave a choice, the choice is mine.
The default set is 53 tests. net-next fails 8 of them: data sent after the socket's own SHT by a writer parked on the window, and a writer that waits out SO_SNDTIMEO after that shutdown; -EFAULT instead of EOF when the SHT's empty frame is counted past the window; a SHT and the FIN of a device going down lost to a full send buffer; a window update after the peer's FIN; -ECONNRESET for a stream the socket shut down itself; a reader parked at the FIN woken to -ENOTCONN. v1 fails the last one only. This series fails none, with no KASAN or lockdep report. The opt-in tests: SO_MSGLIMIT lowered in IUCV_BOUND (three forms: before the SYN|ACK, with the reader holding the socket, with the minimum SO_RCVBUF), a stray SYN|ACK and junk ahead of the FIN, a repeated SYN ahead of it, the SHT past the window, the leaked child of a refused SYN, a reader blocked at the FIN and poll() after it, shutdown(SHUT_RD) against a frame in flight under a probe that widens the gap (udelay(50) on both sides), the z/VM group, and two readers at once under MTTCG. With this series: all 8 messages arrive in the three forms (v1 delivers 3 of 8 in two of them; net-next delivers 8 and closes the socket with -EFAULT in the third); the FIN is applied in both stray tests where v1 loses it; EOF for the SHT past the window where net-next gives WARN and -EFAULT; the parked reader gets EOF and poll() reports EPOLLRDHUP, -ENOTCONN and no RDHUP on net-next and v1; 0 reads of data after SHUT_RD in 200 under the probe over three seeds, 17 to 21 without the spinlock; the z/VM sever wakes the sleeping reader with EOF, -ENOTCONN on net-next; net-next fails the two-reader test in all three MTTCG runs, this series passes them. The refused SYN leaks its child on every kernel, as Hidayath reported: 1056 refused leave 1056 sockets behind, and the control that accepts its 17 frees all 17.
10 kernels each remove or weaken one piece of this series. Nine fail a test this series passes: the budget on the live SO_MSGLIMIT loses data (3 of 8); release_cb() applying nothing leaves connect() waiting for a SYN|ACK the socket recorded; the SYN reply applied only after the backlog dispatches data in IUCV_BOUND and loses it; the SHT counted gives -EFAULT where this series gives EOF; shutdown without the spinlock gives the 17 to 21 late reads above, under the probe only; the wait loop without EOF on IUCV_DISCONN gives -ENOTCONN at the z/VM sever; 1/2 without 2/2 gives EOF without RDHUP; the fixes of [3] alone fail net-next's set minus the window refusal; and the kernel that defers nothing (every frame dispatched under the owner, net-next's race) shows data to poll() on a socket a task owns, in the window tests, and three default tests log it as applying where this series defers. One passes every test: the kernel without the stray filter in afiucv_hs_rcv() records a stray control frame and applies it as a no-op on release instead of freeing it, which only the probe sees. Two pre-existing z/VM findings show on every kernel and are read as such.
Not covered: real hardware, KCSAN (your run is the witness for the sk_shutdown race), and the z/VM callbacks' sk_state writes, which this series does not touch. Logs on request.
--- include/net/iucv/af_iucv.h | 5 + net/iucv/af_iucv.c | 560 +++++++++++++++++++++++++++++++++++---------- 2 files changed, 440 insertions(+), 125 deletions(-)
include/net/iucv/af_iucv.h +5 -0
@@ -130,11 +130,16 @@ struct iucv_sock {u8 flags;u16 msglimit;u16 msglimit_peer; +u16 msglimit_adv;/* the window given to the peer */ +u8 hs_pending;/* control frames recorded for the owner */ +u16 hs_pending_window;atomic_t skbs_in_xmit;atomic_t msg_sent;atomic_t msg_recv;atomic_t pendings;int transport; +unsigned int hs_backlog_data;/* frames waiting in sk_backlog */ +unsigned int hs_backlog_syn;void (*sk_txnotify)(struct sock *sk,enum iucv_tx_notify n); };
net/iucv/af_iucv.c +435 -125
780 lines not on this page · raw message
Signature · 1 line
2.56.0
[email protected]
AI reviewRe: [PATCH RFC net-next v2 1/2] net/iucv: serialize HiperSockets receive with the socket lock
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider: - [Medium] net/iucv: stray FIN in IUCV_BOUND recorded in afiucv_hs_record() disconnects - [Medium]…
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Medium] net/iucv: stray FIN in IUCV_BOUND recorded in afiucv_hs_record() disconnects
- [Medium] net/iucv: post-FIN frames bypass deferred FIN in afiucv_hs_rcv_owned()
- [Medium] net/iucv: zero msglimit_adv when SYN|ACK is accepted in IUCV_BOUND before SYN --
commit 96da64b0d53cef8300fe0a502b2a6c996d2ac838
Author: Bryam Vargas <[email protected]>
net/iucv: serialize HiperSockets receive with the socket lock
This commit serializes HiperSockets packet reception with the socket lock by taking bh_lock_sock() in afiucv_hs_rcv(), recording control frames that arrive while a task owns the socket to apply in release_cb, and locking the socket during iucv_sock_recvmsg(). It also tracks the advertised receive window in msglimit_adv and updates shutdown() and sendmsg() handling.
Quoted 4 lines
net/iucv/af_iucv.c +0
-0
[ ... ]
Quoted 52 lines
net/iucv/af_iucv.c +48
-0@@ -2081,6 +2188,240 @@ static int afiucv_hs_callback_rx(struct sock *sk, struct sk_buff *skb)return NET_RX_SUCCESS ; } +/* +* A frame that changes sk_state waits while a task owns the socket. Data, SHT +* and WIN frames do not change it, so they go through: a reader holding the +* lock would otherwise wait for its own window. They wait while the reply to our +* SYN is pending, to be received connected, and never past frames already +* waiting: sk_backlog.len stays set until __release_sock() has run them all. +*/ +static bool afiucv_hs_defer (struct sock *sk) +{ +return (iucv_sk (sk)->hs_pending &AF_IUCV_PEND_SYNREPLY ) || + sk->sk_backlog.len !=0 ; +} + +/* +* A SYN|ACK, SYN|FIN or FIN that arrives while a task owns the socket is +* recorded, not queued: nothing resends one, and the backlog may not keep an +* skb from the pfmemalloc reserves (see sk_add_backlog()). The first reply to +* our SYN decides, as on arrival; a FIN applies after it. Under sk_lock.slock. +*/ +static void afiucv_hs_record (struct sock *sk,struct sk_buff *skb,u8 flags) +{ +struct iucv_sock *iucv =iucv_sk (sk); + +switch (flags) { +case AF_IUCV_FLAG_SYN |AF_IUCV_FLAG_ACK : +if (!(iucv->hs_pending &AF_IUCV_PEND_SYNREPLY )) { + iucv->hs_pending_window =iucv_trans_hdr (skb)->window; + iucv->hs_pending |=AF_IUCV_PEND_SYNACK ; + } +break ; +case AF_IUCV_FLAG_SYN |AF_IUCV_FLAG_FIN : +if (!(iucv->hs_pending &AF_IUCV_PEND_SYNREPLY )) + iucv->hs_pending |=AF_IUCV_PEND_SYNFIN ; +break ; +case AF_IUCV_FLAG_FIN : + iucv->hs_pending |=AF_IUCV_PEND_FIN ; +break ; + } +consume_skb (skb); +} + +/* the owner applies what afiucv_hs_record() kept, under sk_lock.slock */ +static void afiucv_hs_apply (struct sock *sk,u8 mask) +{ +struct iucv_sock *iucv =iucv_sk (sk); +u8 pending = iucv->hs_pending & mask; + + iucv->hs_pending &= ~pending;
net/iucv/af_iucv.c static int afiucv_hs_callback_rx(struct sock *sk, struct sk_buff *skb) · line 2248
Quoted 10 lines
net/iucv/af_iucv.c +10
-0+/* closed meanwhile: __iucv_sock_close() leaves IUCV_BOUND as it is */ +if (sock_flag (sk,SOCK_ZAPPED )) +return ; +if ((pending &AF_IUCV_PEND_SYNACK ) && sk->sk_state ==IUCV_BOUND ) +afiucv_hs_synack (sk, iucv->hs_pending_window); +if ((pending &AF_IUCV_PEND_SYNFIN ) && sk->sk_state ==IUCV_BOUND ) +afiucv_hs_synfin (sk); +if ((pending &AF_IUCV_PEND_FIN ) && sk->sk_state ==IUCV_CONNECTED ) +afiucv_hs_fin (sk); +}
[Severity: Medium]
What happens if a stray or delayed AF_IUCV_FLAG_FIN frame arrives while a task owns the socket in IUCV_BOUND before the peer's SYN|ACK arrives?
When the socket is not owned by a user task, afiucv_hs_callback_fin() checks sk->sk_state == IUCV_CONNECTED on arrival and ignores a FIN in IUCV_BOUND.
When sock_owned_by_user(sk) is true (for example, inside iucv_sock_connect() or iucv_sock_setsockopt() while a SYN is in flight), afiucv_hs_stray() returns false for AF_IUCV_FLAG_FIN regardless of sk->sk_state, and afiucv_hs_rcv_owned() -> afiucv_hs_record() sets:
case AF_IUCV_FLAG_FIN: iucv->hs_pending |= AF_IUCV_PEND_FIN; break;
even when sk->sk_state != IUCV_CONNECTED and
!(iucv->hs_pending & AF_IUCV_PEND_SYNACK).
If the peer's valid SYN|ACK arrives afterwards while the task still holds lock_sock(sk), afiucv_hs_record() sets AF_IUCV_PEND_SYNACK. When release_sock(sk) runs afiucv_hs_release_cb() -> afiucv_hs_apply(), it first applies AF_IUCV_PEND_SYNACK (transitioning sk->sk_state to IUCV_CONNECTED) and then immediately applies the earlier AF_IUCV_PEND_FIN against the new IUCV_CONNECTED state, calling afiucv_hs_fin(sk) and disconnecting the socket with -ECONNREFUSED.
Should afiucv_hs_record() (or afiucv_hs_stray()) ignore AF_IUCV_FLAG_FIN unless sk->sk_state == IUCV_CONNECTED or AF_IUCV_PEND_SYNACK is already set in iucv->hs_pending?
[ ... ]
Quoted 10 lines
net/iucv/af_iucv.c +10
-0+/* +* Frames the dispatcher would only free, and a SYN it would only refuse, do +* not wait for the owner. +*/ +static bool afiucv_hs_stray (struct sock *sk,u8 flags) +{ +switch (flags) { +case 0 : +case AF_IUCV_FLAG_SHT : +case AF_IUCV_FLAG_WIN :
net/iucv/af_iucv.c static int afiucv_hs_callback_rx(struct sock *sk, struct sk_buff *skb) · line 2329
Quoted 10 lines
net/iucv/af_iucv.c +10
-0+case AF_IUCV_FLAG_FIN : +return false; +case AF_IUCV_FLAG_SYN : +return sk->sk_state !=IUCV_LISTEN ; +case AF_IUCV_FLAG_SYN |AF_IUCV_FLAG_ACK : +case AF_IUCV_FLAG_SYN |AF_IUCV_FLAG_FIN : +return sk->sk_state !=IUCV_BOUND ; + } +return true; +}
[Severity: Medium]
Can a HiperSockets socket in IUCV_BOUND accept an incoming SYN|ACK before a SYN has been sent, leaving iucv->msglimit_adv at 0?
In afiucv_hs_send(), iucv->msglimit_adv is only initialized when an AF_IUCV_FLAG_SYN frame is built:
if (flags == AF_IUCV_FLAG_SYN) {
phs_hdr->window = iucv->msglimit;
/* what the peer may send us, whatever msglimit becomes */
iucv->msglimit_adv = iucv->msglimit;
}In iucv_sock_connect(), iucv->dst_user_id and iucv->dst_name are populated on an IUCV_BOUND socket before calling iucv_send_ctrl(sock->sk, AF_IUCV_FLAG_SYN):
memcpy(iucv->dst_user_id, sa->siucv_user_id, 8); memcpy(iucv->dst_name, sa->siucv_name, 8); if (iucv->transport == AF_IUCV_TRANS_HIPER) err = iucv_send_ctrl(sock->sk, AF_IUCV_FLAG_SYN); else err = afiucv_path_connect(sock, addr); if (err) goto done;
If iucv_send_ctrl() fails (-EAGAIN at the sk_sndbuf check or -ENOBUFS at sock_wmalloc()), the socket remains in IUCV_BOUND with dst_user_id and dst_name set and iucv->msglimit_adv == 0 (or right after iucv_sock_bind() before connect() or listen(), where dst_user_id and dst_name are 8 zero bytes).
Because afiucv_hs_stray(), afiucv_hs_callback_synack(), and afiucv_hs_apply() only check sk->sk_state == IUCV_BOUND without checking whether iucv->msglimit_adv != 0, a matching SYN|ACK frame transitions sk->sk_state to IUCV_CONNECTED with msglimit_adv == 0. On the first non-empty data frame, iucv_sock_recvmsg() then hits:
if (iucv->transport == AF_IUCV_TRANS_HIPER && rlen) {
atomic_inc(&iucv->msg_recv);
if (atomic_read(&iucv->msg_recv) > iucv->msglimit_adv) {
__iucv_sock_close(sk);
err = -EFAULT;
goto out_err;
}
}which closes the socket and returns -EFAULT.
[ ... ]
Quoted 36 lines
net/iucv/af_iucv.c +36
-0+/* run by __release_sock() for each frame that waited; @sk is still owned */ +static int afiucv_hs_backlog_rcv (struct sock *sk,struct sk_buff *skb) +{ +struct net_device *dev =NULL ; + +/* the reply to our SYN came before any frame that waited */ +spin_lock_bh (&sk->sk_lock.slock); +afiucv_hs_apply (sk,AF_IUCV_PEND_SYNREPLY ); +spin_unlock_bh (&sk->sk_lock.slock); +if (iucv_trans_hdr (skb)->flags ==AF_IUCV_FLAG_SYN ) { + dev = skb->dev;/* held by afiucv_hs_backlog_add() */ +if (sk->sk_state !=IUCV_LISTEN ||sk_acceptq_is_full (sk)) { +/* closed, or its accept queue filled, while the SYN +* waited: refused as a stray is, without a child +*/ +afiucv_hs_callback_syn (NULL , skb); +dev_put (dev); +return 0 ; + } + } +/* BH off: the callbacks take message_q.lock with spin_lock() */ +local_bh_disable (); +afiucv_hs_dispatch (sk, skb); +local_bh_enable (); +dev_put (dev); +return 0 ; +} + +/* a task owns @sk, under bh_lock_sock(): what each frame does meanwhile */ +static int afiucv_hs_rcv_owned (struct sock *sk,struct sk_buff *skb,u8 flags) +{ +switch (flags) { +case AF_IUCV_FLAG_SYN |AF_IUCV_FLAG_ACK : +case AF_IUCV_FLAG_SYN |AF_IUCV_FLAG_FIN : +case AF_IUCV_FLAG_FIN : +afiucv_hs_record (sk, skb, flags);
net/iucv/af_iucv.c static int afiucv_hs_callback_rx(struct sock *sk, struct sk_buff *skb) · line 2423
Quoted 10 lines
net/iucv/af_iucv.c +10
-0+return NET_RX_SUCCESS ; +case AF_IUCV_FLAG_SYN : +break ; +default : +if (!afiucv_hs_defer (sk)) +return afiucv_hs_dispatch (sk, skb); + } +afiucv_hs_backlog_add (sk, skb, flags); +return NET_RX_SUCCESS ; +}
[Severity: Medium]
When an AF_IUCV_FLAG_FIN frame arrives while a task owns a connected socket, can subsequent data, SHT, and WIN frames bypass the deferred FIN before release_sock(sk) applies it?
When sock_owned_by_user(sk) is true, afiucv_hs_rcv_owned() -> afiucv_hs_record() records AF_IUCV_PEND_FIN in iucv->hs_pending while leaving sk->sk_state == IUCV_CONNECTED until afiucv_hs_release_cb() runs at the end of release_sock(sk).
If a data (flags == 0), AF_IUCV_FLAG_SHT, or AF_IUCV_FLAG_WIN frame arrives after that FIN while the task still owns sk:
1. When afiucv_hs_defer(sk) is false, afiucv_hs_rcv_owned() immediately calls afiucv_hs_dispatch(sk, skb).
2. When afiucv_hs_defer(sk) is true, afiucv_hs_backlog_add() queues the frame to sk_backlog, and __release_sock() runs afiucv_hs_backlog_rcv() (which only applies AF_IUCV_PEND_SYNREPLY) before afiucv_hs_release_cb() applies AF_IUCV_PEND_FIN.
In both cases, afiucv_hs_callback_rx() and afiucv_hs_callback_win() see sk->sk_state == IUCV_CONNECTED and process the post-FIN frames instead of dropping them. Similarly, if the FIN arrives while iucv_sock_recvmsg() holds lock_sock(sk), sk->sk_state is still IUCV_CONNECTED when iucv_sock_recvmsg() checks:
if (send_win && sk->sk_state == IUCV_CONNECTED) {
err = iucv_send_ctrl(sk, AF_IUCV_FLAG_WIN);so a window update is still sent after the peer's FIN.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
Alexandra Winter
maintainerRe: [PATCH RFC net-next v2 1/2] net/iucv: serialize HiperSockets receive with the socket lock
In reply to Bryam VargasQuoted 2 lines · On 23.10.26 05:00, Bryam Vargas via B4 Relay wrote:
From: Bryam Vargas <[email protected]>
Hi Bryam,
thank you very much for this.
To me this is the most urgent of the open fixes. I'd like to focus on getting this upstream. My hope is that the other open issues can then be more easily be written on top of this one. Also it should then be easier to clarify the impact of the remeining issues, right now many of them suffer from the incomplete socket lock.
I may be wrong with this assesment.
To me this patch looks very good right now.
Can we reduce the scope even more?
Can you polish the style (comments, commit message) some more? The design is not trivial, so future readers, editors and debuggers will be thankful for the effort.
For stable team to evaluate, the commit message should include symptoms, conditions and consequences.
Let us know, if you want Hidayath or myself to take on any of this.
Quoted 2 lines
The HiperSockets receive callbacks change
sk_stateunderbh_lock_sock()without checkingsock_owned_by_user(),
what follows is alist of 3(?) problems in current code, may format it as a list?
Quoted 2 lines
so a FIN or SYN|ACK is applied
while a task owns the socket and acts on the state it read.
What are possible consequences? UAF? Can you give 1 example, maybe even with
KASAN(?) output?
Quoted 3 lines
recvmsg()
takes no socket lock and sends a due window update in any state, even after the FIN;
Is that a problem? Or only bad style?
Quoted 2 lines
one that fails keeps its credit.
You mean, when sending out a WIN fails?
What are the consequences? Overrun scenario? But isn't that covered by your other patch [1] ?
Quoted 4 lines
Take
bh_lock_sock()once inafiucv_hs_rcv(). While a task owns the socket, a SYN|ACK, SYN|FIN or FIN is recorded in the socket and applied by the owner fromproto->release_cb: nothing resends a control frame, so none may be dropped, as the socket backlog drops a pfmemalloc skb.
It took me a while to understand this.
I guess you are saying: Don't queue the SYN|ACK, SYN|FIN or FIN, because they must not be dropped, not even unde rmemory pressure?
Quoted 16 lines
Data, SHT and WIN frames do not change
sk_state, so they go through under sk_lock.slock, reading a state the owner may be changing: a data frame queues or is dropped, a WIN credits the sender, andiucv_sock_destruct()now purgesbacklog_skb_qfor one that queued afterclose(). They wait in the socket backlog while the reply to our SYN is pending, and behind frames already waiting, counted in frames rather than bytes, up to the window we gave the peer plus one, which a conforming peer does not exceed; the reply is applied before them. A listener's SYNs wait the same way, cut to their header, up to its accept queue plus one; past that, or when the backlog cannot take the skb, they are refused at once, without a child, and so is one the listener cannot take when it is applied: closed, or its accept queue full, meanwhile. A SYN|ACK or SYN|FIN no socket state can use is freed inafiucv_hs_rcv(), a SYN for a socket that does not listen refused there.
Quoted 14 lines
The window given to the peer is kept in
msglimit_adv: the SYN carries msglimit, andSO_MSGLIMITmay lower msglimit while the reply is awaited. The backlog budget, the receive check inrecvmsg()and the window-update threshold read the window advertised, which a conforming peer keeps to.recvmsg()runs underlock_sock()on both transports and sleeps without it; a reader parked when the connection goes sees EOF once nothing is left, as a fresh call does. A due window update goes out only while connected, else the credit is dropped.shutdown()setsRCV_SHUTDOWNand purges under the socket spinlock, where the data path tests it.sendmsg()waits for memory without the lock and fails with EPIPE once sending is shut down. Control frames go out aftershutdown(SHUT_WR)without clearingSEND_SHUTDOWNaround their allocation; the FIN and the first SHT go out pastsk_sndbuf, as qeth keeps sent skbs charged until TX completion.
I wonder, if you can split the msglimit_adv vs msglimit into a separate patch?
Quoted 16 lines
Suggested-by Alexandra Winter <[email protected]>Signed-off-by Bryam Vargas <[email protected]>---
--- include/net/iucv/af_iucv.h | 5 + net/iucv/af_iucv.c | 560 +++++++++++++++++++++++++++++++++++---------- 2 files changed, 440 insertions(+), 125 deletions(-)include/net/iucv/af_iucv.h +0
-0
include/net/iucv/af_iucv.h struct iucv_sock { · line 133
Quoted 4 lines
include/net/iucv/af_iucv.h +1
-0@@ -130,11 +130,16 @@ struct iucv_sock {u8 flags;u16 msglimit;u16 msglimit_peer; +u16 msglimit_adv;/* the window given to the peer */
^ Can the msglimit_adv be in a separate patch?
Quoted 18 lines
include/net/iucv/af_iucv.h +4
-0+u8 hs_pending;/* control frames recorded for the owner */ +u16 hs_pending_window;atomic_t skbs_in_xmit;atomic_t msg_sent;atomic_t msg_recv;atomic_t pendings;int transport; +unsigned int hs_backlog_data;/* frames waiting in sk_backlog */ +unsigned int hs_backlog_syn;void (*sk_txnotify)(struct sock *sk,enum iucv_tx_notify n); };net/iucv/af_iucv.c +0
-0@@ -39,12 +39,6 @@
[...]
Quoted 32 lines
net/iucv/af_iucv.c +11
-11@@ -374,26 +389,28 @@ static void iucv_sever_path(struct sock *sk, int with_user_data)static int iucv_send_ctrl (struct sock *sk,u8 flags) {struct iucv_sock *iucv =iucv_sk (sk); -int err =0 ;int blen;struct sk_buff *skb; -u8 shutdown =0 ; +bool force; blen =sizeof (struct af_iucv_trans_hdr ) +LL_RESERVED_SPACE (iucv->hs_dev); -if (sk->sk_shutdown &SEND_SHUTDOWN ) { -/* controlling flags should be sent anyway */ - shutdown = sk->sk_shutdown; - sk->sk_shutdown &=RCV_SHUTDOWN ; - } - skb =sock_alloc_send_skb (sk, blen,1 , &err); -if (skb) { -skb_reserve (skb, blen); - err =afiucv_hs_send (NULL , sk, skb, flags); +/* Control frames go out after a shutdown too. The FIN and the first SHT +* end the stream and may exceed sk_sndbuf, like TCP's FIN; at sk_sndbuf +* the others get -EAGAIN and arm SIGIO, as sock_alloc_send_skb() does. +*/ + force = flags ==AF_IUCV_FLAG_FIN || + (flags ==AF_IUCV_FLAG_SHT && + !(sk->sk_shutdown &SEND_SHUTDOWN )); +if (!force &&sk_wmem_alloc_get (sk) >= sk->sk_sndbuf) { +sk_set_bit (SOCKWQ_ASYNC_NOSPACE , sk); +return -EAGAIN ; }
net/iucv/af_iucv.c static void iucv_sever_path(struct sock *sk, int with_user_data) · line 415
Quoted 10 lines
net/iucv/af_iucv.c +5
-3-if (shutdown) - sk->sk_shutdown = shutdown; -return err; + skb =sock_wmalloc (sk, blen,1 , sk->sk_allocation); +if (!skb) +return -ENOBUFS ; +skb_reserve (skb, blen); +return afiucv_hs_send (NULL , sk, skb, flags); }
I wonder whether this should be in a seperate patch.
Is it really related to 'HiperSockets receive with the socket lock'? You already have another patch that is about fixing shutdown [1], maybe add this there?
Quoted 56 lines
net/iucv/af_iucv.c +20
-3/** @@ -514,6 +531,8 @@ static struct sock *iucv_sock_alloc(struct socket *sock, int proto, gfp_t prio, iucv->transport =AF_IUCV_TRANS_HIPER ; sk->sk_destruct = iucv_sock_destruct; +/* not from iucv_proto: sk_alloc() does not copy proto->backlog_rcv */ + sk->sk_backlog_rcv = afiucv_hs_backlog_rcv; sk->sk_sndtimeo =IUCV_CONN_TIMEOUT ;sock_reset_flag (sk,SOCK_ZAPPED );@@ -819,7 +838,7 @@ static int iucv_sock_listen(struct socket *sock, int backlog)if (sock->type !=SOCK_STREAM && sock->type !=SOCK_SEQPACKET ) goto done; - sk->sk_max_ack_backlog = backlog; +WRITE_ONCE (sk->sk_max_ack_backlog, backlog); sk->sk_ack_backlog =0 ; sk->sk_state =IUCV_LISTEN ; err =0 ;@@ -1031,7 +1050,16 @@ static int iucv_sock_sendmsg(struct socket *sock, struct msghdr *msg, } } skb =sock_alloc_send_pskb (sk, headroom + linear, len - linear, - noblock, &err,0 ); +1 , &err,0 ); +if (!skb && err == -EAGAIN && !noblock) { +/* unlocked: over z/VM the room comes back only when the peer +* receives, and that may wait for our own recvmsg() +*/ +release_sock (sk); + skb =sock_alloc_send_pskb (sk, headroom + linear, + len - linear,0 , &err,0 ); +lock_sock (sk); + }if (!skb) goto out;if (headroom)@@ -1045,10 +1073,16 @@ static int iucv_sock_sendmsg(struct socket *sock, struct msghdr *msg,/* wait if outstanding messages for iucv path has reached */ timeo =sock_sndtimeo (sk, noblock); - err =iucv_sock_wait (sk,iucv_below_msglim (sk), timeo); + err =iucv_sock_wait (sk,iucv_below_msglim (sk) || + (sk->sk_shutdown &SEND_SHUTDOWN ), timeo);if (err) goto fail; +/* return -EPIPE if sending has been shut down meanwhile */ +if (sk->sk_shutdown &SEND_SHUTDOWN ) { + err = -EPIPE ; + goto fail; + }/* return -ECONNRESET if the socket is no longer connected */ if (sk->sk_state !=IUCV_CONNECTED ) { err = -ECONNRESET ;
net/iucv/af_iucv.c static void iucv_process_message_q(struct sock *sk) · line 1292
Quoted 7 lines
net/iucv/af_iucv.c +4
-0@@ -1252,6 +1286,56 @@ static void iucv_process_message_q(struct sock *sk) } } +/* +* Like skb_recv_datagram(), but sleep with the socket lock released, so that +* release_sock() runs the backlog and a deferred FIN reaches the reader. +*/
I'd prefer kernel-doc comments and network style (without the first line /* [2])
But no strong feeling.
Quoted 95 lines
net/iucv/af_iucv.c +59
-12+static struct sk_buff *iucv_sock_recv_skb (struct sock *sk,int flags,int *err) +{ +long timeo =sock_rcvtimeo (sk, flags &MSG_DONTWAIT ); +DEFINE_WAIT_FUNC (wait, woken_wake_function); +struct sk_buff *skb; + +add_wait_queue (sk_sleep (sk), &wait); +for (;;) { +/* receive/dequeue next skb: +* the function understands MSG_PEEK and, thus, does not +* dequeue skb, only refcount is increased. +*/ + skb =skb_recv_datagram (sk, flags |MSG_DONTWAIT , err); +if (skb || *err != -EAGAIN ) +break ; +if (sk->sk_shutdown &RCV_SHUTDOWN ) +break ; +if (!timeo) +break ; +if (sk->sk_state !=IUCV_CONNECTED ) { +/* the connection is gone and nothing is left to read: +* EOF, as a fresh call gets at the top of +* iucv_sock_recvmsg() +*/ +if (sk->sk_state ==IUCV_DISCONN && +skb_queue_empty (&iucv_sk (sk)->backlog_skb_q) && +list_empty (&iucv_sk (sk)->message_q.list)) + *err =0 ; +else + *err = -ENOTCONN ; +break ; + } +if (signal_pending (current)) { + *err =sock_intr_errno (timeo); +break ; + } +sk_wait_event (sk, &timeo, + !skb_queue_empty_lockless (&sk->sk_receive_queue) || +READ_ONCE (sk->sk_err) || + (READ_ONCE (sk->sk_shutdown) &RCV_SHUTDOWN ) || + sk->sk_state !=IUCV_CONNECTED , &wait); + } +remove_wait_queue (sk_sleep (sk), &wait); +return skb; +} +static int iucv_sock_recvmsg (struct socket *sock,struct msghdr *msg,size_t len,int flags) {@@ -1263,31 +1347,32 @@ static int iucv_sock_recvmsg(struct socket *sock, struct msghdr *msg,int err =0 ;u32 offset; +lock_sock (sk);if ((sk->sk_state ==IUCV_DISCONN ) &&skb_queue_empty (&iucv->backlog_skb_q) &&skb_queue_empty (&sk->sk_receive_queue) && -list_empty (&iucv->message_q.list)) -return 0 ; +list_empty (&iucv->message_q.list)) { + copied =0 ; + goto out; + } -if (flags & (MSG_OOB )) -return -EOPNOTSUPP ; +if (flags & (MSG_OOB )) { + err = -EOPNOTSUPP ; + goto out_err; + } -/* receive/dequeue next skb: -* the function understands MSG_PEEK and, thus, does not dequeue skb -* only refcount is increased. -*/ - skb =skb_recv_datagram (sk, flags, &err); + skb =iucv_sock_recv_skb (sk, flags, &err);if (!skb) {if (sk->sk_shutdown &RCV_SHUTDOWN ) -return 0 ; -return err; + err =0 ; + goto out_err; } offset =IUCV_SKB_CB (skb)->offset; rlen = skb->len - offset;/* real length of skb */ copied = min_t(unsigned int, rlen, len);if (!rlen) - sk->sk_shutdown = sk->sk_shutdown |RCV_SHUTDOWN ; +WRITE_ONCE (sk->sk_shutdown, sk->sk_shutdown |RCV_SHUTDOWN ); cskb = skb;if (skb_copy_datagram_msg (cskb, offset, msg, copied)) {
net/iucv/af_iucv.c static int iucv_sock_recvmsg(struct socket *sock, struct msghdr *msg, · line 1417
Quoted 2 lines
net/iucv/af_iucv.c +0
-0@@ -1331,9 +1416,10 @@ static int iucv_sock_recvmsg(struct socket *sock, struct msghdr *msg,*/ if (iucv->transport ==AF_IUCV_TRANS_HIPER && rlen) {
This applies only on top of [1]
Can we do this one first? i.e. independently and based on upstream net?
Ah, now I see that you mentioned that in the cover letter. Probably ok, as this is an RFC.
Quoted 24 lines
net/iucv/af_iucv.c +6
-5atomic_inc (&iucv->msg_recv); -if (atomic_read (&iucv->msg_recv) > iucv->msglimit) { -iucv_sock_close (sk); -return -EFAULT ; +if (atomic_read (&iucv->msg_recv) > iucv->msglimit_adv) { +__iucv_sock_close (sk); + err = -EFAULT ; + goto out_err; } }@@ -1355,18 +1441,23 @@ static int iucv_sock_recvmsg(struct socket *sock, struct msghdr *msg,iucv_process_message_q (sk);if (iucv->transport ==AF_IUCV_TRANS_HIPER &&atomic_read (&iucv->msg_recv) >= - iucv->msglimit /2 ) + iucv->msglimit_adv /2 ) send_win = true; }spin_unlock_bh (&iucv->message_q.lock); -if (send_win) { +if (send_win && sk->sk_state ==IUCV_CONNECTED ) { err =iucv_send_ctrl (sk,AF_IUCV_FLAG_WIN );
net/iucv/af_iucv.c static int iucv_sock_recvmsg(struct socket *sock, struct msghdr *msg, · line 1460
Quoted 10 lines
net/iucv/af_iucv.c +5
-0if (err) { sk->sk_state =IUCV_DISCONN ; sk->sk_state_change (sk); } } +/* no window update went out: the peer, gone or going, takes +* no credit. Drop it. +*/ +if (send_win && sk->sk_state !=IUCV_CONNECTED ) +atomic_set (&iucv->msg_recv,0 );
I wonder whether this should be in a seperate patch.
Is it really related to 'HiperSockets receive with the socket lock'?
Quoted 14 lines
net/iucv/af_iucv.c +2
-1} done:@@ -1375,7 +1466,8 @@ static int iucv_sock_recvmsg(struct socket *sock, struct msghdr *msg, copied = rlen;if (flags &MSG_PEEK )skb_unref (skb); - +out: +release_sock (sk);return copied; err_out:
net/iucv/af_iucv.c static int iucv_sock_recvmsg(struct socket *sock, struct msghdr *msg, · line 1482
Quoted 9 lines
net/iucv/af_iucv.c +2
-1@@ -1383,7 +1475,8 @@ static int iucv_sock_recvmsg(struct socket *sock, struct msghdr *msg,skb_queue_head (&sk->sk_receive_queue, skb); elseskb_unref (skb ); - +out_err: +release_sock (sk);return err; }
done,out,err_out,out_err: Not very descriptive label names. Do you have a better idea?
[...]
For these critical, non-trivial functions below, I think kernel-doc comments and network style (without the first line /* [2]) would be even more helpful.
Describe what is context and what is happeing inside the function. Your comments are already really helpful, but to readers new to the subject, they may still be a bit ambiguous.
I am not an expert on comments, nor in socket handling, so take my comments and proposals below with a grain of salt.
Quoted 7 lines
+/*
+ * A frame that changessk_statewaits while a task owns the socket. Data, SHT
+ * and WIN frames do not change it, so they go through: a reader holding the
+ * lock would otherwise wait for its own window. They wait while the reply to our
+ * SYN is pending, to be received connected, and never past frames already
+ * waiting: sk_backlog.len stays set until__release_sock()has run them all.
+ */
^^ This comment sounds important, but I don't get what it is trying to point out. (After reading the code changes, I think I have an idea what it is referring to)
Quoted 52 lines
net/iucv/af_iucv.c +52
-0+static bool afiucv_hs_defer (struct sock *sk) +{ +return (iucv_sk (sk)->hs_pending &AF_IUCV_PEND_SYNREPLY ) || + sk->sk_backlog.len !=0 ; +} + +/* +* A SYN|ACK, SYN|FIN or FIN that arrives while a task owns the socket is +* recorded, not queued: nothing resends one, and the backlog may not keep an +* skb from the pfmemalloc reserves (see sk_add_backlog()). The first reply to +* our SYN decides, as on arrival; a FIN applies after it. Under sk_lock.slock. +*/ +static void afiucv_hs_record (struct sock *sk,struct sk_buff *skb,u8 flags) +{ +struct iucv_sock *iucv =iucv_sk (sk); + +switch (flags) { +case AF_IUCV_FLAG_SYN |AF_IUCV_FLAG_ACK : +if (!(iucv->hs_pending &AF_IUCV_PEND_SYNREPLY )) { + iucv->hs_pending_window =iucv_trans_hdr (skb)->window; + iucv->hs_pending |=AF_IUCV_PEND_SYNACK ; + } +break ; +case AF_IUCV_FLAG_SYN |AF_IUCV_FLAG_FIN : +if (!(iucv->hs_pending &AF_IUCV_PEND_SYNREPLY )) + iucv->hs_pending |=AF_IUCV_PEND_SYNFIN ; +break ; +case AF_IUCV_FLAG_FIN : + iucv->hs_pending |=AF_IUCV_PEND_FIN ; +break ; + } +consume_skb (skb); +} + +/* the owner applies what afiucv_hs_record() kept, under sk_lock.slock */ +static void afiucv_hs_apply (struct sock *sk,u8 mask) +{ +struct iucv_sock *iucv =iucv_sk (sk); +u8 pending = iucv->hs_pending & mask; + + iucv->hs_pending &= ~pending; +/* closed meanwhile: __iucv_sock_close() leaves IUCV_BOUND as it is */ +if (sock_flag (sk,SOCK_ZAPPED )) +return ; +if ((pending &AF_IUCV_PEND_SYNACK ) && sk->sk_state ==IUCV_BOUND ) +afiucv_hs_synack (sk, iucv->hs_pending_window); +if ((pending &AF_IUCV_PEND_SYNFIN ) && sk->sk_state ==IUCV_BOUND ) +afiucv_hs_synfin (sk); +if ((pending &AF_IUCV_PEND_FIN ) && sk->sk_state ==IUCV_CONNECTED ) +afiucv_hs_fin (sk); +} +
net/iucv/af_iucv.c static int afiucv_hs_callback_rx(struct sock *sk, struct sk_buff *skb)
Quoted 10 lines
net/iucv/af_iucv.c +10
-0+/* release_sock(), after the backlog: still the owner, under sk_lock.slock */ +static void afiucv_hs_release_cb (struct sock *sk) +{ +if (iucv_sk (sk)->hs_pending) +afiucv_hs_apply (sk,AF_IUCV_PEND_ALL ); +} + +/* +* Not sk_add_backlog(): its limit is in bytes, and nothing resends a frame it +* drops.
A full sentence may be even clearer:
"Don't use sk_add_backlog() here, because its limit is in bytes..." (?)
Quoted 1 line
Count frames instead.
It ma ybe helpful to mention why we need to count frames: Because msglimit for iucv is defined in frames (?).
Data, SHT and WIN frames get the window we gave
Quoted 230 lines
+ * the peer plus one: before the reply to our SYN, a peer that keeps to it has
+ * at most that many data frames and one SHT waiting, and no WIN to send us. A
+ * listener's SYNs get its accept queue plus the onesk_acceptq_is_full()
+ * admits past it. Both counts restart only once__release_sock()has run
+ * every frame: a flood cannot keep the owner draining. A SYN the backlog does
+ * not take is refused at once; one that waits is cut to its header, which the
+ * reply reuses, and holds its device for it, as the listener may close, or
+ * fill its accept queue, meanwhile.
+ */net/iucv/af_iucv.c +167
-42+static void afiucv_hs_backlog_add (struct sock *sk,struct sk_buff *skb, +u8 flags) +{ +struct iucv_sock *iucv =iucv_sk (sk); +unsigned int *count, limit; + +if (!sk->sk_backlog.len) { + iucv->hs_backlog_data =0 ; + iucv->hs_backlog_syn =0 ; + } +if (flags ==AF_IUCV_FLAG_SYN ) { + count = &iucv->hs_backlog_syn; + limit =READ_ONCE (sk->sk_max_ack_backlog) +1 ; + }else { + count = &iucv->hs_backlog_data; + limit = iucv->msglimit_adv +1 ; + } +if (*count >= limit || + (skb_pfmemalloc (skb) && !sock_flag (sk,SOCK_MEMALLOC ))) + goto refuse; +if (flags ==AF_IUCV_FLAG_SYN ) { +if (pskb_trim (skb,sizeof (struct af_iucv_trans_hdr ))) + goto refuse; +dev_hold (skb->dev); + } +__sk_add_backlog (sk, skb); +/* msglimit 65535 x 64K frames would wrap the int */ + sk->sk_backlog.len =min_t (u32, sk->sk_backlog.len + skb->truesize, +INT_MAX ); + (*count)++; +return ; + +refuse: +if (flags ==AF_IUCV_FLAG_SYN ) { +afiucv_hs_callback_syn (NULL , skb); + }else { +sk_drops_inc (sk); +kfree_skb_reason (skb,SKB_DROP_REASON_SOCKET_BACKLOG ); + } +} + +/* +* Frames the dispatcher would only free, and a SYN it would only refuse, do +* not wait for the owner. +*/ +static bool afiucv_hs_stray (struct sock *sk,u8 flags) +{ +switch (flags) { +case 0 : +case AF_IUCV_FLAG_SHT : +case AF_IUCV_FLAG_WIN : +case AF_IUCV_FLAG_FIN : +return false; +case AF_IUCV_FLAG_SYN : +return sk->sk_state !=IUCV_LISTEN ; +case AF_IUCV_FLAG_SYN |AF_IUCV_FLAG_ACK : +case AF_IUCV_FLAG_SYN |AF_IUCV_FLAG_FIN : +return sk->sk_state !=IUCV_BOUND ; + } +return true; +} + +/* +* @sk is NULL, or locked by the caller: bh_lock_sock() in afiucv_hs_rcv(), +* where the user may own it too, or user-owned in afiucv_hs_backlog_rcv(). +*/ +static int afiucv_hs_dispatch (struct sock *sk,struct sk_buff *skb) +{ +struct af_iucv_trans_hdr *trans_hdr =iucv_trans_hdr (skb); +int err =NET_RX_SUCCESS ; + +switch (trans_hdr->flags) { +case AF_IUCV_FLAG_SYN : +/* connect request */ + err =afiucv_hs_callback_syn (sk, skb); +break ; +case (AF_IUCV_FLAG_SYN |AF_IUCV_FLAG_ACK ): +/* connect request confirmed */ + err =afiucv_hs_callback_synack (sk, skb); +break ; +case (AF_IUCV_FLAG_SYN |AF_IUCV_FLAG_FIN ): +/* connect request refused */ + err =afiucv_hs_callback_synfin (sk, skb); +break ; +case (AF_IUCV_FLAG_FIN ): +/* close request */ + err =afiucv_hs_callback_fin (sk, skb); +break ; +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 ; + } + fallthrough;/* and receive non-zero length data */ +case (AF_IUCV_FLAG_SHT ): +/* shutdown request */ + fallthrough;/* and receive zero length data */ +case 0 : +/* plain data frame */ +IUCV_SKB_CB (skb)->class = trans_hdr->iucv_hdr.class; + err =afiucv_hs_callback_rx (sk, skb); +break ; +default : +kfree_skb (skb); + } +return err; +} + +/* run by __release_sock() for each frame that waited; @sk is still owned */ +static int afiucv_hs_backlog_rcv (struct sock *sk,struct sk_buff *skb) +{ +struct net_device *dev =NULL ; + +/* the reply to our SYN came before any frame that waited */ +spin_lock_bh (&sk->sk_lock.slock); +afiucv_hs_apply (sk,AF_IUCV_PEND_SYNREPLY ); +spin_unlock_bh (&sk->sk_lock.slock); +if (iucv_trans_hdr (skb)->flags ==AF_IUCV_FLAG_SYN ) { + dev = skb->dev;/* held by afiucv_hs_backlog_add() */ +if (sk->sk_state !=IUCV_LISTEN ||sk_acceptq_is_full (sk)) { +/* closed, or its accept queue filled, while the SYN +* waited: refused as a stray is, without a child +*/ +afiucv_hs_callback_syn (NULL , skb); +dev_put (dev); +return 0 ; + } + } +/* BH off: the callbacks take message_q.lock with spin_lock() */ +local_bh_disable (); +afiucv_hs_dispatch (sk, skb); +local_bh_enable (); +dev_put (dev); +return 0 ; +} + +/* a task owns @sk, under bh_lock_sock(): what each frame does meanwhile */ +static int afiucv_hs_rcv_owned (struct sock *sk,struct sk_buff *skb,u8 flags) +{ +switch (flags) { +case AF_IUCV_FLAG_SYN |AF_IUCV_FLAG_ACK : +case AF_IUCV_FLAG_SYN |AF_IUCV_FLAG_FIN : +case AF_IUCV_FLAG_FIN : +afiucv_hs_record (sk, skb, flags); +return NET_RX_SUCCESS ; +case AF_IUCV_FLAG_SYN : +break ; +default : +if (!afiucv_hs_defer (sk)) +return afiucv_hs_dispatch (sk, skb); + } +afiucv_hs_backlog_add (sk, skb, flags); +return NET_RX_SUCCESS ; +} +/* * afiucv_hs_rcv() - base function for arriving data through HiperSockets * transport @@ -2147,53 +2488,22 @@ static int afiucv_hs_rcv(struct sk_buff *skb, struct net_device *dev,if (!iucv) sk =NULL ; -/* no sock -how should we send with no sock -1) send without sock no send rc checking? -2) introduce default sock to handle this cases - -SYN -> send SYN|ACK in good case, send SYN|FIN in bad case -data -> send FIN -SYN|ACK, SYN|FIN, FIN -> no action? */ +if (!sk) +return afiucv_hs_dispatch (NULL , skb); -switch (trans_hdr->flags) { -case AF_IUCV_FLAG_SYN : -/* connect request */ - err =afiucv_hs_callback_syn (sk, skb); -break ; -case (AF_IUCV_FLAG_SYN |AF_IUCV_FLAG_ACK ): -/* connect request confirmed */ - err =afiucv_hs_callback_synack (sk, skb); -break ; -case (AF_IUCV_FLAG_SYN |AF_IUCV_FLAG_FIN ): -/* connect request refused */ - err =afiucv_hs_callback_synfin (sk, skb); -break ; -case (AF_IUCV_FLAG_FIN ): -/* close request */ - err =afiucv_hs_callback_fin (sk, skb); -break ; -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 ; - } - fallthrough;/* and receive non-zero length data */ -case (AF_IUCV_FLAG_SHT ): -/* shutdown request */ - fallthrough;/* and receive zero length data */ -case 0 : -/* plain data frame */ -IUCV_SKB_CB (skb)->class = trans_hdr->iucv_hdr.class; - err =afiucv_hs_callback_rx (sk, skb); -break ; -default : -kfree_skb (skb); +bh_lock_sock (sk); +if (afiucv_hs_stray (sk, trans_hdr->flags)) { +if (trans_hdr->flags ==AF_IUCV_FLAG_SYN ) +afiucv_hs_callback_syn (NULL , skb); +else +kfree_skb_reason (skb,SKB_DROP_REASON_NO_SOCKET ); + }else if (sock_owned_by_user (sk)) { + err =afiucv_hs_rcv_owned (sk, skb, trans_hdr->flags); + }else {
net/iucv/af_iucv.c static int afiucv_hs_rcv(struct sk_buff *skb, struct net_device *dev, · line 2509
Quoted 10 lines
net/iucv/af_iucv.c +3
-3+ err =afiucv_hs_dispatch (sk, skb); } - -if (sk) -sock_put (sk); +bh_unlock_sock (sk); +sock_put (sk);return err; }
I like the new afiucv_hs_rcv() a lot.
[1] [PATCH net v2 2/2] net/iucv: do not count the SHT frame against the receive window
Re: [PATCH RFC net-next v2 1/2] net/iucv: serialize HiperSockets receive with the socket lock
Alexandra,
Quoted 2 lines · Alexandra Winter
To me this is the most urgent of the open fixes. I'd like to focus on getting this upstream.
[...]
Quoted 1 line
Can we reduce the scope even more?
Yes. v3 [1] is on net, with nothing under it, and split so that the lock patch, 5/7, has only the softirq side:
1/7 msglimit_adv, on its own 2/7 sendmsg() fails with -EPIPE when sending is shut down under it 3/7 recvmsg() under lock_sock() 4/7 code moved for 5/7, no functional change 5/7 the HiperSockets rx path under the socket lock 6/7 RCV_SHUTDOWN set under sk_lock.slock 7/7 EOF for a reader parked when the peer closes (was 2/2)
v3 drops the iucv_send_ctrl() rewrite, the window update only while connected and the credit reset. With them out, v3 fails five of my 53 default tests, the way net fails them; the cover letter lists them so nobody reads them as regressions.
Quoted 1 line
Can you polish the style (comments, commit message) some more?
Every function 4/7 and 5/7 add has a kernel-doc header that says who holds what (softirq under bh_lock_sock(), or the owner under sk_lock.slock) and what the function decides; the comments inside are in net style and short.
Quoted 2 lines
For stable team to evaluate, the commit message should include symptoms, conditions and consequences.
Each of the six that fix something says what is seen, under which conditions and what it costs, and has its Fixes: tag. 4/7 only moves code.
Quoted 1 line
Let us know, if you want Hidayath or myself to take on any of this.
One thing, and it's the part I can't do: the run on z/VM. Hidayath's numbers on v1 [2] are what I calibrated my setup against, and v3 moves enough code that I'd like the same run on it before the RFC tag goes:
the scenarios at 120 s each with net as the baseline, the blocked reader and SO_MSGLIMIT in IUCV_BOUND, and the KCSAN build. Here it's QEMU TCG with an emulated adapter and CP: rates, KCSAN and real SMP only come from the hardware. The cover letter says what I expect from each scenario, patch by patch, so a miss is easy to spot.
Quoted 2 lines
what follows is alist of 3(?) problems in current code, may format it as a list?
3/7 lists the three that are recvmsg()'s own. 5/7 has the softirq's two in its first paragraph, and a list for what happens to each kind of frame. The window update and the credit are out.
Quoted 2 lines
What are possible consequences? UAF? Can you give 1 example, maybe even with KASAN(?) output?
No UAF AFAICT, and no KASAN splat: the KASAN runs here and Hidayath's are clean on net-next for these paths. What's measured is wrong behaviour and leaks, not memory corruption. On his guest, 120 s per scenario on net-next: 3917 reads that go backwards with two readers on one socket, the sk_shutdown bit lost in 565 of 7281 cycles, data read after shutdown(SHUT_RD) in 32 of 212, and 29 KCSAN reports with af_iucv on both sides, 19 of them recvmsg() against shutdown().
The example in the commit message of 5/7 is a listener. A SYN that arrives while close() is emptying the accept queue is answered under the closing task, and its child is queued on a socket that is freed a moment later. Nothing can accept or close that child: it is never freed, and since it carries the listener's name, the next bind() to that name fails with -EADDRINUSE. I reproduced it on net with an mdelay() in iucv_sock_cleanup_listen() to hold the window open, which is otherwise a few instructions wide; with 5/7 and the same delay the SYN waits for the close and is refused. KASAN stayed quiet: the child's pointer to the freed listener is never followed.
The one that comes with kernel output is the third item of 3/7, which I found while reviewing v3: a MSG_PEEK read that a second reader overtakes never frees its skb, and iucv_sock_destruct() takes its WARN_ON() on sk_rmem_alloc when the socket goes. On net main:
WARNING: net/iucv/af_iucv.c:292 at iucv_sock_destruct+0xb8/0xe0, CPU#1: init/1 Call Trace: iucv_sock_destruct+0xbc/0xe0 __sk_destruct+0x5a/0x430 iucv_sock_release+0xbc/0xd0 __sock_release+0x74/0x140 sock_close+0x2a/0x40
It's gone with 3/7.
Quoted 5 lines
Quoted 3 lines
recvmsg()
takes no socket lock and sends a due window update in any state, even after the FIN;Is that a problem? Or only bad style?
On its own, close to style: a window update for a peer that has sent its FIN is a frame nobody uses. It's the IUCV_CONNECTED test you suggested in July. v2 had it because the credit reset needed it (next point); v3 leaves that code as net has it. The test is in [3] 2/2, where you asked whether it can be false the next cycle without the socket lock. On top of v3 a FIN can no longer change sk_state under recvmsg() (5/7). afiucv_hs_callback_txnotify() and the netdev notifier still can; the cover letter lists both as open.
Quoted 5 lines
Quoted 1 line
one that fails keeps its credit.
You mean, when sending out a WIN fails?
What are the consequences? Overrun scenario? But isn't that covered by your other patch [1] ?
Yes, when iucv_send_ctrl() has no room in sk_sndbuf for the WIN:recvmsg() sets IUCV_DISCONN and keeps msg_recv. The next frame read is the peer's SHT, counted one past the window: WARN_ON() and -EFAULT where EOF was due. And yes, your [1], which is [3] 2/2 below, covers it: with the SHT not counted, the credit that was kept cannot pass the window. So the reset is gone from v3. On net without [3] that case still ends in the WARN_ON(); one of my tests shows it on v3 as on net.
Quoted 3 lines
It took me a while to understand this.
I guess you are saying: Don't queue the SYN|ACK, SYN|FIN or FIN, because they must not be dropped, not even unde rmemory pressure?
Yes. Nothing resends them, and the socket backlog can refuse an skb under memory pressure. The commit message and the comment on afiucv_hs_record() say so now.
Quoted 2 lines
I wonder, if you can split the
msglimit_advvs msglimit into a separate patch?
1/7. It goes first because the backlog of 5/7 counts against that window; on the live msglimit it would drop data without a trace, which is what Hidayath's SO_MSGLIMIT scenario found on v1. On its own it fixes two things on net, both after SO_MSGLIMIT is changed with the SYN
out: lowered, the socket is closed on a peer that kept to its window (WARN_ON() and -EFAULT); raised far enough, the peer never gets a window update.
Quoted 4 lines
I wonder whether this should be in a seperate patch.
Is it really related to 'HiperSockets receive with the socket lock'? You already have another patch that is about fixing shutdown [1], maybe add this there?
(the iucv_send_ctrl() rewrite) It isn't related to the lock, and it's out of v3. I wouldn't put it in [3] 2/2 either: that one is the receiver not counting an SHT, this is the sender finding no room in sk_sndbuf for its own FIN or SHT. What v3 keeps from that area is 2/7, which is sendmsg() only.
Quoted 2 lines
I'd prefer kernel-doc comments and network style (without the first line /* [2])
Done for everything the series adds.
Quoted 2 lines
This applies only on top of [1]
Can we do this one first? i.e. independently and based on upstream net?
v3 is on net main (8373c4e8863a) with nothing under it. Of [3], 1/2 applies on top of it as it is; 2/2 needs a rebase, since it touches the lines 1/7 changes.
Quoted 2 lines
I wonder whether this should be in a seperate patch.
Is it really related to 'HiperSockets receive with the socket lock'?
(the atomic_set() of msg_recv) No, and it's out; see the credit above.
Quoted 2 lines
done,out,err_out,out_err: Not very descriptive label names. Do you have a better idea?
The two 3/7 adds to recvmsg() are unlock and err_unlock: they release the socket and return. 5/7 has one more, refuse, in afiucv_hs_backlog_add(), for a frame that cannot wait. done and err_out are the file's own; I left them so the diff stays on the locking.
Quoted 2 lines
^^ This comment sounds important, but I don't get what it is trying to point out.
It was wrong. "a reader holding the lock would otherwise wait for its own window" was the reason in an earlier draft, before recvmsg() slept without the lock. The kernel-doc onafiucv_hs_defer() now gives the two cases in which a data, SHT or WIN frame waits: the reply to our SYN is recorded and not applied yet, so the socket is still IUCV_BOUND and would free the data; or frames wait already, and a later one must not overtake them.
Quoted 2 lines
A full sentence may be even clearer:
"Don't usesk_add_backlog()here, because its limit is in bytes..." (?)
[...]
Quoted 2 lines
It ma ybe helpful to mention why we need to count frames: Because msglimit for iucv is defined in frames (?).
Both taken. And yes, that's why: the window is in frames, and sk_add_backlog() counts bytes, the receive queue's included, so it can refuse a frame the peer was entitled to send.
Quoted 1 line
I like the new
afiucv_hs_rcv()a lot.
Its shape is the same in v3. The filter at its top changed: the AI review of v2 [4] found that a FIN reaching a socket still in IUCV_BOUND was recorded and disconnected it once the SYN|ACK arrived, and that frames behind a recorded FIN were still taken. Both repro here on v2 and are fixed in 5/7.
Thanks for the review.
Bryam
[1] https://lore.kernel.org/netdev/[email protected]/
[2] https://lore.kernel.org/netdev/[email protected]/
[3] https://lore.kernel.org/netdev/[email protected]/
[4] https://lore.kernel.org/all/[email protected]/
RFC PATCH v2 2/2 net/iucv: return EOF to a reader parked at the HiperSockets peer's FIN
In reply to Bryam VargasAfter the peer's FIN only sk_state says the receive side is done. Before the previous patch a reader parked at the FIN returned -ENOTCONN (Hidayath Khan: 14151 of 14151 cycles between two z/VM guests); it now returns 0 by the IUCV_DISCONN test, while poll() still reports no EPOLLRDHUP for the peer's close.
Set RCV_SHUTDOWN where the FIN is applied, as TCP does: poll() reports EPOLLRDHUP, and recvmsg() ends the stream by the test it already uses for the peer's SHT.
---
On its own this covers the HiperSockets FIN; the z/VM sever reaches the sleeping reader through 1/2's wait loop. After 1/2 the write is under sk_lock.slock in every path (the owner's release_cb, or the softirq with no owner), where shutdown() writes the same field.
Measured in the emulated setup: poll() on the parked reader reports EPOLLRDHUP at the FIN with this patch, not without it (1/2 alone gives the reader EOF but no RDHUP; net-next and v1 give -ENOTCONN).
--- net/iucv/af_iucv.c | 4 ++++ 1 file changed, 4 insertions(+)
net/iucv/af_iucv.c +4 -0
@@ -2074,6 +2074,10 @@ static void afiucv_hs_synfin(struct sock *sk)static void afiucv_hs_fin (struct sock *sk) { sk->sk_state =IUCV_DISCONN ; +/* as TCP's FIN: a reader asleep here wakes to EOF, not -ENOTCONN, +* and poll() reports EPOLLRDHUP +*/ +WRITE_ONCE (sk->sk_shutdown, sk->sk_shutdown |RCV_SHUTDOWN ); sk->sk_state_change (sk); }
Signature · 1 line
2.56.0
[email protected]
AI reviewRe: [PATCH RFC net-next v2 2/2] net/iucv: return EOF to a reader parked at the HiperSockets peer's FIN
Sashiko has reviewed this patch and found no issues.
Quoted 8 lines
From: Bryam Vargas <[email protected]>
After the peer's FIN only
sk_statesays the receive side is done. Before the previous patch a reader parked at the FIN returned-ENOTCONN(Hidayath Khan: 14151 of 14151 cycles between two z/VM guests); it now returns 0 by theIUCV_DISCONNtest, whilepoll()still reports no EPOLLRDHUP for the peer's close.
Quoted 7 lines
Set
RCV_SHUTDOWNwhere the FIN is applied, as TCP does:poll()reports EPOLLRDHUP, andrecvmsg()ends the stream by the test it already uses for the peer's SHT.Reported-by Hidayath Khan <[email protected]>Signed-off-by Bryam Vargas <[email protected]>
Sashiko has reviewed this patch and found no issues.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=2