af_unix: Stop recv(MSG_PEEK) at consumed OOB skb.
authorKuniyuki Iwashima <kuniyu@amazon.com>
Tue, 25 Jun 2024 01:36:37 +0000 (18:36 -0700)
committerPaolo Abeni <pabeni@redhat.com>
Thu, 27 Jun 2024 10:05:00 +0000 (12:05 +0200)
After consuming OOB data, recv() reading the preceding data must break at
the OOB skb regardless of MSG_PEEK.

Currently, MSG_PEEK does not stop recv() for AF_UNIX, and the behaviour is
not compliant with TCP.

  >>> from socket import *
  >>> c1, c2 = socketpair(AF_UNIX)
  >>> c1.send(b'hello', MSG_OOB)
  5
  >>> c1.send(b'world')
  5
  >>> c2.recv(1, MSG_OOB)
  b'o'
  >>> c2.recv(9, MSG_PEEK)  # This should return b'hell'
  b'hellworld'              # even with enough buffer.

Let's fix it by returning NULL for consumed skb and unlinking it only if
MSG_PEEK is not specified.

This patch also adds test cases that add recv(MSG_PEEK) before each recv().

Without fix:

  #  RUN           msg_oob.peek.oob_ahead_break ...
  # msg_oob.c:134:oob_ahead_break:AF_UNIX :hellworld
  # msg_oob.c:135:oob_ahead_break:Expected:hell
  # msg_oob.c:137:oob_ahead_break:Expected ret[0] (9) == expected_len (4)
  # oob_ahead_break: Test terminated by assertion
  #          FAIL  msg_oob.peek.oob_ahead_break
  not ok 13 msg_oob.peek.oob_ahead_break

With fix:

  #  RUN           msg_oob.peek.oob_ahead_break ...
  #            OK  msg_oob.peek.oob_ahead_break
  ok 13 msg_oob.peek.oob_ahead_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 5e695a9a609c26a0fb75dfbcef768014df382ace..2eaecf9d78a4957fb53b1310dee892940e7338ef 100644 (file)
@@ -2613,9 +2613,12 @@ 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) && !(flags & MSG_PEEK)) {
-               skb_unlink(skb, &sk->sk_receive_queue);
-               consume_skb(skb);
+       if (!unix_skb_len(skb)) {
+               if (!(flags & MSG_PEEK)) {
+                       skb_unlink(skb, &sk->sk_receive_queue);
+                       consume_skb(skb);
+               }
+
                skb = NULL;
        } else {
                struct sk_buff *unlinked_skb = NULL;
index d427d39d080638102414bd2b4f75d42371f4dc7d..de8d1fcde88377b710ae2de0f2df2f861092b005 100644 (file)
@@ -21,6 +21,21 @@ FIXTURE(msg_oob)
                                 */
 };
 
+FIXTURE_VARIANT(msg_oob)
+{
+       bool peek;
+};
+
+FIXTURE_VARIANT_ADD(msg_oob, no_peek)
+{
+       .peek = false,
+};
+
+FIXTURE_VARIANT_ADD(msg_oob, peek)
+{
+       .peek = true
+};
+
 static void create_unix_socketpair(struct __test_metadata *_metadata,
                                   FIXTURE_DATA(msg_oob) *self)
 {
@@ -156,8 +171,14 @@ static void __recvpair(struct __test_metadata *_metadata,
        __sendpair(_metadata, self, buf, len, flags)
 
 #define recvpair(expected_buf, expected_len, buf_len, flags)           \
-       __recvpair(_metadata, self,                                     \
-                  expected_buf, expected_len, buf_len, flags)
+       do {                                                            \
+               if (variant->peek)                                      \
+                       __recvpair(_metadata, self,                     \
+                                  expected_buf, expected_len,          \
+                                  buf_len, (flags) | MSG_PEEK);        \
+               __recvpair(_metadata, self,                             \
+                          expected_buf, expected_len, buf_len, flags); \
+       } while (0)
 
 TEST_F(msg_oob, non_oob)
 {