Re: [PATCH bpf] bpf, sockmap: Fix af_unix null-ptr-deref in proto update
From: Martin KaFai Lau <martin.lau@linux.dev>
Date: 2026-01-30 21:29:52
Also in:
bpf, lkml
On 1/30/26 3:00 AM, Michal Luczaj wrote:
quoted
quoted
Follow-up to discussion at https://lore.kernel.org/netdev/20240610174906.32921-1-kuniyu@amazon.com/ (local).It is a long thread to dig. Please summarize the discussion in the commit message.OK, there we go: The root cause of the null-ptr-deref is that unix_stream_connect() sets sk_state (`WRITE_ONCE(sk->sk_state, TCP_ESTABLISHED)`) _before_ it assigns a peer (`unix_peer(sk) = newsk`). sk_state == TCP_ESTABLISHED makes sock_map_sk_state_allowed() believe that socket is properly set up, which would include having a defined peer. In other words, there's a window when you can call unix_stream_bpf_update_proto() on socket which still has unix_peer(sk) == NULL. My initial idea was to simply move peer assignment _before_ the sk_state update, but the maintainer wasn't interested in changing the unix_stream_connect() hot path. He suggested taking care of it in the sockmap code. My understanding is that users are not supposed to put sockets in a sockmap when said socket is only half-way through connect() call. Hence `return -EINVAL` on a missing peer. Now, if users should be allowed to legally race connect() vs. sockmap update, then I guess we can wait for connect() to "finalize" e.g. by taking the unix_state_lock(), as discussed below.quoted
From looking at this commit message, if the existing lock_sock held by update_elem is not useful for af_unix,Right, the existing lock_sock is not useful. update's lock_sock holds sock::sk_lock, while unix_state_lock() holds unix_sock::lock.
It sounds like lock_sock is the incorrect lock to hold for af_unix. Is taking lock_sock in sock_map doing anything useful for af_unix? Should sock_map hold the unix_state_lock instead of lock_sock? Other than update_elem, do other lock_sock() usages in sock_map have a similar issue for af_unix?
quoted
it is not clear why a new test "!sk_pair" on top of the existing WRITE_ONCE(sk->sk_state...) is a fix."On top"? Just to make sure we're looking at the same thing: above I was trying to show two parallel flows with unix_peer() fetch in thread-0 and WRITE_ONCE(sk->sk_state...) and `unix_peer(sk) = newsk` in thread-1. It fixes the problem because now update_proto won't call sock_hold(NULL).quoted
A minor thing is sock_map_sk_state_allowed doesn't have READ_ONCE(sk->sk_state) for sk_is_stream_unix also.Ok, I'll add this as a separate patch in v2. Along with the !tcp case of sock_map_redirect_allowed()?
sgtm. thanks.
quoted
If unix_stream_connect does not hold lock_sock, can unix_state_lock be used here? lock_sock has already been taken, update_elem should not be the hot path.Yes, it can be used, it was proposed in the old thread. In fact, critical section can be empty; only used to wait for unix_stream_connect() to release the lock, which would guarantee unix_peer(sk) != NULL by then. if (!psock->sk_pair) { + unix_state_lock(sk); + unix_state_unlock(sk); sk_pair = unix_peer(sk); sock_hold(sk_pair);
I don't have a strong opinion on waiting or checking NULL. imo, both are not easy to understand. One is sk_state had already been checked earlier under a lock_sock but still needs to check NULL on unix_peer(). Another one is an empty unix_state_[un]lock(). If taking unix_state_lock, may as well just use the existing unix_peer_get(sk). If its return value cannot (?) be NULL, WARN_ON_ONCE() instead of having a special empty lock/unlock pattern here. If the correct lock (unix_state_lock) was held earlier in update_elem, all these would go away. Also, it is not immediately clear why a non-NULL unix_peer(sk) is safe here. From looking around af_unix.c, is it because the sk refcnt is held earlier in update_elem? For unix_stream, unix_peer(sk) will stay valid until unix_release_sock(sk). Am I reading it correctly?