Browse Source

fs: validate args of truncate functions in js

This patch

 1. moves the basic validation of arguments to `truncate` family
    of functions to the JavaScript layer from the C++ layer.

 2. makes sure that the File Descriptors are validated strictly.

PR-URL: https://github.com/nodejs/node/pull/2498
Reviewed-By: Trevor Norris <trev.norris@gmail.com>
v7.x
Sakthipriyan Vairamani 9 years ago
parent
commit
c86c1eeab5
  1. 107
      lib/fs.js
  2. 65
      src/node_file.cc
  3. 76
      test/parallel/test-fs-truncate-fd.js
  4. 94
      test/parallel/test-fs-truncate.js
  5. 68
      test/parallel/test-fs-utimes.js
  6. 9
      test/parallel/test-fs-write-no-fd.js

107
lib/fs.js

@ -81,6 +81,16 @@ function throwOptionsError(options) {
'but got ' + typeof options + ' instead'); 'but got ' + typeof options + ' instead');
} }
function isFD(fd) {
return Number.isInteger(fd) && fd >= 0 && fd <= 0xFFFFFFFF;
}
function sanitizeFD(fd) {
if (!isFD(fd))
throw new TypeError('file descriptor must be a unsigned 32-bit integer');
return fd;
}
// Ensure that callbacks run in the global context. Only use this function // Ensure that callbacks run in the global context. Only use this function
// for callbacks that are passed to the binding layer, callbacks that are // for callbacks that are passed to the binding layer, callbacks that are
// invoked from JS already run in the proper scope. // invoked from JS already run in the proper scope.
@ -112,10 +122,6 @@ function nullCheck(path, callback) {
return true; return true;
} }
function isFd(path) {
return (path >>> 0) === path;
}
// Static method to set the stats properties on a Stats object. // Static method to set the stats properties on a Stats object.
fs.Stats = function( fs.Stats = function(
dev, dev,
@ -257,7 +263,7 @@ fs.readFile = function(path, options, callback) {
return; return;
var context = new ReadFileContext(callback, encoding); var context = new ReadFileContext(callback, encoding);
context.isUserFd = isFd(path); // file descriptor ownership context.isUserFd = isFD(path); // file descriptor ownership
var req = new FSReqWrap(); var req = new FSReqWrap();
req.context = context; req.context = context;
req.oncomplete = readFileAfterOpen; req.oncomplete = readFileAfterOpen;
@ -473,7 +479,7 @@ fs.readFileSync = function(path, options) {
assertEncoding(encoding); assertEncoding(encoding);
var flag = options.flag || 'r'; var flag = options.flag || 'r';
var isUserFd = isFd(path); // file descriptor ownership var isUserFd = isFD(path); // file descriptor ownership
var fd = isUserFd ? path : fs.openSync(path, flag, 0o666); var fd = isUserFd ? path : fs.openSync(path, flag, 0o666);
var st = tryStatSync(fd, isUserFd); var st = tryStatSync(fd, isUserFd);
@ -570,12 +576,12 @@ Object.defineProperty(exports, '_stringToFlags', {
fs.close = function(fd, callback) { fs.close = function(fd, callback) {
var req = new FSReqWrap(); var req = new FSReqWrap();
req.oncomplete = makeCallback(arguments[arguments.length - 1]); req.oncomplete = makeCallback(callback);
binding.close(fd, req); binding.close(sanitizeFD(fd), req);
}; };
fs.closeSync = function(fd) { fs.closeSync = function(fd) {
return binding.close(fd); return binding.close(sanitizeFD(fd));
}; };
function modeNum(m, def) { function modeNum(m, def) {
@ -612,6 +618,7 @@ fs.openSync = function(path, flags, mode) {
var readWarned = false; var readWarned = false;
fs.read = function(fd, buffer, offset, length, position, callback) { fs.read = function(fd, buffer, offset, length, position, callback) {
callback = makeCallback(arguments[arguments.length - 1]); callback = makeCallback(arguments[arguments.length - 1]);
fd = sanitizeFD(fd);
if (!(buffer instanceof Buffer)) { if (!(buffer instanceof Buffer)) {
// legacy string interface (fd, length, position, encoding, callback) // legacy string interface (fd, length, position, encoding, callback)
readWarned = printDeprecation('fs.read\'s legacy String interface ' + readWarned = printDeprecation('fs.read\'s legacy String interface ' +
@ -672,6 +679,7 @@ fs.readSync = function(fd, buffer, offset, length, position) {
var legacy = false; var legacy = false;
var encoding; var encoding;
fd = sanitizeFD(fd);
if (!(buffer instanceof Buffer)) { if (!(buffer instanceof Buffer)) {
// legacy string interface (fd, length, position, encoding, callback) // legacy string interface (fd, length, position, encoding, callback)
readSyncWarned = printDeprecation('fs.readSync\'s legacy String interface' + readSyncWarned = printDeprecation('fs.readSync\'s legacy String interface' +
@ -713,6 +721,7 @@ fs.readSync = function(fd, buffer, offset, length, position) {
// fs.write(fd, string[, position[, encoding]], callback); // fs.write(fd, string[, position[, encoding]], callback);
fs.write = function(fd, buffer, offset, length, position, callback) { fs.write = function(fd, buffer, offset, length, position, callback) {
callback = makeCallback(arguments[arguments.length - 1]); callback = makeCallback(arguments[arguments.length - 1]);
fd = sanitizeFD(fd);
function wrapper(err, written) { function wrapper(err, written) {
// Retain a reference to buffer so that it can't be GC'ed too soon. // Retain a reference to buffer so that it can't be GC'ed too soon.
callback(err, written || 0, buffer); callback(err, written || 0, buffer);
@ -748,6 +757,7 @@ fs.write = function(fd, buffer, offset, length, position, callback) {
// OR // OR
// fs.writeSync(fd, string[, position[, encoding]]); // fs.writeSync(fd, string[, position[, encoding]]);
fs.writeSync = function(fd, buffer, offset, length, position) { fs.writeSync = function(fd, buffer, offset, length, position) {
fd = sanitizeFD(fd);
if (buffer instanceof Buffer) { if (buffer instanceof Buffer) {
if (position === undefined) if (position === undefined)
position = null; position = null;
@ -779,14 +789,18 @@ fs.renameSync = function(oldPath, newPath) {
}; };
fs.truncate = function(path, len, callback) { fs.truncate = function(path, len, callback) {
if (typeof path === 'number') { callback = makeCallback(arguments[arguments.length - 1]);
if (isFD(path))
return fs.ftruncate(path, len, callback); return fs.ftruncate(path, len, callback);
}
callback = makeCallback(arguments[arguments.length - 1]); if (typeof path !== 'string')
throw new TypeError('path must be a string');
if (typeof len === 'function' || len === undefined) { if (typeof len === 'function' || len == undefined) {
len = 0; len = 0;
} else if (!Number.isInteger(len) || len < 0) {
throw new TypeError('length must be a positive integer');
} }
fs.open(path, 'r+', function(er, fd) { fs.open(path, 'r+', function(er, fd) {
@ -802,13 +816,18 @@ fs.truncate = function(path, len, callback) {
}; };
fs.truncateSync = function(path, len) { fs.truncateSync = function(path, len) {
if (typeof path === 'number') { if (isFD(path))
// legacy
return fs.ftruncateSync(path, len); return fs.ftruncateSync(path, len);
}
if (len === undefined) { if (typeof path !== 'string')
throw new TypeError('path must be a string');
if (len === undefined || len === null) {
len = 0; len = 0;
} else if (!Number.isInteger(len) || len < 0) {
throw new TypeError('length must be a positive integer');
} }
// allow error to be thrown, but still close fd. // allow error to be thrown, but still close fd.
var fd = fs.openSync(path, 'r+'); var fd = fs.openSync(path, 'r+');
var ret; var ret;
@ -822,18 +841,30 @@ fs.truncateSync = function(path, len) {
}; };
fs.ftruncate = function(fd, len, callback) { fs.ftruncate = function(fd, len, callback) {
if (typeof len === 'function' || len === undefined) { callback = makeCallback(arguments[arguments.length - 1]);
fd = sanitizeFD(fd);
if (typeof len === 'function' || len == undefined) {
len = 0; len = 0;
} else if (!Number.isInteger(len) || len < 0) {
throw new TypeError('length must be a positive integer');
} }
var req = new FSReqWrap(); var req = new FSReqWrap();
req.oncomplete = makeCallback(arguments[arguments.length - 1]); req.oncomplete = callback;
binding.ftruncate(fd, len, req); binding.ftruncate(fd, len, req);
}; };
fs.ftruncateSync = function(fd, len) { fs.ftruncateSync = function(fd, len) {
if (len === undefined) { fd = sanitizeFD(fd);
if (len === undefined || len === null) {
len = 0; len = 0;
} else if (!Number.isInteger(len) || len < 0) {
throw new TypeError('length must be a positive integer');
} }
return binding.ftruncate(fd, len); return binding.ftruncate(fd, len);
}; };
@ -853,21 +884,21 @@ fs.rmdirSync = function(path) {
fs.fdatasync = function(fd, callback) { fs.fdatasync = function(fd, callback) {
var req = new FSReqWrap(); var req = new FSReqWrap();
req.oncomplete = makeCallback(callback); req.oncomplete = makeCallback(callback);
binding.fdatasync(fd, req); binding.fdatasync(sanitizeFD(fd), req);
}; };
fs.fdatasyncSync = function(fd) { fs.fdatasyncSync = function(fd) {
return binding.fdatasync(fd); return binding.fdatasync(sanitizeFD(fd));
}; };
fs.fsync = function(fd, callback) { fs.fsync = function(fd, callback) {
var req = new FSReqWrap(); var req = new FSReqWrap();
req.oncomplete = makeCallback(arguments[arguments.length - 1]); req.oncomplete = makeCallback(callback);
binding.fsync(fd, req); binding.fsync(sanitizeFD(fd), req);
}; };
fs.fsyncSync = function(fd) { fs.fsyncSync = function(fd) {
return binding.fsync(fd); return binding.fsync(sanitizeFD(fd));
}; };
fs.mkdir = function(path, mode, callback) { fs.mkdir = function(path, mode, callback) {
@ -915,8 +946,8 @@ fs.readdirSync = function(path, options) {
fs.fstat = function(fd, callback) { fs.fstat = function(fd, callback) {
var req = new FSReqWrap(); var req = new FSReqWrap();
req.oncomplete = makeCallback(arguments[arguments.length - 1]); req.oncomplete = makeCallback(callback);
binding.fstat(fd, req); binding.fstat(sanitizeFD(fd), req);
}; };
fs.lstat = function(path, callback) { fs.lstat = function(path, callback) {
@ -936,7 +967,7 @@ fs.stat = function(path, callback) {
}; };
fs.fstatSync = function(fd) { fs.fstatSync = function(fd) {
return binding.fstat(fd); return binding.fstat(sanitizeFD(fd));
}; };
fs.lstatSync = function(path) { fs.lstatSync = function(path) {
@ -1053,11 +1084,11 @@ fs.unlinkSync = function(path) {
fs.fchmod = function(fd, mode, callback) { fs.fchmod = function(fd, mode, callback) {
var req = new FSReqWrap(); var req = new FSReqWrap();
req.oncomplete = makeCallback(arguments[arguments.length - 1]); req.oncomplete = makeCallback(arguments[arguments.length - 1]);
binding.fchmod(fd, modeNum(mode), req); binding.fchmod(sanitizeFD(fd), modeNum(mode), req);
}; };
fs.fchmodSync = function(fd, mode) { fs.fchmodSync = function(fd, mode) {
return binding.fchmod(fd, modeNum(mode)); return binding.fchmod(sanitizeFD(fd), modeNum(mode));
}; };
if (constants.hasOwnProperty('O_SYMLINK')) { if (constants.hasOwnProperty('O_SYMLINK')) {
@ -1136,11 +1167,11 @@ if (constants.hasOwnProperty('O_SYMLINK')) {
fs.fchown = function(fd, uid, gid, callback) { fs.fchown = function(fd, uid, gid, callback) {
var req = new FSReqWrap(); var req = new FSReqWrap();
req.oncomplete = makeCallback(arguments[arguments.length - 1]); req.oncomplete = makeCallback(arguments[arguments.length - 1]);
binding.fchown(fd, uid, gid, req); binding.fchown(sanitizeFD(fd), uid, gid, req);
}; };
fs.fchownSync = function(fd, uid, gid) { fs.fchownSync = function(fd, uid, gid) {
return binding.fchown(fd, uid, gid); return binding.fchown(sanitizeFD(fd), uid, gid);
}; };
fs.chown = function(path, uid, gid, callback) { fs.chown = function(path, uid, gid, callback) {
@ -1197,6 +1228,7 @@ fs.utimesSync = function(path, atime, mtime) {
fs.futimes = function(fd, atime, mtime, callback) { fs.futimes = function(fd, atime, mtime, callback) {
callback = makeCallback(arguments[arguments.length - 1]); callback = makeCallback(arguments[arguments.length - 1]);
fd = sanitizeFD(fd);
atime = toUnixTimestamp(atime); atime = toUnixTimestamp(atime);
mtime = toUnixTimestamp(mtime); mtime = toUnixTimestamp(mtime);
var req = new FSReqWrap(); var req = new FSReqWrap();
@ -1205,6 +1237,7 @@ fs.futimes = function(fd, atime, mtime, callback) {
}; };
fs.futimesSync = function(fd, atime, mtime) { fs.futimesSync = function(fd, atime, mtime) {
fd = sanitizeFD(fd);
atime = toUnixTimestamp(atime); atime = toUnixTimestamp(atime);
mtime = toUnixTimestamp(mtime); mtime = toUnixTimestamp(mtime);
binding.futimes(fd, atime, mtime); binding.futimes(fd, atime, mtime);
@ -1257,7 +1290,7 @@ fs.writeFile = function(path, data, options, callback) {
var flag = options.flag || 'w'; var flag = options.flag || 'w';
if (isFd(path)) { if (isFD(path)) {
writeFd(path, true); writeFd(path, true);
return; return;
} }
@ -1291,7 +1324,7 @@ fs.writeFileSync = function(path, data, options) {
assertEncoding(options.encoding); assertEncoding(options.encoding);
var flag = options.flag || 'w'; var flag = options.flag || 'w';
var isUserFd = isFd(path); // file descriptor ownership var isUserFd = isFD(path); // file descriptor ownership
var fd = isUserFd ? path : fs.openSync(path, flag, options.mode); var fd = isUserFd ? path : fs.openSync(path, flag, options.mode);
if (!(data instanceof Buffer)) { if (!(data instanceof Buffer)) {
@ -1329,7 +1362,7 @@ fs.appendFile = function(path, data, options, callback) {
options = util._extend({ flag: 'a' }, options); options = util._extend({ flag: 'a' }, options);
// force append behavior when using a supplied file descriptor // force append behavior when using a supplied file descriptor
if (isFd(path)) if (isFD(path))
options.flag = 'a'; options.flag = 'a';
fs.writeFile(path, data, options, callback); fs.writeFile(path, data, options, callback);
@ -1348,7 +1381,7 @@ fs.appendFileSync = function(path, data, options) {
options = util._extend({ flag: 'a' }, options); options = util._extend({ flag: 'a' }, options);
// force append behavior when using a supplied file descriptor // force append behavior when using a supplied file descriptor
if (isFd(path)) if (isFD(path))
options.flag = 'a'; options.flag = 'a';
fs.writeFileSync(path, data, options); fs.writeFileSync(path, data, options);
@ -1932,7 +1965,7 @@ function SyncWriteStream(fd, options) {
options = options || {}; options = options || {};
this.fd = fd; this.fd = sanitizeFD(fd);
this.writable = true; this.writable = true;
this.readable = false; this.readable = false;
this.autoClose = options.autoClose === undefined ? true : options.autoClose; this.autoClose = options.autoClose === undefined ? true : options.autoClose;

65
src/node_file.cc

@ -49,6 +49,11 @@ using v8::Value;
#define GET_OFFSET(a) ((a)->IsNumber() ? (a)->IntegerValue() : -1) #define GET_OFFSET(a) ((a)->IsNumber() ? (a)->IntegerValue() : -1)
static int SanitizeFD(Local<Value> fd) {
CHECK(fd->IsUint32() && "file descriptor must be a unsigned 32-bit integer");
return fd->Uint32Value();
}
class FSReqWrap: public ReqWrap<uv_fs_t> { class FSReqWrap: public ReqWrap<uv_fs_t> {
public: public:
enum Ownership { COPY, MOVE }; enum Ownership { COPY, MOVE };
@ -406,10 +411,8 @@ static void Close(const FunctionCallbackInfo<Value>& args) {
if (args.Length() < 1) if (args.Length() < 1)
return TYPE_ERROR("fd is required"); return TYPE_ERROR("fd is required");
if (!args[0]->IsInt32())
return TYPE_ERROR("fd must be a file descriptor");
int fd = args[0]->Int32Value(); int fd = SanitizeFD(args[0]);
if (args[1]->IsObject()) { if (args[1]->IsObject()) {
ASYNC_CALL(close, args[1], UTF8, fd) ASYNC_CALL(close, args[1], UTF8, fd)
@ -641,10 +644,8 @@ static void FStat(const FunctionCallbackInfo<Value>& args) {
if (args.Length() < 1) if (args.Length() < 1)
return TYPE_ERROR("fd is required"); return TYPE_ERROR("fd is required");
if (!args[0]->IsInt32())
return TYPE_ERROR("fd must be a file descriptor");
int fd = args[0]->Int32Value(); int fd = SanitizeFD(args[0]);
if (args[1]->IsObject()) { if (args[1]->IsObject()) {
ASYNC_CALL(fstat, args[1], UTF8, fd) ASYNC_CALL(fstat, args[1], UTF8, fd)
@ -772,21 +773,10 @@ static void FTruncate(const FunctionCallbackInfo<Value>& args) {
if (args.Length() < 2) if (args.Length() < 2)
return TYPE_ERROR("fd and length are required"); return TYPE_ERROR("fd and length are required");
if (!args[0]->IsInt32())
return TYPE_ERROR("fd must be a file descriptor");
int fd = args[0]->Int32Value(); int fd = SanitizeFD(args[0]);
// FIXME(bnoordhuis) It's questionable to reject non-ints here but still
// allow implicit coercion from null or undefined to zero. Probably best
// handled in lib/fs.js.
Local<Value> len_v(args[1]); Local<Value> len_v(args[1]);
if (!len_v->IsUndefined() &&
!len_v->IsNull() &&
!IsInt64(len_v->NumberValue())) {
return env->ThrowTypeError("Not an integer");
}
const int64_t len = len_v->IntegerValue(); const int64_t len = len_v->IntegerValue();
if (args[2]->IsObject()) { if (args[2]->IsObject()) {
@ -801,10 +791,8 @@ static void Fdatasync(const FunctionCallbackInfo<Value>& args) {
if (args.Length() < 1) if (args.Length() < 1)
return TYPE_ERROR("fd is required"); return TYPE_ERROR("fd is required");
if (!args[0]->IsInt32())
return TYPE_ERROR("fd must be a file descriptor");
int fd = args[0]->Int32Value(); int fd = SanitizeFD(args[0]);
if (args[1]->IsObject()) { if (args[1]->IsObject()) {
ASYNC_CALL(fdatasync, args[1], UTF8, fd) ASYNC_CALL(fdatasync, args[1], UTF8, fd)
@ -818,10 +806,8 @@ static void Fsync(const FunctionCallbackInfo<Value>& args) {
if (args.Length() < 1) if (args.Length() < 1)
return TYPE_ERROR("fd is required"); return TYPE_ERROR("fd is required");
if (!args[0]->IsInt32())
return TYPE_ERROR("fd must be a file descriptor");
int fd = args[0]->Int32Value(); int fd = SanitizeFD(args[0]);
if (args[1]->IsObject()) { if (args[1]->IsObject()) {
ASYNC_CALL(fsync, args[1], UTF8, fd) ASYNC_CALL(fsync, args[1], UTF8, fd)
@ -1024,12 +1010,8 @@ static void Open(const FunctionCallbackInfo<Value>& args) {
static void WriteBuffer(const FunctionCallbackInfo<Value>& args) { static void WriteBuffer(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args); Environment* env = Environment::GetCurrent(args);
if (!args[0]->IsInt32())
return env->ThrowTypeError("First argument must be file descriptor");
CHECK(Buffer::HasInstance(args[1])); CHECK(Buffer::HasInstance(args[1]));
int fd = SanitizeFD(args[0]);
int fd = args[0]->Int32Value();
Local<Object> obj = args[1].As<Object>(); Local<Object> obj = args[1].As<Object>();
const char* buf = Buffer::Data(obj); const char* buf = Buffer::Data(obj);
size_t buffer_length = Buffer::Length(obj); size_t buffer_length = Buffer::Length(obj);
@ -1071,10 +1053,8 @@ static void WriteBuffer(const FunctionCallbackInfo<Value>& args) {
static void WriteBuffers(const FunctionCallbackInfo<Value>& args) { static void WriteBuffers(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args); Environment* env = Environment::GetCurrent(args);
CHECK(args[0]->IsInt32());
CHECK(args[1]->IsArray()); CHECK(args[1]->IsArray());
int fd = SanitizeFD(args[0]);
int fd = args[0]->Int32Value();
Local<Array> chunks = args[1].As<Array>(); Local<Array> chunks = args[1].As<Array>();
int64_t pos = GET_OFFSET(args[2]); int64_t pos = GET_OFFSET(args[2]);
Local<Value> req = args[3]; Local<Value> req = args[3];
@ -1111,12 +1091,9 @@ static void WriteBuffers(const FunctionCallbackInfo<Value>& args) {
static void WriteString(const FunctionCallbackInfo<Value>& args) { static void WriteString(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args); Environment* env = Environment::GetCurrent(args);
if (!args[0]->IsInt32())
return env->ThrowTypeError("First argument must be file descriptor");
Local<Value> req; Local<Value> req;
Local<Value> string = args[1]; Local<Value> string = args[1];
int fd = args[0]->Int32Value(); int fd = SanitizeFD(args[0]);
char* buf = nullptr; char* buf = nullptr;
int64_t pos; int64_t pos;
size_t len; size_t len;
@ -1191,12 +1168,10 @@ static void Read(const FunctionCallbackInfo<Value>& args) {
if (args.Length() < 2) if (args.Length() < 2)
return TYPE_ERROR("fd and buffer are required"); return TYPE_ERROR("fd and buffer are required");
if (!args[0]->IsInt32())
return TYPE_ERROR("fd must be a file descriptor");
if (!Buffer::HasInstance(args[1])) if (!Buffer::HasInstance(args[1]))
return TYPE_ERROR("Second argument needs to be a buffer"); return TYPE_ERROR("Second argument needs to be a buffer");
int fd = args[0]->Int32Value(); int fd = SanitizeFD(args[0]);
Local<Value> req; Local<Value> req;
@ -1267,12 +1242,10 @@ static void FChmod(const FunctionCallbackInfo<Value>& args) {
if (args.Length() < 2) if (args.Length() < 2)
return TYPE_ERROR("fd and mode are required"); return TYPE_ERROR("fd and mode are required");
if (!args[0]->IsInt32())
return TYPE_ERROR("fd must be a file descriptor");
if (!args[1]->IsInt32()) if (!args[1]->IsInt32())
return TYPE_ERROR("mode must be an integer"); return TYPE_ERROR("mode must be an integer");
int fd = args[0]->Int32Value(); int fd = SanitizeFD(args[0]);
int mode = static_cast<int>(args[1]->Int32Value()); int mode = static_cast<int>(args[1]->Int32Value());
if (args[2]->IsObject()) { if (args[2]->IsObject()) {
@ -1328,14 +1301,12 @@ static void FChown(const FunctionCallbackInfo<Value>& args) {
return TYPE_ERROR("uid required"); return TYPE_ERROR("uid required");
if (len < 3) if (len < 3)
return TYPE_ERROR("gid required"); return TYPE_ERROR("gid required");
if (!args[0]->IsInt32())
return TYPE_ERROR("fd must be an int");
if (!args[1]->IsUint32()) if (!args[1]->IsUint32())
return TYPE_ERROR("uid must be an unsigned int"); return TYPE_ERROR("uid must be an unsigned int");
if (!args[2]->IsUint32()) if (!args[2]->IsUint32())
return TYPE_ERROR("gid must be an unsigned int"); return TYPE_ERROR("gid must be an unsigned int");
int fd = args[0]->Int32Value(); int fd = SanitizeFD(args[0]);
uv_uid_t uid = static_cast<uv_uid_t>(args[1]->Uint32Value()); uv_uid_t uid = static_cast<uv_uid_t>(args[1]->Uint32Value());
uv_gid_t gid = static_cast<uv_gid_t>(args[2]->Uint32Value()); uv_gid_t gid = static_cast<uv_gid_t>(args[2]->Uint32Value());
@ -1385,14 +1356,12 @@ static void FUTimes(const FunctionCallbackInfo<Value>& args) {
return TYPE_ERROR("atime required"); return TYPE_ERROR("atime required");
if (len < 3) if (len < 3)
return TYPE_ERROR("mtime required"); return TYPE_ERROR("mtime required");
if (!args[0]->IsInt32())
return TYPE_ERROR("fd must be an int");
if (!args[1]->IsNumber()) if (!args[1]->IsNumber())
return TYPE_ERROR("atime must be a number"); return TYPE_ERROR("atime must be a number");
if (!args[2]->IsNumber()) if (!args[2]->IsNumber())
return TYPE_ERROR("mtime must be a number"); return TYPE_ERROR("mtime must be a number");
const int fd = args[0]->Int32Value(); const int fd = SanitizeFD(args[0]);
const double atime = static_cast<double>(args[1]->NumberValue()); const double atime = static_cast<double>(args[1]->NumberValue());
const double mtime = static_cast<double>(args[2]->NumberValue()); const double mtime = static_cast<double>(args[2]->NumberValue());

76
test/parallel/test-fs-truncate-fd.js

@ -1,21 +1,71 @@
'use strict'; 'use strict';
var common = require('../common'); const common = require('../common');
var assert = require('assert'); const assert = require('assert');
var path = require('path'); const fs = require('fs');
var fs = require('fs'); const path = require('path');
var tmp = common.tmpDir;
common.refreshTmpDir(); common.refreshTmpDir();
var filename = path.resolve(tmp, 'truncate-file.txt'); const fds = [];
const filename = path.resolve(common.tmpDir, 'truncate-file.txt');
fs.writeFileSync(filename, 'hello world', 'utf8'); fs.writeFileSync(filename, 'hello world', 'utf8');
var fd = fs.openSync(filename, 'r+'); const fd = fs.openSync(filename, 'r+');
fds.push(fd);
fs.truncate(fd, 5, common.mustCall(function(err) { fs.truncate(fd, 5, common.mustCall(function(err) {
assert.ok(!err); assert.ifError(err);
assert.equal(fs.readFileSync(filename, 'utf8'), 'hello'); assert.equal(fs.readFileSync(filename, 'utf8'), 'hello');
})); }));
process.on('exit', function() { {
fs.closeSync(fd); // test partial truncation of a file
fs.unlinkSync(filename); const fileName = path.resolve(common.tmpDir, 'truncate-file-1.txt');
console.log('ok'); console.log(fileName);
}); fs.writeFileSync(fileName, 'hello world', 'utf8');
const fd = fs.openSync(fileName, 'r+');
fds.push(fd);
fs.truncate(fd, 5, common.mustCall(function(err) {
assert.ifError(err);
assert.strictEqual(fs.readFileSync(fileName, 'utf8'), 'hello');
}));
}
{
// make sure numbers as strings are not treated as fds with sync version
const fileName = path.resolve(common.tmpDir, 'truncate-file-2.txt');
console.log(fileName);
fs.writeFileSync(fileName, 'One');
const fd = fs.openSync(fileName, 'r');
fds.push(fd);
const fdFileName = path.resolve(common.tmpDir, '' + fd);
fs.writeFileSync(fdFileName, 'Two');
assert.strictEqual(fs.readFileSync(fileName).toString(), 'One');
assert.strictEqual(fs.readFileSync(fdFileName).toString(), 'Two');
fs.truncateSync(fdFileName);
assert.strictEqual(fs.readFileSync(fileName).toString(), 'One');
assert.strictEqual(fs.readFileSync(fdFileName).toString(), '');
}
{
// make sure numbers as strings are not treated as fds with async version
const fileName = path.resolve(common.tmpDir, 'truncate-file-3.txt');
console.log(fileName);
fs.writeFileSync(fileName, 'One');
const fd = fs.openSync(fileName, 'r');
fds.push(fd);
const fdFileName = path.resolve(common.tmpDir, '' + fd);
fs.writeFileSync(fdFileName, 'Two');
assert.strictEqual(fs.readFileSync(fileName).toString(), 'One');
assert.strictEqual(fs.readFileSync(fdFileName).toString(), 'Two');
fs.truncate(fdFileName, common.mustCall(function(err) {
assert.ifError(err);
assert.strictEqual(fs.readFileSync(fileName).toString(), 'One');
assert.strictEqual(fs.readFileSync(fdFileName).toString(), '');
}));
}
process.on('exit', () => fds.forEach((fd) => fs.closeSync(fd)));

94
test/parallel/test-fs-truncate.js

@ -24,6 +24,55 @@ fs.truncateSync(filename);
stat = fs.statSync(filename); stat = fs.statSync(filename);
assert.equal(stat.size, 0); assert.equal(stat.size, 0);
// path must be a string
assert.throws(function() {
fs.truncateSync({});
}, /path must be a string/);
assert.throws(function() {
fs.truncateSync([]);
}, /path must be a string/);
// Even invalid file descriptors are not allowed
assert.throws(function() {
fs.truncateSync(-1);
}, /path must be a string/);
assert.throws(function() {
fs.truncateSync(NaN);
}, /path must be a string/);
assert.throws(function() {
fs.truncateSync(Infinity);
}, /path must be a string/);
// Invalid lengths will also fail
assert.throws(function() {
fs.truncateSync('', '');
}, /length must be a positive integer/);
assert.throws(function() {
fs.truncateSync('', -1);
}, /length must be a positive integer/);
assert.throws(function() {
fs.truncateSync('', NaN);
}, /length must be a positive integer/);
assert.throws(function() {
fs.truncateSync('', Infinity);
}, /length must be a positive integer/);
// null is a special case which will also be treated as zero length
fs.writeFileSync(filename, data);
stat = fs.statSync(filename);
assert.equal(stat.size, 1024 * 16);
fs.truncateSync(filename, null);
stat = fs.statSync(filename);
assert.equal(stat.size, 0);
// ftruncateSync // ftruncateSync
fs.writeFileSync(filename, data); fs.writeFileSync(filename, data);
var fd = fs.openSync(filename, 'r+'); var fd = fs.openSync(filename, 'r+');
@ -41,6 +90,51 @@ assert.equal(stat.size, 0);
fs.closeSync(fd); fs.closeSync(fd);
// file descriptor must be a unsigned 32-bit integer
assert.throws(function() {
fs.ftruncateSync({});
}, /file descriptor must be a unsigned 32-bit integer/);
// Even invalid file descriptors are not allowed
assert.throws(function() {
fs.ftruncateSync(-1);
}, /file descriptor must be a unsigned 32-bit integer/);
assert.throws(function() {
fs.ftruncateSync(NaN);
}, /file descriptor must be a unsigned 32-bit integer/);
assert.throws(function() {
fs.ftruncateSync(Infinity);
}, /file descriptor must be a unsigned 32-bit integer/);
// Invalid lengths will also fail
assert.throws(function() {
fs.ftruncateSync(1, '');
}, /length must be a positive integer/);
assert.throws(function() {
fs.ftruncateSync(1, -1);
}, /length must be a positive integer/);
assert.throws(function() {
fs.ftruncateSync(1, NaN);
}, /length must be a positive integer/);
assert.throws(function() {
fs.ftruncateSync(1, Infinity);
}, /length must be a positive integer/);
// null is a special case which will also be treated as zero length
fs.writeFileSync(filename, data);
fd = fs.openSync(filename, 'r+');
fs.ftruncateSync(fd, null);
stat = fs.statSync(filename);
assert.equal(stat.size, 0);
fs.closeSync(fd);
// async tests // async tests
testTruncate(common.mustCall(function(er) { testTruncate(common.mustCall(function(er) {
if (er) throw er; if (er) throw er;

68
test/parallel/test-fs-utimes.js

@ -76,15 +76,6 @@ function runTest(atime, mtime, callback) {
} }
expect_errno('utimesSync', 'foobarbaz', err, 'ENOENT'); expect_errno('utimesSync', 'foobarbaz', err, 'ENOENT');
tests_run++; tests_run++;
err = undefined;
try {
fs.futimesSync(-1, atime, mtime);
} catch (ex) {
err = ex;
}
expect_errno('futimesSync', -1, err, 'EBADF');
tests_run++;
} }
// //
@ -105,13 +96,8 @@ function runTest(atime, mtime, callback) {
fs.futimes(fd, atime, mtime, function(err) { fs.futimes(fd, atime, mtime, function(err) {
expect_ok('futimes', fd, err, atime, mtime); expect_ok('futimes', fd, err, atime, mtime);
syncTests();
fs.futimes(-1, atime, mtime, function(err) { callback();
expect_errno('futimes', -1, err, 'EBADF');
syncTests();
callback();
});
tests_run++;
}); });
tests_run++; tests_run++;
}); });
@ -137,6 +123,56 @@ runTest(new Date('1982-09-10 13:37'), new Date('1982-09-10 13:37'), function() {
}); });
}); });
// validate if the file descriptors are in the valid range
assert.throws(() => fs.futimes(-1, () => {}),
/TypeError: file descriptor must be a unsigned 32-bit integer/);
assert.throws(() => fs.futimesSync(-1),
/TypeError: file descriptor must be a unsigned 32-bit integer/);
assert.throws(() => fs.futimes('-1', () => {}),
/TypeError: file descriptor must be a unsigned 32-bit integer/);
assert.throws(() => fs.futimesSync('-1'),
/TypeError: file descriptor must be a unsigned 32-bit integer/);
assert.throws(() => fs.futimes(0xFFFFFFFF + 1, () => {}),
/TypeError: file descriptor must be a unsigned 32-bit integer/);
assert.throws(() => fs.futimesSync(0xFFFFFFFF + 1),
/TypeError: file descriptor must be a unsigned 32-bit integer/);
assert.throws(() => fs.futimes('0x100000000', () => {}),
/TypeError: file descriptor must be a unsigned 32-bit integer/);
assert.throws(() => fs.futimesSync('0x100000000'),
/TypeError: file descriptor must be a unsigned 32-bit integer/);
// invalid file descriptors also should throw
assert.throws(() => fs.futimes(null, () => {}),
/TypeError: file descriptor must be a unsigned 32-bit integer/);
assert.throws(() => fs.futimesSync(null),
/TypeError: file descriptor must be a unsigned 32-bit integer/);
assert.throws(() => fs.futimes(undefined, () => {}),
/TypeError: file descriptor must be a unsigned 32-bit integer/);
assert.throws(() => fs.futimesSync(undefined),
/TypeError: file descriptor must be a unsigned 32-bit integer/);
assert.throws(() => fs.futimes(NaN, () => {}),
/TypeError: file descriptor must be a unsigned 32-bit integer/);
assert.throws(() => fs.futimesSync(NaN),
/TypeError: file descriptor must be a unsigned 32-bit integer/);
assert.throws(() => fs.futimes({}, () => {}),
/TypeError: file descriptor must be a unsigned 32-bit integer/);
assert.throws(() => fs.futimesSync({}),
/TypeError: file descriptor must be a unsigned 32-bit integer/);
assert.throws(() => fs.futimes([], () => {}),
/TypeError: file descriptor must be a unsigned 32-bit integer/);
assert.throws(() => fs.futimesSync([]),
/TypeError: file descriptor must be a unsigned 32-bit integer/);
assert.throws(() => fs.futimes(() => true, () => {}),
/TypeError: file descriptor must be a unsigned 32-bit integer/);
assert.throws(() => fs.futimesSync(() => true),
/TypeError: file descriptor must be a unsigned 32-bit integer/);
assert.throws(() => fs.futimes(() => /RegEx/, () => {}),
/TypeError: file descriptor must be a unsigned 32-bit integer/);
assert.throws(() => fs.futimesSync(() => /RegEx/),
/TypeError: file descriptor must be a unsigned 32-bit integer/);
assert.throws(() => fs.futimes(() => 3.14, () => {}),
/TypeError: file descriptor must be a unsigned 32-bit integer/);
assert.throws(() => fs.futimesSync(() => 3.14),
/TypeError: file descriptor must be a unsigned 32-bit integer/);
process.on('exit', function() { process.on('exit', function() {
console.log('Tests run / ok:', tests_run, '/', tests_ok); console.log('Tests run / ok:', tests_run, '/', tests_ok);

9
test/parallel/test-fs-write-no-fd.js

@ -1,12 +1,13 @@
'use strict'; 'use strict';
require('../common'); require('../common');
const fs = require('fs'); const fs = require('fs');
const assert = require('assert'); const assert = require('assert');
assert.throws(function() { assert.throws(function() {
fs.write(null, Buffer.allocUnsafe(1), 0, 1); fs.write(null, Buffer.allocUnsafe(1), 0, 1, () => {});
}, /TypeError/); }, /TypeError: file descriptor must be a unsigned 32-bit integer/);
assert.throws(function() { assert.throws(function() {
fs.write(null, '1', 0, 1); fs.write(undefined, '1', 0, 1, () => {});
}, /TypeError/); }, /TypeError: file descriptor must be a unsigned 32-bit integer/);

Loading…
Cancel
Save