Skip to content

Commit 9557213

Browse files
committed
Ensure guarantees about safe closing of poll watchers
Consider the following scenario: uv_poll_init(loop, poll, fd); uv_poll_start(poll, UV_READABLE, cb); // the cb gets invoked etc. uv_poll_stop(poll); close(fd); fd = allocate_new_socket(); // allocate_new_socket() is assigned the same fd by "bad luck" from the OS // some time later: uv_poll_init(loop, otherpoll, fd); uv_poll_start(otherpoll, UV_READABLE, cb); uv_close(poll); // uv__io_stop: Assertion `loop->watchers[w->fd] == w' failed. According to documentation, "however the fd can be safely closed immediately after a call to uv_poll_stop() or uv_close()." Though, in this scenario, we close()'d our file descriptor, and by bad luck we got the same file descriptor again and register a new handle for it and start polling. Previously that would lead to an assertion failure, if we were to properly free the original handle via uv_close(). This commit fixes that by moving the check whether a only a single poll handle is active to uv_poll_start() instead of the stopping routines.
1 parent 18ff13c commit 9557213

File tree

7 files changed

+99
-3
lines changed

7 files changed

+99
-3
lines changed

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,7 @@ if(LIBUV_BUILD_TESTS)
360360
test/test-poll-close-doesnt-corrupt-stack.c
361361
test/test-poll-close.c
362362
test/test-poll-closesocket.c
363+
test/test-poll-multiple-handles.c
363364
test/test-poll-oob.c
364365
test/test-poll.c
365366
test/test-process-priority.c

Makefile.am

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,7 @@ test_run_tests_SOURCES = test/blackhole-server.c \
232232
test/test-poll-close.c \
233233
test/test-poll-close-doesnt-corrupt-stack.c \
234234
test/test-poll-closesocket.c \
235+
test/test-poll-multiple-handles.c \
235236
test/test-poll-oob.c \
236237
test/test-process-priority.c \
237238
test/test-process-title.c \

src/unix/core.c

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -899,13 +899,12 @@ void uv__io_stop(uv_loop_t* loop, uv__io_t* w, unsigned int events) {
899899
if (w->pevents == 0) {
900900
QUEUE_REMOVE(&w->watcher_queue);
901901
QUEUE_INIT(&w->watcher_queue);
902+
w->events = 0;
902903

903-
if (loop->watchers[w->fd] != NULL) {
904-
assert(loop->watchers[w->fd] == w);
904+
if (w == loop->watchers[w->fd]) {
905905
assert(loop->nfds > 0);
906906
loop->watchers[w->fd] = NULL;
907907
loop->nfds--;
908-
w->events = 0;
909908
}
910909
}
911910
else if (QUEUE_EMPTY(&w->watcher_queue))

src/unix/poll.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,12 +116,20 @@ int uv_poll_stop(uv_poll_t* handle) {
116116

117117

118118
int uv_poll_start(uv_poll_t* handle, int pevents, uv_poll_cb poll_cb) {
119+
uv__io_t** watchers;
120+
uv__io_t* w;
119121
int events;
120122

121123
assert((pevents & ~(UV_READABLE | UV_WRITABLE | UV_DISCONNECT |
122124
UV_PRIORITIZED)) == 0);
123125
assert(!uv__is_closing(handle));
124126

127+
watchers = handle->loop->watchers;
128+
w = &handle->io_watcher;
129+
130+
if (uv__fd_exists(handle->loop, w->fd) && watchers[w->fd] != w)
131+
return UV_EEXIST;
132+
125133
uv__poll_stop(handle);
126134

127135
if (pevents == 0)

test/test-list.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,7 @@ TEST_DECLARE (poll_nested_epoll)
425425
#ifdef UV_HAVE_KQUEUE
426426
TEST_DECLARE (poll_nested_kqueue)
427427
#endif
428+
TEST_DECLARE (poll_multiple_handles)
428429

429430
TEST_DECLARE (ip4_addr)
430431
TEST_DECLARE (ip6_addr_link_local)
@@ -841,6 +842,7 @@ TASK_LIST_START
841842
#ifdef UV_HAVE_KQUEUE
842843
TEST_ENTRY (poll_nested_kqueue)
843844
#endif
845+
TEST_ENTRY (poll_multiple_handles)
844846

845847
TEST_ENTRY (socket_buffer_size)
846848

test/test-poll-multiple-handles.c

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/* Copyright Joyent, Inc. and other Node contributors. All rights reserved.
2+
*
3+
* Permission is hereby granted, free of charge, to any person obtaining a copy
4+
* of this software and associated documentation files (the "Software"), to
5+
* deal in the Software without restriction, including without limitation the
6+
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
7+
* sell copies of the Software, and to permit persons to whom the Software is
8+
* furnished to do so, subject to the following conditions:
9+
*
10+
* The above copyright notice and this permission notice shall be included in
11+
* all copies or substantial portions of the Software.
12+
*
13+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18+
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
19+
* IN THE SOFTWARE.
20+
*/
21+
22+
#include <errno.h>
23+
24+
#ifndef _WIN32
25+
# include <fcntl.h>
26+
# include <sys/socket.h>
27+
# include <unistd.h>
28+
#endif
29+
30+
#include "uv.h"
31+
#include "task.h"
32+
33+
34+
static int close_cb_called = 0;
35+
36+
37+
static void close_cb(uv_handle_t* handle) {
38+
close_cb_called++;
39+
}
40+
41+
42+
TEST_IMPL(poll_multiple_handles) {
43+
uv_os_sock_t sock;
44+
uv_poll_t first_poll_handle, second_poll_handle;
45+
46+
#ifdef _WIN32
47+
{
48+
struct WSAData wsa_data;
49+
int r = WSAStartup(MAKEWORD(2, 2), &wsa_data);
50+
ASSERT(r == 0);
51+
}
52+
#endif
53+
54+
sock = socket(AF_INET, SOCK_STREAM, 0);
55+
uv_poll_init_socket(uv_default_loop(), &first_poll_handle, sock);
56+
uv_poll_init_socket(uv_default_loop(), &second_poll_handle, sock);
57+
58+
uv_poll_start(&first_poll_handle, UV_READABLE, NULL);
59+
60+
/* We may not start polling while another polling handle is active on that fd */
61+
#ifndef _WIN32
62+
/* We do not track handles in an O(1) lookupable way on Windows, so not checking that here */
63+
ASSERT(uv_poll_start(&second_poll_handle, UV_READABLE, NULL) == UV_EEXIST);
64+
#endif
65+
66+
/* After stopping the other polling handle, we now should be able to poll */
67+
uv_poll_stop(&first_poll_handle);
68+
ASSERT(uv_poll_start(&second_poll_handle, UV_READABLE, NULL) == 0);
69+
70+
/* Closing an already stopped polling handle is safe in any case */
71+
uv_close((uv_handle_t*) &first_poll_handle, close_cb);
72+
73+
uv_run(uv_default_loop(), UV_RUN_ONCE);
74+
ASSERT(close_cb_called == 1);
75+
76+
ASSERT(uv_is_active((uv_handle_t*) &second_poll_handle));
77+
uv_close((uv_handle_t*) &second_poll_handle, close_cb);
78+
79+
uv_run(uv_default_loop(), UV_RUN_DEFAULT);
80+
ASSERT(close_cb_called == 2);
81+
82+
MAKE_VALGRIND_HAPPY();
83+
return 0;
84+
}

test/test.gyp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@
8686
'test-poll-close.c',
8787
'test-poll-close-doesnt-corrupt-stack.c',
8888
'test-poll-closesocket.c',
89+
'test-poll-multiple-handles.c',
8990
'test-poll-oob.c',
9091
'test-process-priority.c',
9192
'test-process-title.c',

0 commit comments

Comments
 (0)