lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite for Android: free password hash cracker in your pocket
[<prev] [next>] [<thread-prev] [day] [month] [year] [list]
Message-ID: <20250530022303.3189981-1-kuni1840@gmail.com>
Date: Thu, 29 May 2025 19:21:29 -0700
From: Kuniyuki Iwashima <kuni1840@...il.com>
To: brauner@...nel.org
Cc: daniel@...earbox.net,
	david@...dahead.eu,
	edumazet@...gle.com,
	kuba@...nel.org,
	kuniyu@...zon.com,
	lennart@...ttering.net,
	netdev@...r.kernel.org,
	pabeni@...hat.com,
	kuni1840@...il.com
Subject: Re: af-unix: ECONNRESET with fully consumed out-of-band data

From: Christian Brauner <brauner@...nel.org>
Date: Thu, 29 May 2025 12:37:54 +0200
> Hey,
> 
> I've played with out-of-band data on unix sockets and I'm observing strange
> behavior. Below is a minimal reproducer.
> 
> This is sending exactly one byte of out-of-band data from the client to the
> server. The client shuts down the write side aftewards and issues a blocking
> read waiting for the server to sever the connection.
> 
> The server consumes the single byte of out-of-band data sent by the client and
> closes the connection.
> 
> The client should see a zero read as all data has been consumed but instead it
> sees ECONNRESET indicating an unclean shutdown.
> 
> But it's even stranger. If the server issues a regular data read() after
> consuming the single out-of-band byte it will get a zero read indicating EOF as
> the child shutdown the write side. The fun part is that this zero read in the
> parent also makes the child itself see a zero read/EOF after the client severs
> the connection indicating a clean shutdown. Which makes no sense to me
> whatsoever.
> 
> In contrast, when sending exactly one byte of regular data the client sees a
> zero read aka EOF correctly indicating a clean shutdown.
> 
> It seems a bug to me that a single byte of out-of-band data leads to an unclean
> shutdown even though it has been correctly consumed and there's no more data
> left in the socket.

Thanks for the report!

This is definitely a bug.  Even after reading the OOB data, skb holding
the 1 byte must stay in the recv queue to mark the OOB boundary.

So, we need to consider that when close()ing a socket like:

---8<---
diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
index bd507f74e35e..13d5d53c0e53 100644
--- a/net/unix/af_unix.c
+++ b/net/unix/af_unix.c
@@ -654,6 +654,11 @@ static void unix_sock_destructor(struct sock *sk)
 #endif
 }
 
+static unsigned int unix_skb_len(const struct sk_buff *skb)
+{
+	return skb->len - UNIXCB(skb).consumed;
+}
+
 static void unix_release_sock(struct sock *sk, int embrion)
 {
 	struct unix_sock *u = unix_sk(sk);
@@ -681,6 +686,11 @@ static void unix_release_sock(struct sock *sk, int embrion)
 	unix_state_unlock(sk);
 
 #if IS_ENABLED(CONFIG_AF_UNIX_OOB)
+	skb = skb_peek(&sk->sk_receive_queue);
+	if (skb && !unix_skb_len(skb)) {
+		__skb_unlink(skb, &sk->sk_receive_queue);
+		consume_skb(skb);
+	}
 	u->oob_skb = NULL;
 #endif
 
@@ -2569,11 +2579,6 @@ static long unix_stream_data_wait(struct sock *sk, long timeo,
 	return timeo;
 }
 
-static unsigned int unix_skb_len(const struct sk_buff *skb)
-{
-	return skb->len - UNIXCB(skb).consumed;
-}
-
 struct unix_stream_read_state {
 	int (*recv_actor)(struct sk_buff *, int, int,
 			  struct unix_stream_read_state *);
---8<---

I'll post an official patch later.


> 
> Maybe that's expected and there's a reasonable explanation but that's very
> unexpected behavior.
> 
> #include <stdio.h>
> #include <stdlib.h>
> #include <string.h>
> #include <unistd.h>
> #include <sys/socket.h>
> #include <errno.h>
> #include <sys/wait.h>
> 
> int main(void) {
> 	int sv[2];
> 	pid_t pid;
> 	char buf[16];
> 	ssize_t n;
> 
> 	if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) < 0)
> 		_exit(EXIT_FAILURE);
> 
> 	pid = fork();
> 	if (pid < 0)
> 		_exit(EXIT_FAILURE);
> 
> 	if (pid == 0) {
> 		close(sv[0]);
> 
> 		/* Send OOB data to the server. */
> 		printf("child: %zd\n", send(sv[1], "1", 1, MSG_OOB));
> 
> 		/* We're done sending data so shutdown the write side. */
> 		shutdown(sv[1], SHUT_WR);
> 
> 		/* We expect to see EOF here, but we see ECONNRESET instead. */
> 		if (read(sv[1], buf, 1) != 0) {
> 			fprintf(stderr, "%d => %m - Child read did not return EOF\n", errno);
> 			_exit(EXIT_FAILURE);
> 		}
> 
> 		_exit(EXIT_SUCCESS);
> 	}
> 
> 	/* The parent acts as a client here. */
> 	close(sv[1]);
> 
> 	/* Hack: MSG_OOB doesn't block, so we need to make sure the OOB data has arrived. */
> 	sleep(2);
> 	
> 	/* Read the OOB data. */
> 	printf("%zd\n", recv(sv[0], buf, sizeof(buf), MSG_OOB));
> 
> 	/* If you uncomment the following code you can make the child see a zero read/EOF: */
> 	// printf("%zd\n", read(sv[0], buf, sizeof(buf)));
> 
> 	/*
> 	 * Close the connection. The child should see EOF but sees ECONNRESET instead...
> 	 * Try removing MSG_OOB and see how the child sees EOF instead.
> 	 */
> 	close(sv[0]);
> 
> 	waitpid(pid, NULL, 0);
> 	_exit(EXIT_SUCCESS);
> }
> 

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ