Netdev List
 help / color / mirror / Atom feed
* [RFC] net/iucv: af_iucv socket locking - accounting, and a staged plan
@ 2026-07-24 22:29 Bryam Vargas
  0 siblings, 0 replies; only message in thread
From: Bryam Vargas @ 2026-07-24 22:29 UTC (permalink / raw)
  To: Alexandra Winter, Thorsten Winkler
  Cc: Hidayath Khan, Heiko Carstens, Vasily Gorbik, Alexander Gordeev,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman, linux-s390, netdev, linux-kernel

Alexandra,

Here is the socket-locking RFC I promised. No patches yet, on purpose: the
accounting turned up one result that changes which fix is right, and I'd
rather agree the direction with you than post a series you have to redirect.

All line numbers are against net-next 0c452fbdf413.


What's there today
------------------

sk->sk_state has 20 assignment sites. One (iucv_sock_alloc:504) runs before the
socket is published on iucv_sk_list at :506, and two set a child socket under
the parent's lock (below). The other 17 are spread across five contexts:

  process, under lock_sock():
    iucv_sock_bind:633,647   iucv_sock_listen:806   iucv_sock_close:411,417,432
    iucv_sock_cleanup_listen:306
  process, under no lock:
    iucv_sock_recvmsg:1338
  iucv tasklet, under bh_lock_sock():
    iucv_callback_txdone:1793   iucv_callback_connrej:1810
  iucv tasklet, under no lock:
    iucv_callback_connack:1708
  NET_RX softirq, under bh_lock_sock():
    afiucv_hs_callback_synack:1941   afiucv_hs_callback_synfin:1961
    afiucv_hs_callback_fin:1983
  qeth TX completion, under no lock:
    afiucv_hs_callback_txnotify:2190,2197
  netdev notifier, under no lock:
    afiucv_netdev_event:2222

Two counts sum it up: sock_owned_by_user() appears zero times in the file, and
there is no socket backlog at all - no .backlog_rcv in iucv_proto, no
sk_add_backlog() anywhere. iucv->backlog_skb_q is a private data queue, not the
socket layer backlog. So the machinery your option A refers to isn't being
mis-used here, it's absent.

Child sockets get their state set under the parent's lock: connreq:1696 and
hs_callback_syn:1917 both set nsk->sk_state while holding bh_lock_sock on the
parent, and iucv_sock_alloc:506 already published the child on iucv_sk_list
before its state was set.


Three gaps, each with a herd7 witness
-------------------------------------

I modelled the locking discipline in LKMM rather than argue it in prose. Six
litmus tests, herd7 7.58. Each RED has a GREEN whose only delta is the lock or
the check, plus a CONTROL so the harness is shown to discriminate:

  RED     bh_lock_sock vs lock_sock, no owner check     Flag data-race
  GREEN   ... same, with sock_owned_by_user() + defer   clean
  RED     recvmsg unlocked, softirq HAS the owner check Flag data-race
  GREEN   ... same, with recvmsg under lock_sock        clean
  RED     a writer holding no lock at all               Flag data-race
  CONTROL two bh_lock_sock writers                      clean

The first pair is your sentence from f558120cd709 ("bh_lock_sock() is not
serializing the tasklet context against process context") as a model verdict.
lock_sock() releases sk_lock.slock before its critical section and leaves only
sk_lock.owned set; bh_lock_sock() holds slock through its critical section. The
two critical sections share no lock, so only an explicit sock_owned_by_user()
check can order them.

The third row is the one the plan turns on, so it's spelled out below. The rest
inline or in a follow-up, whichever you prefer.


The result that changes the plan
--------------------------------

Option A as written in f558120cd709 doesn't reach the receive path.

iucv_sock_recvmsg (1237-1362) takes no socket lock - not lock_sock, not
bh_lock_sock - and it writes sk->sk_state at :1338. Since it never sets
sk_lock.owned, a sock_owned_by_user() check added in the softirq always reads
zero and always takes the "process now" branch. The deferral branch is dead
code against recvmsg. That's the third litmus above; since it's the one the
plan turns on, here it is rather than on request. P1 already has the option-A
fix applied:

  C iucv-recvmsg-vs-hs-callback
  {}

  P0(int *sk_state)
  {
        *sk_state = 5;                  /* recvmsg:1338 -- no socket lock held */
  }

  P1(spinlock_t *slock, int *owned, int *sk_state, int *backlog)
  {
        int r0;

        spin_lock(slock);               /* bh_lock_sock */
        r0 = READ_ONCE(*owned);         /* sock_owned_by_user() */
        if (r0 == 0)
                *sk_state = 2;          /* always taken: recvmsg never sets owned */
        else
                WRITE_ONCE(*backlog, 1);        /* the branch that never runs */
        spin_unlock(slock);
  }

  exists (sk_state=2)

  $ cd tools/memory-model && herd7 -conf linux-kernel.cfg iucv-recvmsg.litmus
  Flag data-race

Give P0 the lock_sock() handshake (set owned under slock, write sk_state, clear
it) and the flag goes away, with nothing else changed.

So the owner check plus re-enqueue fixes the state machine against
close/bind/listen/shutdown, but not against the reader -- and the reader is the
path you asked about.

Putting recvmsg under lock_sock isn't a one-liner either. Two things in the
way, both of which I'd rather you ruled on:

  a) recvmsg:1313 calls iucv_sock_close(), which takes lock_sock:401. Taking
     the lock in recvmsg self-deadlocks there unless the close is split into a
     __iucv_sock_close() that assumes the lock.

     That exact call already cost a CVE: 3589d20a666c ("net/iucv: fix locking
     in .getsockopt", CVE-2026-64004), which you reviewed and tested, fixed a
     NULL deref from getsockopt(SO_MSGSIZE) racing the iucv_sock_close() that
     recvmsg invokes at :1313. Locking getsockopt closed it because
     iucv_sock_close() takes the socket lock itself - which is also exactly why
     recvmsg cannot simply take it too.

  b) skb_recv_datagram() doesn't release the socket lock while it waits, so a
     blocking recv would hold it across the sleep and stall backlog processing
     for the duration. __iucv_sock_wait already has the right shape for this
     (release_sock / schedule_timeout / lock_sock), but it changes recvmsg's
     blocking behaviour.


Staged plan
-----------

Why split rather than send one rework: your caveat in f558120cd709 about return
values and "changes to all users of iucv" applies to the tasklet path, because
those handlers run from iucv_tasklet_fn while holding iucv_table_lock and
iucv_handler is shared with monreader, vmlogrdr, smsgiucv and hvc_iucv. It
doesn't apply to the HiperSockets path: afiucv_hs_rcv is a packet_type handler
in NET_RX softirq that owns the skb, so it can defer to the socket backlog with
no core change and no other driver touched.

Stage 1, HiperSockets receive path. Add iucv_proto.backlog_rcv, hoist
bh_lock_sock from the individual afiucv_hs_callback_* into afiucv_hs_rcv, and
defer via sk_add_backlog() when the socket is owned. Put recvmsg under
lock_sock, with the two items above. Entirely inside af_iucv.c, and it's the
part you named.

Stage 2, iucv tasklet path. This is where option A vs option B actually has to
be answered, and where the core and the four other iucv users are affected.
Option B subsumes it but needs iucv_tasklet_fn to stop holding iucv_table_lock
across handler dispatch, or dispatch moved to the existing iucv_work_fn.

Stage 3, the writers holding no lock. No amount of owner-check discipline at
the other sites reaches these; they need a lock first. iucv_callback_connack is
unambiguous - same tasklet as connrej and shutdown, which already take
bh_lock_sock - so it can just do the same. I have that one ready as a
standalone patch if you want it independently.


Questions
---------

Q1. For stage 1, is putting iucv_sock_recvmsg under lock_sock acceptable to
    you, given (b) changes its blocking behaviour? The alternative is to
    promote message_q.lock to the receive-path lock and move sk_state under it,
    which leaves recvmsg's sleep alone but makes message_q.lock cover something
    it doesn't cover today.

Q2. For stage 2, option A or option B? B is more invasive in net/iucv/iucv.c
    but makes Q1 and stage 3 moot. If you and Hidayath would rather own the
    core change, I'll do stage 1 and hand you the analysis for the rest.

Q3. afiucv_hs_callback_txnotify runs in two contexts, which is why I didn't
    just add a lock to it: qeth_tx_poll (NAPI, softirq) and
    qeth_drain_output_queue (process, teardown) both reach it through
    qeth_notify_skbs. Which one should the locking be written for?


Caveats
-------

I have no IBM Z, so none of this is validated on real HiperSockets. herd7
proves the accesses race under LKMM; it says nothing about memory-safety
impact, which would need a KASAN run on hardware. Treat the model results as an
argument about which lock covers which write, not as a reproduction. I'd rely
on you or Hidayath to validate each revision on real HW before anything lands.

One coordination note: Hidayath's "net/af_iucv: fix use-after-free of listen
sock in iucv_callback_connreq()" is still open, and I'm deliberately not
touching that function. The staged plan resolves connreq as a consequence of
the general direction rather than competing with his patch.

Prior discussion, for anyone picking this up cold:
https://lore.kernel.org/all/20260705-b4-disp-fc79c0dc-v1-1-d2cdcb57afa9@proton.me/
https://lore.kernel.org/all/20260707-b4-disp-783fedbb-v1-1-463b9dbda2ea@proton.me/
https://lore.kernel.org/all/20260706084825.6231-1-hidayath@linux.ibm.com/

Thanks,
Bryam


^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2026-07-24 22:29 UTC | newest]

Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-24 22:29 [RFC] net/iucv: af_iucv socket locking - accounting, and a staged plan Bryam Vargas

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox