Subject: [PATCH v2 12/14] unix-socket: add no-chdir option to unix_stream_listen()

From: "Jeff Hostetler via GitGitGadget" <[email-masked]>
Date: Mon, 01 Feb 2021 19:45:45 +0000

From: Jeff Hostetler <[email-masked]>

Calls to `chdir()` are dangerous in a multi-threaded context.  If
`unix_stream_listen()` is given a socket pathname that is too big to
fit in a `sockaddr_un` structure, it will `chdir()` to the parent
directory of the requested socket pathname, create the socket using a
relative pathname, and then `chdir()` back.  This is not thread-safe.

Add `disallow_chdir` flag to `struct unix_sockaddr_context` and change
all callers to pass an initialized context structure.

Teach `unix_sockaddr_init()` to not allow calls to `chdir()` when flag
is set.

Signed-off-by: Jeff Hostetler <[email-masked]>
---
 unix-socket.c | 19 ++++++++++++++++---
 unix-socket.h |  2 ++
 2 files changed, 18 insertions(+), 3 deletions(-)

diff --git a/unix-socket.c b/unix-socket.c
index 8bcef18ea55..9726992f276 100644
--- a/unix-socket.c
+++ b/unix-socket.c
@@ -11,8 +11,15 @@ static int chdir_len(const char *orig, int len)
 
 struct unix_sockaddr_context {
 	char *orig_dir;
+	unsigned int disallow_chdir:1;
 };
 
+#define UNIX_SOCKADDR_CONTEXT_INIT \
+{ \
+	.orig_dir=NULL, \
+	.disallow_chdir=0, \
+}
+
 static void unix_sockaddr_cleanup(struct unix_sockaddr_context *ctx)
 {
 	if (!ctx->orig_dir)
@@ -32,7 +39,11 @@ static int unix_sockaddr_init(struct sockaddr_un *sa, const char *path,
 {
 	int size = strlen(path) + 1;
 
-	ctx->orig_dir = NULL;
+	if (ctx->disallow_chdir && size > sizeof(sa->sun_path)) {
+		errno = ENAMETOOLONG;
+		return -1;
+	}
+
 	if (size > sizeof(sa->sun_path)) {
 		const char *slash = find_last_dir_sep(path);
 		const char *dir;
@@ -67,7 +78,7 @@ int unix_stream_connect(const char *path)
 {
 	int fd, saved_errno;
 	struct sockaddr_un sa;
-	struct unix_sockaddr_context ctx;
+	struct unix_sockaddr_context ctx = UNIX_SOCKADDR_CONTEXT_INIT;
 
 	if (unix_sockaddr_init(&sa, path, &ctx) < 0)
 		return -1;
@@ -96,7 +107,9 @@ int unix_stream_listen(const char *path,
 	int bind_successful = 0;
 	int backlog;
 	struct sockaddr_un sa;
-	struct unix_sockaddr_context ctx;
+	struct unix_sockaddr_context ctx = UNIX_SOCKADDR_CONTEXT_INIT;
+
+	ctx.disallow_chdir = opts->disallow_chdir;
 
 	if (unix_sockaddr_init(&sa, path, &ctx) < 0)
 		return -1;
diff --git a/unix-socket.h b/unix-socket.h
index c28372ef48e..5b0e8ccef10 100644
--- a/unix-socket.h
+++ b/unix-socket.h
@@ -4,12 +4,14 @@
 struct unix_stream_listen_opts {
 	int listen_backlog_size;
 	unsigned int force_unlink_before_bind:1;
+	unsigned int disallow_chdir:1;
 };
 
 #define UNIX_STREAM_LISTEN_OPTS_INIT \
 { \
 	.listen_backlog_size = 5, \
 	.force_unlink_before_bind = 1, \
+	.disallow_chdir = 0, \
 }
 
 int unix_stream_connect(const char *path);
-- 
gitgitgadget