Commit 106a6749 authored by Jakub Kicinski's avatar Jakub Kicinski
Browse files

Merge branch 'af_unix-fix-so_peek_off-bug-in-unix_stream_read_generic'

Kuniyuki Iwashima says:

====================
af_unix: Fix SO_PEEK_OFF bug in unix_stream_read_generic().

Miao Wang reported a bug of SO_PEEK_OFF on AF_UNIX SOCK_STREAM socket.

Patch 1 fixes the bug and Patch 2 adds a new selftest to cover the case.
====================

Link: https://patch.msgid.link/20251117174740.3684604-1-kuniyu@google.com


Signed-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parents d47515af e1bb28bf
Loading
Loading
Loading
Loading
+1 −2
Original line number Diff line number Diff line
@@ -2954,6 +2954,7 @@ static int unix_stream_read_generic(struct unix_stream_read_state *state,

	u = unix_sk(sk);

redo:
	/* Lock the socket to prevent queue disordering
	 * while sleeps in memcpy_tomsg
	 */
@@ -2965,7 +2966,6 @@ static int unix_stream_read_generic(struct unix_stream_read_state *state,
		struct sk_buff *skb, *last;
		int chunk;

redo:
		unix_state_lock(sk);
		if (sock_flag(sk, SOCK_DEAD)) {
			err = -ECONNRESET;
@@ -3015,7 +3015,6 @@ static int unix_stream_read_generic(struct unix_stream_read_state *state,
				goto out;
			}

			mutex_lock(&u->iolock);
			goto redo;
unlock:
			unix_state_unlock(sk);
+1 −0
Original line number Diff line number Diff line
@@ -45,6 +45,7 @@ skf_net_off
socket
so_incoming_cpu
so_netns_cookie
so_peek_off
so_txtime
so_rcv_listener
stress_reuseport_listen
+1 −0
Original line number Diff line number Diff line
@@ -6,6 +6,7 @@ TEST_GEN_PROGS := \
	scm_inq \
	scm_pidfd \
	scm_rights \
	so_peek_off \
	unix_connect \
# end of TEST_GEN_PROGS

+162 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0
/* Copyright 2025 Google LLC */

#include <stdlib.h>
#include <unistd.h>

#include <sys/socket.h>

#include "../../kselftest_harness.h"

FIXTURE(so_peek_off)
{
	int fd[2];	/* 0: sender, 1: receiver */
};

FIXTURE_VARIANT(so_peek_off)
{
	int type;
};

FIXTURE_VARIANT_ADD(so_peek_off, stream)
{
	.type = SOCK_STREAM,
};

FIXTURE_VARIANT_ADD(so_peek_off, dgram)
{
	.type = SOCK_DGRAM,
};

FIXTURE_VARIANT_ADD(so_peek_off, seqpacket)
{
	.type = SOCK_SEQPACKET,
};

FIXTURE_SETUP(so_peek_off)
{
	struct timeval timeout = {
		.tv_sec = 0,
		.tv_usec = 3000,
	};
	int ret;

	ret = socketpair(AF_UNIX, variant->type, 0, self->fd);
	ASSERT_EQ(0, ret);

	ret = setsockopt(self->fd[1], SOL_SOCKET, SO_RCVTIMEO_NEW,
			 &timeout, sizeof(timeout));
	ASSERT_EQ(0, ret);

	ret = setsockopt(self->fd[1], SOL_SOCKET, SO_PEEK_OFF,
			 &(int){0}, sizeof(int));
	ASSERT_EQ(0, ret);
}

FIXTURE_TEARDOWN(so_peek_off)
{
	close_range(self->fd[0], self->fd[1], 0);
}

#define sendeq(fd, str, flags)					\
	do {							\
		int bytes, len = strlen(str);			\
								\
		bytes = send(fd, str, len, flags);		\
		ASSERT_EQ(len, bytes);				\
	} while (0)

#define recveq(fd, str, buflen, flags)				\
	do {							\
		char buf[(buflen) + 1] = {};			\
		int bytes;					\
								\
		bytes = recv(fd, buf, buflen, flags);		\
		ASSERT_NE(-1, bytes);				\
		ASSERT_STREQ(str, buf);				\
	} while (0)

#define async							\
	for (pid_t pid = (pid = fork(),				\
			  pid < 0 ?				\
			  __TH_LOG("Failed to start async {}"),	\
			  _metadata->exit_code = KSFT_FAIL,	\
			  __bail(1, _metadata),			\
			  0xdead :				\
			  pid);					\
	     !pid; exit(0))

TEST_F(so_peek_off, single_chunk)
{
	sendeq(self->fd[0], "aaaabbbb", 0);

	recveq(self->fd[1], "aaaa", 4, MSG_PEEK);
	recveq(self->fd[1], "bbbb", 100, MSG_PEEK);
}

TEST_F(so_peek_off, two_chunks)
{
	sendeq(self->fd[0], "aaaa", 0);
	sendeq(self->fd[0], "bbbb", 0);

	recveq(self->fd[1], "aaaa", 4, MSG_PEEK);
	recveq(self->fd[1], "bbbb", 100, MSG_PEEK);
}

TEST_F(so_peek_off, two_chunks_blocking)
{
	async {
		usleep(1000);
		sendeq(self->fd[0], "aaaa", 0);
	}

	recveq(self->fd[1], "aaaa", 4, MSG_PEEK);

	async {
		usleep(1000);
		sendeq(self->fd[0], "bbbb", 0);
	}

	/* goto again; -> goto redo; in unix_stream_read_generic(). */
	recveq(self->fd[1], "bbbb", 100, MSG_PEEK);
}

TEST_F(so_peek_off, two_chunks_overlap)
{
	sendeq(self->fd[0], "aaaa", 0);
	recveq(self->fd[1], "aa", 2, MSG_PEEK);

	sendeq(self->fd[0], "bbbb", 0);

	if (variant->type == SOCK_STREAM) {
		/* SOCK_STREAM tries to fill the buffer. */
		recveq(self->fd[1], "aabb", 4, MSG_PEEK);
		recveq(self->fd[1], "bb", 100, MSG_PEEK);
	} else {
		/* SOCK_DGRAM and SOCK_SEQPACKET returns at the skb boundary. */
		recveq(self->fd[1], "aa", 100, MSG_PEEK);
		recveq(self->fd[1], "bbbb", 100, MSG_PEEK);
	}
}

TEST_F(so_peek_off, two_chunks_overlap_blocking)
{
	async {
		usleep(1000);
		sendeq(self->fd[0], "aaaa", 0);
	}

	recveq(self->fd[1], "aa", 2, MSG_PEEK);

	async {
		usleep(1000);
		sendeq(self->fd[0], "bbbb", 0);
	}

	/* Even SOCK_STREAM does not wait if at least one byte is read. */
	recveq(self->fd[1], "aa", 100, MSG_PEEK);

	recveq(self->fd[1], "bbbb", 100, MSG_PEEK);
}

TEST_HARNESS_MAIN