From 1eac3310ad3cfb20dda42c1e5c62cf642f310c21 Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Sat, 10 Aug 2024 21:04:09 +0200 Subject: [PATCH] linux: support abstract unix socket autobinding (#4499) Autobinding is a feature that lets the kernel pick a name for the abstract socket, instead of userspace having to provide one. Two bugs that this change exposed are also fixed: 1. strlen(sa.sun_path) can read past the end if the file path is exactly sizeof(sa.sun_path) long (use memchr instead), and 2. don't return UV_ENOBUFS for abstract sockets when the buffer is exactly large enough to hold the result; per commit e5f4b79809, abstract socket names are not zero-terminated --- src/unix/pipe.c | 33 +++++++++++---- test/test-list.h | 2 + test/test-pipe-bind-error.c | 2 - test/test-pipe-getsockname.c | 79 ++++++++++++++++++++++++++++++++++-- 4 files changed, 101 insertions(+), 15 deletions(-) diff --git a/src/unix/pipe.c b/src/unix/pipe.c index 7238cd05d7f..1f9acfac41e 100644 --- a/src/unix/pipe.c +++ b/src/unix/pipe.c @@ -76,8 +76,13 @@ int uv_pipe_bind2(uv_pipe_t* handle, if (name == NULL) return UV_EINVAL; + /* namelen==0 on Linux means autobind the listen socket in the abstract + * socket namespace, see `man 7 unix` for details. + */ +#if !defined(__linux__) if (namelen == 0) return UV_EINVAL; +#endif if (includes_nul(name, namelen)) return UV_EINVAL; @@ -344,8 +349,15 @@ static int uv__pipe_getsockpeername(const uv_pipe_t* handle, uv__peersockfunc func, char* buffer, size_t* size) { +#if defined(__linux__) + static const int is_linux = 1; +#else + static const int is_linux = 0; +#endif struct sockaddr_un sa; socklen_t addrlen; + size_t slop; + char* p; int err; addrlen = sizeof(sa); @@ -359,17 +371,20 @@ static int uv__pipe_getsockpeername(const uv_pipe_t* handle, return err; } -#if defined(__linux__) - if (sa.sun_path[0] == 0) - /* Linux abstract namespace */ + slop = 1; + if (is_linux && sa.sun_path[0] == '\0') { + /* Linux abstract namespace. Not zero-terminated. */ + slop = 0; addrlen -= offsetof(struct sockaddr_un, sun_path); - else -#endif - addrlen = strlen(sa.sun_path); - + } else { + p = memchr(sa.sun_path, '\0', sizeof(sa.sun_path)); + if (p == NULL) + p = ARRAY_END(sa.sun_path); + addrlen = p - sa.sun_path; + } - if ((size_t)addrlen >= *size) { - *size = addrlen + 1; + if ((size_t)addrlen + slop > *size) { + *size = addrlen + slop; return UV_ENOBUFS; } diff --git a/test/test-list.h b/test/test-list.h index 1bc8c9a9b1e..8d5cc49a5de 100644 --- a/test/test-list.h +++ b/test/test-list.h @@ -209,6 +209,7 @@ TEST_DECLARE (pipe_connect_to_file) TEST_DECLARE (pipe_connect_on_prepare) TEST_DECLARE (pipe_getsockname) TEST_DECLARE (pipe_getsockname_abstract) +TEST_DECLARE (pipe_getsockname_autobind) TEST_DECLARE (pipe_getsockname_blocking) TEST_DECLARE (pipe_pending_instances) TEST_DECLARE (pipe_sendmsg) @@ -827,6 +828,7 @@ TASK_LIST_START TEST_ENTRY (pipe_overlong_path) TEST_ENTRY (pipe_getsockname) TEST_ENTRY (pipe_getsockname_abstract) + TEST_ENTRY (pipe_getsockname_autobind) TEST_ENTRY (pipe_getsockname_blocking) TEST_ENTRY (pipe_pending_instances) TEST_ENTRY (pipe_sendmsg) diff --git a/test/test-pipe-bind-error.c b/test/test-pipe-bind-error.c index d79c2e59a25..16164a7ee90 100644 --- a/test/test-pipe-bind-error.c +++ b/test/test-pipe-bind-error.c @@ -202,7 +202,6 @@ TEST_IMPL(pipe_overlong_path) { ASSERT_OK(uv_run(uv_default_loop(), UV_RUN_DEFAULT)); #endif /*if defined(_AIX) && !defined(__PASE__)*/ #endif /* ifndef _WIN32 */ - ASSERT_EQ(UV_EINVAL, uv_pipe_bind(&pipe, "")); uv_pipe_connect(&req, &pipe, "", @@ -213,5 +212,4 @@ TEST_IMPL(pipe_overlong_path) { MAKE_VALGRIND_HAPPY(uv_default_loop()); return 0; - } diff --git a/test/test-pipe-getsockname.c b/test/test-pipe-getsockname.c index d76b6ad4917..34b572343c6 100644 --- a/test/test-pipe-getsockname.c +++ b/test/test-pipe-getsockname.c @@ -78,6 +78,36 @@ static void pipe_client_connect_cb(uv_connect_t* req, int status) { } +#if defined(__linux__) +/* Socket name looks like \0[0-9a-f]{5}, e.g. "\0bad42" */ +static void check_is_autobind_abstract_socket_name(const char *p, size_t len) { + ASSERT_EQ(len, 6); + ASSERT_EQ(*p, '\0'); + + while (*p != '\0') { + ASSERT((*p >= '0' && *p <= '9') || (*p >= 'a' && *p <= 'f')); + p++; + } +} + + +static void pipe_client_autobind_connect_cb(uv_connect_t* req, int status) { + char buf[16]; + size_t len; + + ASSERT_OK(status); + len = 5; + ASSERT_EQ(UV_ENOBUFS, uv_pipe_getpeername(&pipe_client, buf, &len)); + len = 6; + ASSERT_OK(uv_pipe_getpeername(&pipe_client, buf, &len)); + check_is_autobind_abstract_socket_name(buf, len); + pipe_client_connect_cb_called++; + uv_close((uv_handle_t*) &pipe_client, pipe_close_cb); + uv_close((uv_handle_t*) &pipe_server, pipe_close_cb); +} +#endif /* defined(__linux__) */ + + static void pipe_server_connection_cb(uv_stream_t* handle, int status) { /* This function *may* be called, depending on whether accept or the * connection callback is called first. @@ -124,9 +154,11 @@ TEST_IMPL(pipe_getsockname) { ASSERT_STR_EQ(pipe_server.pipe_fname, TEST_PIPENAME); #endif - len = sizeof buf; - r = uv_pipe_getsockname(&pipe_server, buf, &len); - ASSERT_OK(r); + len = sizeof(TEST_PIPENAME) - 1; + ASSERT_EQ(UV_ENOBUFS, uv_pipe_getsockname(&pipe_server, buf, &len)); + + len = sizeof(TEST_PIPENAME); + ASSERT_OK(uv_pipe_getsockname(&pipe_server, buf, &len)); ASSERT_NE(0, buf[len - 1]); ASSERT_EQ(buf[len], '\0'); @@ -160,7 +192,8 @@ TEST_IMPL(pipe_getsockname) { len = sizeof buf; r = uv_pipe_getsockname(&pipe_client, buf, &len); - ASSERT(r == 0 && len == 0); + ASSERT_EQ(r, 0); + ASSERT_EQ(len, 0); len = sizeof buf; r = uv_pipe_getpeername(&pipe_client, buf, &len); @@ -228,6 +261,44 @@ TEST_IMPL(pipe_getsockname_abstract) { #endif } + +TEST_IMPL(pipe_getsockname_autobind) { +#if defined(__linux__) + char buf[256]; + size_t buflen; + + buflen = sizeof(buf); + memset(buf, 0, sizeof(buf)); + ASSERT_OK(uv_pipe_init(uv_default_loop(), &pipe_server, 0)); + ASSERT_OK(uv_pipe_bind2(&pipe_server, "", 0, 0)); + ASSERT_OK(uv_pipe_getsockname(&pipe_server, buf, &buflen)); + check_is_autobind_abstract_socket_name(buf, buflen); + ASSERT_OK(uv_listen((uv_stream_t*) &pipe_server, 0, + pipe_server_connection_cb)); + ASSERT_OK(uv_pipe_init(uv_default_loop(), &pipe_client, 0)); + ASSERT_OK(uv_pipe_connect2(&connect_req, &pipe_client, + buf, + 1 + strlen(&buf[1]), + 0, + pipe_client_autobind_connect_cb)); + ASSERT_OK(uv_run(uv_default_loop(), UV_RUN_DEFAULT)); + ASSERT_EQ(1, pipe_client_connect_cb_called); + ASSERT_EQ(2, pipe_close_cb_called); + MAKE_VALGRIND_HAPPY(uv_default_loop()); + return 0; +#else + /* On other platforms it should simply fail with UV_EINVAL. */ + ASSERT_OK(uv_pipe_init(uv_default_loop(), &pipe_server, 0)); + ASSERT_EQ(UV_EINVAL, uv_pipe_bind2(&pipe_server, "", 0, 0)); + uv_close((uv_handle_t*) &pipe_server, pipe_close_cb); + ASSERT_OK(uv_run(uv_default_loop(), UV_RUN_DEFAULT)); + ASSERT_EQ(1, pipe_close_cb_called); + MAKE_VALGRIND_HAPPY(uv_default_loop()); + return 0; +#endif +} + + TEST_IMPL(pipe_getsockname_blocking) { #ifdef _WIN32 HANDLE readh, writeh;