Subject: Re: [PATCH bpf v2 1/2] bpf: sockmap, af_unix stream sockets need to hold ref for pair sock

From: Jakub Sitnicki <[email-masked]>
Date: Fri, 24 Nov 2023 14:43:24 +0100

On Thu, Nov 23, 2023 at 12:24 PM -08, Cong Wang wrote:
> On Wed, Nov 22, 2023 at 11:24:51AM -0800, John Fastabend wrote:
>> AF_UNIX stream sockets are a paired socket. So sending on one of the pairs
>> will lookup the paired socket as part of the send operation. It is possible
>> however to put just one of the pairs in a BPF map. This currently
>> increments the refcnt on the sock in the sockmap to ensure it is not
>> free'd by the stack before sockmap cleans up its state and stops any
>> skbs being sent/recv'd to that socket.
>> 
>> But we missed a case. If the peer socket is closed it will be
>> free'd by the stack. However, the paired socket can still be
>> referenced from BPF sockmap side because we hold a reference
>> there. Then if we are sending traffic through BPF sockmap to
>> that socket it will try to dereference the free'd pair in its
>> send logic creating a use after free.  And following splat,
>
> Hmm, how could it pass the SOCK_DEAD test in unix_stream_sendmsg()?
>
> 2285                 unix_state_lock(other);
> 2286
> 2287                 if (sock_flag(other, SOCK_DEAD) ||
> 2288                     (other->sk_shutdown & RCV_SHUTDOWN))
> 2289                         goto pipe_err_free;

The quoted UAF happens after unix_state_unlock(other):

  2285                  unix_state_lock(other);
  2286
  2287                  if (sock_flag(other, SOCK_DEAD) ||
  2288                      (other->sk_shutdown & RCV_SHUTDOWN))
  2289                          goto pipe_err_free;
  2290
  2291                  maybe_add_creds(skb, sock, other);
  2292                  scm_stat_add(other, skb);
  2293                  skb_queue_tail(&other->sk_receive_queue, skb);
  2294                  unix_state_unlock(other);
  2295                  other->sk_data_ready(other); <-- UAF

Although, I think I saw it happen at unix_state_lock(other) as well.

We don't hold a ref on other, so we're racing with __sock_release /
unix_release_sock.