af_unix: Don't stop recv(MSG_DONTWAIT) if consumed OOB skb is at the head.
authorKuniyuki Iwashima <kuniyu@amazon.com>
Tue, 25 Jun 2024 01:36:38 +0000 (18:36 -0700)
committerPaolo Abeni <pabeni@redhat.com>
Thu, 27 Jun 2024 10:05:01 +0000 (12:05 +0200)
Let's say a socket send()s "hello" with MSG_OOB and "world" without flags,

  >>> from socket import *
  >>> c1, c2 = socketpair(AF_UNIX)
  >>> c1.send(b'hello', MSG_OOB)
  5
  >>> c1.send(b'world')
  5

and its peer recv()s "hell" and "o".

  >>> c2.recv(10)
  b'hell'
  >>> c2.recv(1, MSG_OOB)
  b'o'

Now the consumed OOB skb stays at the head of recvq to return a correct
value for ioctl(SIOCATMARK), which is broken now and fixed by a later
patch.

Then, if peer issues recv() with MSG_DONTWAIT, manage_oob() returns NULL,
so recv() ends up with -EAGAIN.

  >>> c2.setblocking(False)  # This causes -EAGAIN even with available data
  >>> c2.recv(5)
  Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
  BlockingIOError: [Errno 11] Resource temporarily unavailable

However, next recv() will return the following available data, "world".

  >>> c2.recv(5)
  b'world'

When the consumed OOB skb is at the head of the queue, we need to fetch
the next skb to fix the weird behaviour.

Note that the issue does not happen without MSG_DONTWAIT because we can
retry after manage_oob().

This patch also adds a test case that covers the issue.

Without fix:

  #  RUN           msg_oob.no_peek.ex_oob_break ...
  # msg_oob.c:134:ex_oob_break:AF_UNIX :Resource temporarily unavailable
  # msg_oob.c:135:ex_oob_break:Expected:ld
  # msg_oob.c:137:ex_oob_break:Expected ret[0] (-1) == expected_len (2)
  # ex_oob_break: Test terminated by assertion
  #          FAIL  msg_oob.no_peek.ex_oob_break
  not ok 8 msg_oob.no_peek.ex_oob_break

With fix:

  #  RUN           msg_oob.no_peek.ex_oob_break ...
  #            OK  msg_oob.no_peek.ex_oob_break
  ok 8 msg_oob.no_peek.ex_oob_break

Fixes: 314001f0bf92 ("af_unix: Add OOB support")
Signed-off-by: Kuniyuki Iwashima <kuniyu@amazon.com>
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
net/unix/af_unix.c
tools/testing/selftests/net/af_unix/msg_oob.c

index 2eaecf9d78a4957fb53b1310dee892940e7338ef..b0b97f8d0d09851a73cf8d667eb18c74ee56d6b3 100644 (file)
@@ -2614,12 +2614,23 @@ static struct sk_buff *manage_oob(struct sk_buff *skb, struct sock *sk,
        struct unix_sock *u = unix_sk(sk);
 
        if (!unix_skb_len(skb)) {
-               if (!(flags & MSG_PEEK)) {
-                       skb_unlink(skb, &sk->sk_receive_queue);
-                       consume_skb(skb);
+               struct sk_buff *unlinked_skb = NULL;
+
+               spin_lock(&sk->sk_receive_queue.lock);
+
+               if (copied) {
+                       skb = NULL;
+               } else if (flags & MSG_PEEK) {
+                       skb = skb_peek_next(skb, &sk->sk_receive_queue);
+               } else {
+                       unlinked_skb = skb;
+                       skb = skb_peek_next(skb, &sk->sk_receive_queue);
+                       __skb_unlink(unlinked_skb, &sk->sk_receive_queue);
                }
 
-               skb = NULL;
+               spin_unlock(&sk->sk_receive_queue.lock);
+
+               consume_skb(unlinked_skb);
        } else {
                struct sk_buff *unlinked_skb = NULL;
 
index de8d1fcde88377b710ae2de0f2df2f861092b005..b5226ccec3ecef3c3bfe4b324989801087aa148b 100644 (file)
@@ -238,4 +238,15 @@ TEST_F(msg_oob, oob_break_drop)
        recvpair("", -EINVAL, 1, MSG_OOB);
 }
 
+TEST_F(msg_oob, ex_oob_break)
+{
+       sendpair("hello", 5, MSG_OOB);
+       sendpair("wor", 3, MSG_OOB);
+       sendpair("ld", 2, 0);
+
+       recvpair("hellowo", 7, 10, 0);          /* Break at OOB but not at ex-OOB. */
+       recvpair("r", 1, 1, MSG_OOB);
+       recvpair("ld", 2, 2, 0);
+}
+
 TEST_HARNESS_MAIN