Skip to content

Commit

Permalink
fs: unset FileHandle fd after close
Browse files Browse the repository at this point in the history
- Do not set the fd as a property on the native object.
- Use the already-existent `GetFD()` method to pass the
  fd from C++ to JS.
- Cache the fd in JS to avoid repeated accesses to the
  C++ getter.
- Set the fd to `-1` after close, thus reliably making
  subsequent calls using the `FileHandle` return `EBADF`.

Fixes: nodejs#31361

PR-URL: nodejs#31389
Reviewed-By: Richard Lau <riclau@uk.ibm.com>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Minwoo Jung <nodecorelab@gmail.com>
Reviewed-By: David Carlier <devnexen@gmail.com>
Reviewed-By: Rich Trott <rtrott@gmail.com>
  • Loading branch information
addaleax committed Jan 18, 2020
1 parent cc0748f commit 7864c53
Show file tree
Hide file tree
Showing 4 changed files with 33 additions and 15 deletions.
5 changes: 4 additions & 1 deletion lib/internal/fs/promises.js
Expand Up @@ -53,21 +53,23 @@ const pathModule = require('path');
const { promisify } = require('internal/util');

const kHandle = Symbol('kHandle');
const kFd = Symbol('kFd');
const { kUsePromises } = binding;

const getDirectoryEntriesPromise = promisify(getDirents);

class FileHandle {
constructor(filehandle) {
this[kHandle] = filehandle;
this[kFd] = filehandle.fd;
}

getAsyncId() {
return this[kHandle].getAsyncId();
}

get fd() {
return this[kHandle].fd;
return this[kFd];
}

appendFile(data, options) {
Expand Down Expand Up @@ -123,6 +125,7 @@ class FileHandle {
}

close = () => {
this[kFd] = -1;
return this[kHandle].close();
}
}
Expand Down
16 changes: 3 additions & 13 deletions src/node_file.cc
Expand Up @@ -53,7 +53,6 @@ namespace fs {

using v8::Array;
using v8::Context;
using v8::DontDelete;
using v8::EscapableHandleScope;
using v8::Function;
using v8::FunctionCallbackInfo;
Expand All @@ -68,8 +67,6 @@ using v8::Number;
using v8::Object;
using v8::ObjectTemplate;
using v8::Promise;
using v8::PropertyAttribute;
using v8::ReadOnly;
using v8::String;
using v8::Symbol;
using v8::Uint32;
Expand Down Expand Up @@ -138,15 +135,6 @@ FileHandle* FileHandle::New(Environment* env, int fd, Local<Object> obj) {
.ToLocal(&obj)) {
return nullptr;
}
PropertyAttribute attr =
static_cast<PropertyAttribute>(ReadOnly | DontDelete);
if (obj->DefineOwnProperty(env->context(),
env->fd_string(),
Integer::New(env->isolate(), fd),
attr)
.IsNothing()) {
return nullptr;
}
return new FileHandle(env, obj, fd);
}

Expand Down Expand Up @@ -190,12 +178,13 @@ inline void FileHandle::Close() {
uv_fs_t req;
int ret = uv_fs_close(env()->event_loop(), &req, fd_, nullptr);
uv_fs_req_cleanup(&req);
AfterClose();

struct err_detail { int ret; int fd; };

err_detail detail { ret, fd_ };

AfterClose();

if (ret < 0) {
// Do not unref this
env()->SetImmediate([detail](Environment* env) {
Expand Down Expand Up @@ -340,6 +329,7 @@ void FileHandle::ReleaseFD(const FunctionCallbackInfo<Value>& args) {
void FileHandle::AfterClose() {
closing_ = false;
closed_ = true;
fd_ = -1;
if (reading_ && !persistent().IsEmpty())
EmitRead(UV_EOF);
}
Expand Down
2 changes: 1 addition & 1 deletion src/node_file.h
Expand Up @@ -205,7 +205,7 @@ class FileHandle final : public AsyncWrap, public StreamBase {

static void New(const v8::FunctionCallbackInfo<v8::Value>& args);

int fd() const { return fd_; }
int GetFD() override { return fd_; }

// Will asynchronously close the FD and return a Promise that will
// be resolved once closing is complete.
Expand Down
25 changes: 25 additions & 0 deletions test/parallel/test-fs-filehandle-use-after-close.js
@@ -0,0 +1,25 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const fs = require('fs').promises;

(async () => {
const filehandle = await fs.open(__filename);

assert.notStrictEqual(filehandle.fd, -1);
await filehandle.close();
assert.strictEqual(filehandle.fd, -1);

// Open another file handle first. This would typically receive the fd
// that `filehandle` previously used. In earlier versions of Node.js, the
// .stat() call would then succeed because it still used the original fd;
// See https://github.com/nodejs/node/issues/31361 for more details.
const otherFilehandle = await fs.open(process.execPath);

assert.rejects(() => filehandle.stat(), {
code: 'EBADF',
syscall: 'fstat'
});

await otherFilehandle.close();
})().then(common.mustCall());

0 comments on commit 7864c53

Please sign in to comment.