diff mbox series

[net,2/2] af_unix: Add GC race reproducer + slow down unix_stream_connect()

Message ID 20240408161336.612064-3-mhal@rbox.co (mailing list archive)
State Changes Requested
Delegated to: Netdev Maintainers
Headers show
Series af_unix: Garbage collector vs connect() race condition | expand

Checks

Context Check Description
netdev/series_format success Posting correctly formatted
netdev/tree_selection success Clearly marked for net
netdev/ynl success Generated files up to date; no warnings/errors; no diff in generated;
netdev/fixes_present success Fixes tag present in non-next series
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit success Errors and warnings before: 952 this patch: 952
netdev/build_tools success Errors and warnings before: 0 this patch: 0
netdev/cc_maintainers warning 5 maintainers not CCed: linux-kselftest@vger.kernel.org dhowells@redhat.com shuah@kernel.org alexander@mihalicyn.com brauner@kernel.org
netdev/build_clang success Errors and warnings before: 954 this patch: 954
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/deprecated_api success None detected
netdev/check_selftest success No net selftest shell script
netdev/verify_fixes success No Fixes tag
netdev/build_allmodconfig_warn success Errors and warnings before: 964 this patch: 964
netdev/checkpatch warning WARNING: added, moved or deleted file(s), does MAINTAINERS need updating?
netdev/build_clang_rust success No Rust files in patch. Skipping build
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/source_inline success Was 0 now: 0

Commit Message

Michal Luczaj April 8, 2024, 3:58 p.m. UTC
Attempt to crash kernel racing unix socket garbage collector.

Signed-off-by: Michal Luczaj <mhal@rbox.co>
---
 net/unix/af_unix.c                            |   2 +
 tools/testing/selftests/net/af_unix/Makefile  |   2 +-
 .../selftests/net/af_unix/gc_vs_connect.c     | 158 ++++++++++++++++++
 3 files changed, 161 insertions(+), 1 deletion(-)
 create mode 100644 tools/testing/selftests/net/af_unix/gc_vs_connect.c
diff mbox series

Patch

diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
index 5b41e2321209..8e56a094dc80 100644
--- a/net/unix/af_unix.c
+++ b/net/unix/af_unix.c
@@ -1636,6 +1636,8 @@  static int unix_stream_connect(struct socket *sock, struct sockaddr *uaddr,
 
 	unix_state_unlock(sk);
 
+	mdelay(1);
+
 	/* take ten and send info to listening sock */
 	spin_lock(&other->sk_receive_queue.lock);
 	__skb_queue_tail(&other->sk_receive_queue, skb);
diff --git a/tools/testing/selftests/net/af_unix/Makefile b/tools/testing/selftests/net/af_unix/Makefile
index 221c387a7d7f..3b12a9290e06 100644
--- a/tools/testing/selftests/net/af_unix/Makefile
+++ b/tools/testing/selftests/net/af_unix/Makefile
@@ -1,4 +1,4 @@ 
 CFLAGS += $(KHDR_INCLUDES)
-TEST_GEN_PROGS := diag_uid test_unix_oob unix_connect scm_pidfd
+TEST_GEN_PROGS := diag_uid test_unix_oob unix_connect scm_pidfd gc_vs_connect
 
 include ../../lib.mk
diff --git a/tools/testing/selftests/net/af_unix/gc_vs_connect.c b/tools/testing/selftests/net/af_unix/gc_vs_connect.c
new file mode 100644
index 000000000000..8b724f1616dd
--- /dev/null
+++ b/tools/testing/selftests/net/af_unix/gc_vs_connect.c
@@ -0,0 +1,158 @@ 
+// SPDX-License-Identifier: GPL-2.0
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <assert.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <pthread.h>
+#include <sys/un.h>
+#include <sys/socket.h>
+
+#define SOCK_TYPE	SOCK_STREAM	/* or SOCK_SEQPACKET */
+
+union {
+	char buf[CMSG_SPACE(sizeof(int))];
+	struct cmsghdr align;
+} cbuf;
+
+struct iovec io = {
+	.iov_base = (char[1]) {0},
+	.iov_len = 1
+};
+
+struct msghdr msg = {
+	.msg_iov = &io,
+	.msg_iovlen = 1,
+	.msg_control = cbuf.buf,
+	.msg_controllen = sizeof(cbuf.buf)
+};
+
+pthread_barrier_t barr;
+struct sockaddr_un saddr;
+int salen, client;
+
+static void barrier(void)
+{
+	int ret = pthread_barrier_wait(&barr);
+
+	assert(!ret || ret == PTHREAD_BARRIER_SERIAL_THREAD);
+}
+
+static int socket_unix(void)
+{
+	int sock = socket(AF_UNIX, SOCK_TYPE, 0);
+
+	assert(sock != -1);
+	return sock;
+}
+
+static int recv_fd(int socket)
+{
+	struct cmsghdr *cmsg;
+	int ret, fd;
+
+	ret = recvmsg(socket, &msg, 0);
+	assert(ret == 1 && !(msg.msg_flags & MSG_CTRUNC));
+
+	cmsg = CMSG_FIRSTHDR(&msg);
+	assert(cmsg);
+	memcpy(&fd, CMSG_DATA(cmsg), sizeof(fd));
+	assert(fd >= 0);
+
+	return fd;
+}
+
+static void send_fd(int socket, int fd)
+{
+	struct cmsghdr *cmsg;
+	int ret;
+
+	cmsg = CMSG_FIRSTHDR(&msg);
+	assert(cmsg);
+	cmsg->cmsg_level = SOL_SOCKET;
+	cmsg->cmsg_type = SCM_RIGHTS;
+	cmsg->cmsg_len = CMSG_LEN(sizeof(fd));
+
+	memcpy(CMSG_DATA(cmsg), &fd, sizeof(fd));
+
+	do {
+		ret = sendmsg(socket, &msg, 0);
+		assert(ret == 1 || (ret == -1 && errno == ENOTCONN));
+	} while (ret != 1);
+}
+
+static void *racer_connect(void *arg)
+{
+	for (;;) {
+		int ret;
+
+		barrier();
+		ret = connect(client, (struct sockaddr *)&saddr, salen);
+		assert(!ret);
+		barrier();
+	}
+
+	return NULL;
+}
+
+static void *racer_gc(void *arg)
+{
+	for (;;)
+		close(socket_unix()); /* trigger GC */
+
+	return NULL;
+}
+
+int main(void)
+{
+	pthread_t thread_conn, thread_gc;
+	int ret, pair[2];
+
+	printf("running\n");
+
+	ret = pthread_barrier_init(&barr, NULL, 2);
+	assert(!ret);
+
+	ret = pthread_create(&thread_conn, NULL, racer_connect, NULL);
+	assert(!ret);
+
+	ret = pthread_create(&thread_gc, NULL, racer_gc, NULL);
+	assert(!ret);
+
+	ret = socketpair(AF_UNIX, SOCK_TYPE, 0, pair);
+	assert(!ret);
+
+	saddr.sun_family = AF_UNIX;
+	salen = sizeof(saddr.sun_family) +
+		sprintf(saddr.sun_path, "%c/unix-gc-%d", '\0', getpid());
+
+	for (;;) {
+		int server, victim;
+
+		server = socket_unix();
+		ret = bind(server, (struct sockaddr *)&saddr, salen);
+		assert(!ret);
+		ret = listen(server, -1);
+		assert(!ret);
+
+		send_fd(pair[0], server);
+		close(server);
+
+		client = socket_unix();
+		victim = socket_unix();
+
+		barrier();
+		send_fd(client, victim);
+		close(victim);
+		barrier();
+
+		server = recv_fd(pair[1]);
+		close(client);
+		close(server);
+	}
+
+	return 0;
+}