fsevents: fix race on simultaneous init+close

When `uv_fsevents_t` handle is closed immediately after initializing,
there is a possibility that the `CFRunLoop`'s thread will process both
of these events at the same time. `uv__is_active(handle)` will return
`0`, and the `uv_close()` semaphore will be unblocked, leading
to the use after free in node.js.

See: https://github.com/nodejs/node/issues/4091
PR-URL: https://github.com/libuv/libuv/pull/637
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
This commit is contained in:
Fedor Indutny 2015-12-02 03:59:10 -05:00
parent 43994b4e3c
commit 0761bfe6a3

View File

@ -73,9 +73,16 @@ typedef struct uv__fsevents_event_s uv__fsevents_event_t;
typedef struct uv__cf_loop_signal_s uv__cf_loop_signal_t;
typedef struct uv__cf_loop_state_s uv__cf_loop_state_t;
enum uv__cf_loop_signal_type_e {
kUVCFLoopSignalRegular,
kUVCFLoopSignalClosing
};
typedef enum uv__cf_loop_signal_type_e uv__cf_loop_signal_type_t;
struct uv__cf_loop_signal_s {
QUEUE member;
uv_fs_event_t* handle;
uv__cf_loop_signal_type_t type;
};
struct uv__fsevents_event_s {
@ -98,7 +105,9 @@ struct uv__cf_loop_state_s {
/* Forward declarations */
static void uv__cf_loop_cb(void* arg);
static void* uv__cf_loop_runner(void* arg);
static int uv__cf_loop_signal(uv_loop_t* loop, uv_fs_event_t* handle);
static int uv__cf_loop_signal(uv_loop_t* loop,
uv_fs_event_t* handle,
uv__cf_loop_signal_type_t type);
/* Lazy-loaded by uv__fsevents_global_init(). */
static CFArrayRef (*pCFArrayCreate)(CFAllocatorRef,
@ -383,7 +392,8 @@ static void uv__fsevents_destroy_stream(uv_loop_t* loop) {
/* Runs in CF thread, when there're new fsevent handles to add to stream */
static void uv__fsevents_reschedule(uv_fs_event_t* handle) {
static void uv__fsevents_reschedule(uv_fs_event_t* handle,
uv__cf_loop_signal_type_t type) {
uv__cf_loop_state_t* state;
QUEUE* q;
uv_fs_event_t* curr;
@ -482,7 +492,7 @@ final:
*
* NOTE: This is coupled with `uv_sem_wait()` in `uv__fsevents_close`
*/
if (!uv__is_active(handle))
if (type == kUVCFLoopSignalClosing)
uv_sem_post(&state->fsevent_sem);
}
@ -672,7 +682,7 @@ void uv__fsevents_loop_delete(uv_loop_t* loop) {
if (loop->cf_state == NULL)
return;
if (uv__cf_loop_signal(loop, NULL) != 0)
if (uv__cf_loop_signal(loop, NULL, kUVCFLoopSignalRegular) != 0)
abort();
uv_thread_join(&loop->cf_thread);
@ -746,7 +756,7 @@ static void uv__cf_loop_cb(void* arg) {
if (s->handle == NULL)
pCFRunLoopStop(state->loop);
else
uv__fsevents_reschedule(s->handle);
uv__fsevents_reschedule(s->handle, s->type);
uv__free(s);
}
@ -754,7 +764,9 @@ static void uv__cf_loop_cb(void* arg) {
/* Runs in UV loop to notify CF thread */
int uv__cf_loop_signal(uv_loop_t* loop, uv_fs_event_t* handle) {
int uv__cf_loop_signal(uv_loop_t* loop,
uv_fs_event_t* handle,
uv__cf_loop_signal_type_t type) {
uv__cf_loop_signal_t* item;
uv__cf_loop_state_t* state;
@ -763,6 +775,7 @@ int uv__cf_loop_signal(uv_loop_t* loop, uv_fs_event_t* handle) {
return -ENOMEM;
item->handle = handle;
item->type = type;
uv_mutex_lock(&loop->cf_mutex);
QUEUE_INSERT_TAIL(&loop->cf_signals, &item->member);
@ -825,7 +838,7 @@ int uv__fsevents_init(uv_fs_event_t* handle) {
/* Reschedule FSEventStream */
assert(handle != NULL);
err = uv__cf_loop_signal(handle->loop, handle);
err = uv__cf_loop_signal(handle->loop, handle, kUVCFLoopSignalRegular);
if (err)
goto fail_loop_signal;
@ -865,7 +878,7 @@ int uv__fsevents_close(uv_fs_event_t* handle) {
/* Reschedule FSEventStream */
assert(handle != NULL);
err = uv__cf_loop_signal(handle->loop, handle);
err = uv__cf_loop_signal(handle->loop, handle, kUVCFLoopSignalClosing);
if (err)
return -err;