Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
fs: clean up Dir.read() uv_fs_t data before calling into JS
A call into JS can schedule another operation on the same `uv_dir_t`.
In particular, when the handle is closed from the callback for a
directory read operation, there previously was a race condition window:

1. A `dir.read()` operation is submitted to libuv
2. The read operation is finished by libuv, calling `AfterDirRead()`
3. We call into JS
4. JS calls dir.close()
5. libuv posts the close request to a thread in the pool
6. The close request runs, destroying the directory handle
7. `AfterDirRead()` is being exited.

Exiting the `FSReqAfterScope` in step 7 attempts to destroy the original
uv_fs_t` from step 1, which now points to an `uv_dir_t` that has
already been destroyed in step 5.

By forcing the `FSReqAfterScope` to clean up before we call into JS,
we can be sure that no other operations on the same `uv_dir_t` are
submitted concurrently.

This addresses issues observed when running with ASAN/valgrind.
  • Loading branch information
addaleax committed May 7, 2020
commit fa868cb77289b9eecb61a6fa904a969ff9d9b9a3
10 changes: 7 additions & 3 deletions src/node_dir.cc
Original file line number Diff line number Diff line change
Expand Up @@ -197,8 +197,8 @@ static MaybeLocal<Array> DirentListToArray(
}

static void AfterDirRead(uv_fs_t* req) {
FSReqBase* req_wrap = FSReqBase::from_req(req);
FSReqAfterScope after(req_wrap, req);
BaseObjectPtr<FSReqBase> req_wrap { FSReqBase::from_req(req) };
FSReqAfterScope after(req_wrap.get(), req);

if (!after.Proceed()) {
return;
Expand All @@ -210,12 +210,12 @@ static void AfterDirRead(uv_fs_t* req) {
if (req->result == 0) {
// Done
Local<Value> done = Null(isolate);
after.Clear();
req_wrap->Resolve(done);
return;
}

uv_dir_t* dir = static_cast<uv_dir_t*>(req->ptr);
req->ptr = nullptr;

Local<Value> error;
Local<Array> js_array;
Expand All @@ -224,9 +224,13 @@ static void AfterDirRead(uv_fs_t* req) {
req->result,
req_wrap->encoding(),
&error).ToLocal(&js_array)) {
// Clear libuv resources *before* delivering results to JS land because
// that can schedule another operation on the same uv_dir_t. Ditto below.
after.Clear();
return req_wrap->Reject(error);
}

after.Clear();
req_wrap->Resolve(js_array);
}

Expand Down
25 changes: 18 additions & 7 deletions src/node_file.cc
Original file line number Diff line number Diff line change
Expand Up @@ -556,8 +556,15 @@ FSReqAfterScope::FSReqAfterScope(FSReqBase* wrap, uv_fs_t* req)
}

FSReqAfterScope::~FSReqAfterScope() {
Clear();
}

void FSReqAfterScope::Clear() {
if (!wrap_) return;

uv_fs_req_cleanup(wrap_->req());
delete wrap_;
wrap_->Detach();
wrap_.reset();
}

// TODO(joyeecheung): create a normal context object, and
Expand All @@ -570,12 +577,16 @@ FSReqAfterScope::~FSReqAfterScope() {
// which is also why the errors should have been constructed
// in JS for more flexibility.
void FSReqAfterScope::Reject(uv_fs_t* req) {
wrap_->Reject(UVException(wrap_->env()->isolate(),
req->result,
wrap_->syscall(),
nullptr,
req->path,
wrap_->data()));
BaseObjectPtr<FSReqBase> wrap { wrap_ };
Local<Value> exception =
UVException(wrap_->env()->isolate(),
req->result,
wrap_->syscall(),
nullptr,
req->path,
wrap_->data());
Clear();
wrap->Reject(exception);
}

bool FSReqAfterScope::Proceed() {
Expand Down
3 changes: 2 additions & 1 deletion src/node_file.h
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ class FSReqAfterScope final {
public:
FSReqAfterScope(FSReqBase* wrap, uv_fs_t* req);
~FSReqAfterScope();
void Clear();

bool Proceed();

Expand All @@ -195,7 +196,7 @@ class FSReqAfterScope final {
FSReqAfterScope& operator=(const FSReqAfterScope&&) = delete;

private:
FSReqBase* wrap_ = nullptr;
BaseObjectPtr<FSReqBase> wrap_;
uv_fs_t* req_ = nullptr;
v8::HandleScope handle_scope_;
v8::Context::Scope context_scope_;
Expand Down