Skip to content

Commit

Permalink
linux: support abstract unix socket autobinding (libuv#4499)
Browse files Browse the repository at this point in the history
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 e5f4b79,
   abstract socket names are not zero-terminated
  • Loading branch information
bnoordhuis authored Aug 10, 2024
1 parent a53e787 commit 1eac331
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 15 deletions.
33 changes: 24 additions & 9 deletions src/unix/pipe.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -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;
}

Expand Down
2 changes: 2 additions & 0 deletions test/test-list.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 0 additions & 2 deletions test/test-pipe-bind-error.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
"",
Expand All @@ -213,5 +212,4 @@ TEST_IMPL(pipe_overlong_path) {

MAKE_VALGRIND_HAPPY(uv_default_loop());
return 0;

}
79 changes: 75 additions & 4 deletions test/test-pipe-getsockname.c
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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');
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down

0 comments on commit 1eac331

Please sign in to comment.