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
This commit is contained in:
parent
a53e7877e4
commit
1eac3310ad
@ -76,8 +76,13 @@ int uv_pipe_bind2(uv_pipe_t* handle,
|
|||||||
if (name == NULL)
|
if (name == NULL)
|
||||||
return UV_EINVAL;
|
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)
|
if (namelen == 0)
|
||||||
return UV_EINVAL;
|
return UV_EINVAL;
|
||||||
|
#endif
|
||||||
|
|
||||||
if (includes_nul(name, namelen))
|
if (includes_nul(name, namelen))
|
||||||
return UV_EINVAL;
|
return UV_EINVAL;
|
||||||
@ -344,8 +349,15 @@ static int uv__pipe_getsockpeername(const uv_pipe_t* handle,
|
|||||||
uv__peersockfunc func,
|
uv__peersockfunc func,
|
||||||
char* buffer,
|
char* buffer,
|
||||||
size_t* size) {
|
size_t* size) {
|
||||||
|
#if defined(__linux__)
|
||||||
|
static const int is_linux = 1;
|
||||||
|
#else
|
||||||
|
static const int is_linux = 0;
|
||||||
|
#endif
|
||||||
struct sockaddr_un sa;
|
struct sockaddr_un sa;
|
||||||
socklen_t addrlen;
|
socklen_t addrlen;
|
||||||
|
size_t slop;
|
||||||
|
char* p;
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
addrlen = sizeof(sa);
|
addrlen = sizeof(sa);
|
||||||
@ -359,17 +371,20 @@ static int uv__pipe_getsockpeername(const uv_pipe_t* handle,
|
|||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(__linux__)
|
slop = 1;
|
||||||
if (sa.sun_path[0] == 0)
|
if (is_linux && sa.sun_path[0] == '\0') {
|
||||||
/* Linux abstract namespace */
|
/* Linux abstract namespace. Not zero-terminated. */
|
||||||
|
slop = 0;
|
||||||
addrlen -= offsetof(struct sockaddr_un, sun_path);
|
addrlen -= offsetof(struct sockaddr_un, sun_path);
|
||||||
else
|
} else {
|
||||||
#endif
|
p = memchr(sa.sun_path, '\0', sizeof(sa.sun_path));
|
||||||
addrlen = strlen(sa.sun_path);
|
if (p == NULL)
|
||||||
|
p = ARRAY_END(sa.sun_path);
|
||||||
|
addrlen = p - sa.sun_path;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((size_t)addrlen + slop > *size) {
|
||||||
if ((size_t)addrlen >= *size) {
|
*size = addrlen + slop;
|
||||||
*size = addrlen + 1;
|
|
||||||
return UV_ENOBUFS;
|
return UV_ENOBUFS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -209,6 +209,7 @@ TEST_DECLARE (pipe_connect_to_file)
|
|||||||
TEST_DECLARE (pipe_connect_on_prepare)
|
TEST_DECLARE (pipe_connect_on_prepare)
|
||||||
TEST_DECLARE (pipe_getsockname)
|
TEST_DECLARE (pipe_getsockname)
|
||||||
TEST_DECLARE (pipe_getsockname_abstract)
|
TEST_DECLARE (pipe_getsockname_abstract)
|
||||||
|
TEST_DECLARE (pipe_getsockname_autobind)
|
||||||
TEST_DECLARE (pipe_getsockname_blocking)
|
TEST_DECLARE (pipe_getsockname_blocking)
|
||||||
TEST_DECLARE (pipe_pending_instances)
|
TEST_DECLARE (pipe_pending_instances)
|
||||||
TEST_DECLARE (pipe_sendmsg)
|
TEST_DECLARE (pipe_sendmsg)
|
||||||
@ -827,6 +828,7 @@ TASK_LIST_START
|
|||||||
TEST_ENTRY (pipe_overlong_path)
|
TEST_ENTRY (pipe_overlong_path)
|
||||||
TEST_ENTRY (pipe_getsockname)
|
TEST_ENTRY (pipe_getsockname)
|
||||||
TEST_ENTRY (pipe_getsockname_abstract)
|
TEST_ENTRY (pipe_getsockname_abstract)
|
||||||
|
TEST_ENTRY (pipe_getsockname_autobind)
|
||||||
TEST_ENTRY (pipe_getsockname_blocking)
|
TEST_ENTRY (pipe_getsockname_blocking)
|
||||||
TEST_ENTRY (pipe_pending_instances)
|
TEST_ENTRY (pipe_pending_instances)
|
||||||
TEST_ENTRY (pipe_sendmsg)
|
TEST_ENTRY (pipe_sendmsg)
|
||||||
|
|||||||
@ -202,7 +202,6 @@ TEST_IMPL(pipe_overlong_path) {
|
|||||||
ASSERT_OK(uv_run(uv_default_loop(), UV_RUN_DEFAULT));
|
ASSERT_OK(uv_run(uv_default_loop(), UV_RUN_DEFAULT));
|
||||||
#endif /*if defined(_AIX) && !defined(__PASE__)*/
|
#endif /*if defined(_AIX) && !defined(__PASE__)*/
|
||||||
#endif /* ifndef _WIN32 */
|
#endif /* ifndef _WIN32 */
|
||||||
ASSERT_EQ(UV_EINVAL, uv_pipe_bind(&pipe, ""));
|
|
||||||
uv_pipe_connect(&req,
|
uv_pipe_connect(&req,
|
||||||
&pipe,
|
&pipe,
|
||||||
"",
|
"",
|
||||||
@ -213,5 +212,4 @@ TEST_IMPL(pipe_overlong_path) {
|
|||||||
|
|
||||||
MAKE_VALGRIND_HAPPY(uv_default_loop());
|
MAKE_VALGRIND_HAPPY(uv_default_loop());
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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) {
|
static void pipe_server_connection_cb(uv_stream_t* handle, int status) {
|
||||||
/* This function *may* be called, depending on whether accept or the
|
/* This function *may* be called, depending on whether accept or the
|
||||||
* connection callback is called first.
|
* connection callback is called first.
|
||||||
@ -124,9 +154,11 @@ TEST_IMPL(pipe_getsockname) {
|
|||||||
ASSERT_STR_EQ(pipe_server.pipe_fname, TEST_PIPENAME);
|
ASSERT_STR_EQ(pipe_server.pipe_fname, TEST_PIPENAME);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
len = sizeof buf;
|
len = sizeof(TEST_PIPENAME) - 1;
|
||||||
r = uv_pipe_getsockname(&pipe_server, buf, &len);
|
ASSERT_EQ(UV_ENOBUFS, uv_pipe_getsockname(&pipe_server, buf, &len));
|
||||||
ASSERT_OK(r);
|
|
||||||
|
len = sizeof(TEST_PIPENAME);
|
||||||
|
ASSERT_OK(uv_pipe_getsockname(&pipe_server, buf, &len));
|
||||||
|
|
||||||
ASSERT_NE(0, buf[len - 1]);
|
ASSERT_NE(0, buf[len - 1]);
|
||||||
ASSERT_EQ(buf[len], '\0');
|
ASSERT_EQ(buf[len], '\0');
|
||||||
@ -160,7 +192,8 @@ TEST_IMPL(pipe_getsockname) {
|
|||||||
|
|
||||||
len = sizeof buf;
|
len = sizeof buf;
|
||||||
r = uv_pipe_getsockname(&pipe_client, buf, &len);
|
r = uv_pipe_getsockname(&pipe_client, buf, &len);
|
||||||
ASSERT(r == 0 && len == 0);
|
ASSERT_EQ(r, 0);
|
||||||
|
ASSERT_EQ(len, 0);
|
||||||
|
|
||||||
len = sizeof buf;
|
len = sizeof buf;
|
||||||
r = uv_pipe_getpeername(&pipe_client, buf, &len);
|
r = uv_pipe_getpeername(&pipe_client, buf, &len);
|
||||||
@ -228,6 +261,44 @@ TEST_IMPL(pipe_getsockname_abstract) {
|
|||||||
#endif
|
#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) {
|
TEST_IMPL(pipe_getsockname_blocking) {
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
HANDLE readh, writeh;
|
HANDLE readh, writeh;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user