Skip to content

Commit

Permalink
fs: validate fd for sync calls on c++
Browse files Browse the repository at this point in the history
  • Loading branch information
anonrig committed Dec 5, 2023
1 parent 2e458d9 commit 301c45b
Show file tree
Hide file tree
Showing 8 changed files with 189 additions and 56 deletions.
27 changes: 6 additions & 21 deletions lib/fs.js
Original file line number Diff line number Diff line change
Expand Up @@ -513,13 +513,12 @@ function defaultCloseCallback(err) {
* @returns {void}
*/
function close(fd, callback = defaultCloseCallback) {
fd = getValidatedFd(fd);
if (callback !== defaultCloseCallback)
callback = makeCallback(callback);

const req = new FSReqCallback();
req.oncomplete = callback;
binding.close(fd, req);
binding.close(getValidatedFd(fd), req);
}

/**
Expand All @@ -528,9 +527,7 @@ function close(fd, callback = defaultCloseCallback) {
* @returns {void}
*/
function closeSync(fd) {
fd = getValidatedFd(fd);

binding.close(fd);
binding.close(getValidatedFd(fd));
}

/**
Expand Down Expand Up @@ -1110,7 +1107,6 @@ function ftruncate(fd, len = 0, callback) {
callback = len;
len = 0;
}
fd = getValidatedFd(fd);
validateInteger(len, 'len');
len = MathMax(0, len);
callback = makeCallback(callback);
Expand All @@ -1127,7 +1123,6 @@ function ftruncate(fd, len = 0, callback) {
* @returns {void}
*/
function ftruncateSync(fd, len = 0) {
fd = getValidatedFd(fd);
validateInteger(len, 'len');
len = MathMax(0, len);
binding.ftruncate(fd, len);
Expand Down Expand Up @@ -1279,7 +1274,6 @@ function rmSync(path, options) {
* @returns {void}
*/
function fdatasync(fd, callback) {
fd = getValidatedFd(fd);
const req = new FSReqCallback();
req.oncomplete = makeCallback(callback);
binding.fdatasync(fd, req);
Expand All @@ -1293,7 +1287,6 @@ function fdatasync(fd, callback) {
* @returns {void}
*/
function fdatasyncSync(fd) {
fd = getValidatedFd(fd);
binding.fdatasync(fd);
}

Expand All @@ -1305,7 +1298,6 @@ function fdatasyncSync(fd) {
* @returns {void}
*/
function fsync(fd, callback) {
fd = getValidatedFd(fd);
const req = new FSReqCallback();
req.oncomplete = makeCallback(callback);
binding.fsync(fd, req);
Expand All @@ -1318,7 +1310,6 @@ function fsync(fd, callback) {
* @returns {void}
*/
function fsyncSync(fd) {
fd = getValidatedFd(fd);
binding.fsync(fd);
}

Expand Down Expand Up @@ -1539,7 +1530,6 @@ function fstat(fd, options = { bigint: false }, callback) {
callback = options;
options = kEmptyObject;
}
fd = getValidatedFd(fd);
callback = makeStatsCallback(callback);

const req = new FSReqCallback(options.bigint);
Expand Down Expand Up @@ -1622,7 +1612,6 @@ function statfs(path, options = { bigint: false }, callback) {
* @returns {Stats | undefined}
*/
function fstatSync(fd, options = { bigint: false }) {
fd = getValidatedFd(fd);
const stats = binding.fstat(fd, options.bigint, undefined, false);
if (stats === undefined) {
return;
Expand Down Expand Up @@ -1888,7 +1877,6 @@ function unlinkSync(path) {
* @returns {void}
*/
function fchmod(fd, mode, callback) {
fd = getValidatedFd(fd);
mode = parseFileMode(mode, 'mode');
callback = makeCallback(callback);

Expand All @@ -1905,7 +1893,7 @@ function fchmod(fd, mode, callback) {
*/
function fchmodSync(fd, mode) {
binding.fchmod(
getValidatedFd(fd),
fd,
parseFileMode(mode, 'mode'),
);
}
Expand Down Expand Up @@ -2033,14 +2021,13 @@ function lchownSync(path, uid, gid) {
* @returns {void}
*/
function fchown(fd, uid, gid, callback) {
fd = getValidatedFd(fd);
validateInteger(uid, 'uid', -1, kMaxUserId);
validateInteger(gid, 'gid', -1, kMaxUserId);
callback = makeCallback(callback);

const req = new FSReqCallback();
req.oncomplete = callback;
binding.fchown(fd, uid, gid, req);
binding.fchown(getValidatedFd(fd), uid, gid, req);
}

/**
Expand All @@ -2051,12 +2038,11 @@ function fchown(fd, uid, gid, callback) {
* @returns {void}
*/
function fchownSync(fd, uid, gid) {
fd = getValidatedFd(fd);
validateInteger(uid, 'uid', -1, kMaxUserId);
validateInteger(gid, 'gid', -1, kMaxUserId);

const ctx = {};
binding.fchown(fd, uid, gid, undefined, ctx);
binding.fchown(getValidatedFd(fd), uid, gid, undefined, ctx);
handleErrorFromBinding(ctx);
}

Expand Down Expand Up @@ -2147,7 +2133,6 @@ function utimesSync(path, atime, mtime) {
* @returns {void}
*/
function futimes(fd, atime, mtime, callback) {
fd = getValidatedFd(fd);
atime = toUnixTimestamp(atime, 'atime');
mtime = toUnixTimestamp(mtime, 'mtime');
callback = makeCallback(callback);
Expand All @@ -2168,7 +2153,7 @@ function futimes(fd, atime, mtime, callback) {
*/
function futimesSync(fd, atime, mtime) {
binding.futimes(
getValidatedFd(fd),
fd,
toUnixTimestamp(atime, 'atime'),
toUnixTimestamp(mtime, 'mtime'),
);
Expand Down
27 changes: 17 additions & 10 deletions src/node_file.cc
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,15 @@
#include "req_wrap-inl.h"
#include "stream_base-inl.h"
#include "string_bytes.h"
#include "util.h"

#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <cstring>
#include <sys/types.h>
#include <cerrno>
#include <climits>
#include <cstdio>
#include <cstring>

#if defined(__MINGW32__) || defined(_MSC_VER)
# include <io.h>
Expand Down Expand Up @@ -1154,7 +1156,7 @@ static void FStat(const FunctionCallbackInfo<Value>& args) {
const int argc = args.Length();
CHECK_GE(argc, 2);

CHECK(args[0]->IsInt32());
VALIDATE_FD(env, args[0], false);
int fd = args[0].As<Int32>()->Value();

bool use_bigint = args[1]->IsTrue();
Expand Down Expand Up @@ -1405,18 +1407,18 @@ static void FTruncate(const FunctionCallbackInfo<Value>& args) {
const int argc = args.Length();
CHECK_GE(argc, 2);

CHECK(args[0]->IsInt32());
VALIDATE_FD(env, args[0], false);
const int fd = args[0].As<Int32>()->Value();

CHECK(IsSafeJsInt(args[1]));
const int64_t len = args[1].As<Integer>()->Value();

if (argc > 2) {
if (argc > 2) { // ftruncate(fd, len, req)
FSReqBase* req_wrap_async = GetReqWrap(args, 2);
FS_ASYNC_TRACE_BEGIN0(UV_FS_FTRUNCATE, req_wrap_async)
AsyncCall(env, req_wrap_async, args, "ftruncate", UTF8, AfterNoArgs,
uv_fs_ftruncate, fd, len);
} else {
} else { // ftruncate(fd, len)
FSReqWrapSync req_wrap_sync("ftruncate");
FS_SYNC_TRACE_BEGIN(ftruncate);
SyncCallAndThrowOnError(env, &req_wrap_sync, uv_fs_ftruncate, fd, len);
Expand All @@ -1430,7 +1432,7 @@ static void Fdatasync(const FunctionCallbackInfo<Value>& args) {
const int argc = args.Length();
CHECK_GE(argc, 1);

CHECK(args[0]->IsInt32());
VALIDATE_FD(env, args[0], false);
const int fd = args[0].As<Int32>()->Value();

if (argc > 1) { // fdatasync(fd, req)
Expand All @@ -1453,7 +1455,7 @@ static void Fsync(const FunctionCallbackInfo<Value>& args) {
const int argc = args.Length();
CHECK_GE(argc, 1);

CHECK(args[0]->IsInt32());
VALIDATE_FD(env, args[0], false);
const int fd = args[0].As<Int32>()->Value();

if (argc > 1) {
Expand Down Expand Up @@ -2525,8 +2527,13 @@ static void FChmod(const FunctionCallbackInfo<Value>& args) {
const int argc = args.Length();
CHECK_GE(argc, 2);

CHECK(args[0]->IsInt32());
VALIDATE_FD(env, args[0], false);
auto t = args[0]->TypeOf(env->isolate());
Utf8Value type(env->isolate(), t);
printf("type of args[0] is %s\n", type.out());
printf("int32max %s\n", std::to_string(INT32_MAX).c_str());
const int fd = args[0].As<Int32>()->Value();
printf("fd is %d\n", fd);

CHECK(args[1]->IsInt32());
const int mode = args[1].As<Int32>()->Value();
Expand Down Expand Up @@ -2683,7 +2690,7 @@ static void FUTimes(const FunctionCallbackInfo<Value>& args) {
const int argc = args.Length();
CHECK_GE(argc, 3);

CHECK(args[0]->IsInt32());
VALIDATE_FD(env, args[0], false);
const int fd = args[0].As<Int32>()->Value();

CHECK(args[1]->IsNumber());
Expand Down
50 changes: 50 additions & 0 deletions src/util.cc
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
// USE OR OTHER DEALINGS IN THE SOFTWARE.

#include "util.h" // NOLINT(build/include_inline)
#include <cmath>
#include "debug_utils.h"
#include "util-inl.h"

#include "debug_utils-inl.h"
Expand All @@ -31,6 +33,7 @@
#include "node_v8_platform-inl.h"
#include "string_bytes.h"
#include "uv.h"
#include "v8-value.h"

#ifdef _WIN32
#include <io.h> // _S_IREAD _S_IWRITE
Expand Down Expand Up @@ -702,4 +705,51 @@ RAIIIsolate::RAIIIsolate(const SnapshotData* data)

RAIIIsolate::~RAIIIsolate() {}

// Returns a string representation of the input value, including type.
// JavaScript implementation is available in lib/internal/errors.js
std::string DetermineSpecificErrorType(Environment* env,
v8::Local<v8::Value> input) {
if (input->IsFunction()) {
return "function";
} else if (input->IsString()) {
auto value = Utf8Value(env->isolate(), input).ToString();
if (value.size() > 28) {
value = value.substr(0, 25) + "...";
}
if (value.find('\'') == std::string::npos) {
return SPrintF("type string ('%s')", value);
}

// Stringify the input value.
Local<String> stringified =
v8::JSON::Stringify(env->context(), input).ToLocalChecked();
Utf8Value stringified_value(env->isolate(), stringified);
return SPrintF("type string (%s)", stringified_value.out());
} else if (input->IsObject()) {
v8::Local<v8::String> constructor_name =
input.As<v8::Object>()->GetConstructorName();
Utf8Value name(env->isolate(), constructor_name);
return SPrintF("an instance of %s", name.out());
}

Utf8Value utf8_value(env->isolate(),
input->ToString(env->context()).ToLocalChecked());

if (input->IsNumber() || input->IsInt32() || input->IsUint32()) {
auto value = input.As<v8::Number>()->Value();
if (std::isnan(value)) {
return "type number (NaN)";
} else if (std::isinf(value)) {
return "type number (Infinity)";
}
return SPrintF("type number (%s)", utf8_value.out());
} else if (input->IsBigInt() || input->IsBoolean() || input->IsSymbol()) {
Utf8Value type(env->isolate(), input->TypeOf(env->isolate()));
return SPrintF("type %s (%s)", type.out(), utf8_value.out());
}

// For example: null, undefined
return utf8_value.ToString();
}

} // namespace node
39 changes: 39 additions & 0 deletions src/util.h
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,42 @@ void DumpJavaScriptBacktrace(FILE* fp);
#define UNREACHABLE(...) \
ERROR_AND_ABORT("Unreachable code reached" __VA_OPT__(": ") __VA_ARGS__)

#define VALIDATE_FD(env, input, has_req) \
do { \
if (has_req) { \
CHECK(input->IsInt32()); \
return; \
} \
if (!input->IsInt32() && !input->IsNumber()) { \
std::string error_type = node::DetermineSpecificErrorType(env, input); \
THROW_ERR_INVALID_ARG_TYPE(env, \
"The \"fd\" argument must be of type " \
"number. Received %s", \
error_type.c_str()); \
return; \
} \
const auto fd = input.As<Number>()->Value(); \
const bool is_out_of_range = fd < 0 || fd > INT32_MAX; \
if (is_out_of_range || !IsSafeJsInt(input)) { \
Utf8Value utf8_value(env->isolate(), \
input->ToString(env->context()).ToLocalChecked()); \
if (is_out_of_range && !std::isinf(fd)) { \
THROW_ERR_OUT_OF_RANGE(env, \
"The value of \"fd\" is out of range. " \
"It must be >= 0 && <= %s. Received %d", \
std::to_string(INT32_MAX), \
utf8_value.out()); \
} else { \
THROW_ERR_OUT_OF_RANGE( \
env, \
"The value of \"fd\" is out of range. It must be an integer. " \
"Received %s", \
utf8_value.out()); \
} \
return; \
} \
} while (0);

// ECMA262 20.1.2.6 Number.MAX_SAFE_INTEGER (2^53-1)
constexpr int64_t kMaxSafeJsInteger = 9007199254740991;

Expand Down Expand Up @@ -996,6 +1032,9 @@ class RAIIIsolate {
v8::Isolate::Scope isolate_scope_;
};

std::string DetermineSpecificErrorType(Environment* env,
v8::Local<v8::Value> input);

} // namespace node

#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
Expand Down
Loading

0 comments on commit 301c45b

Please sign in to comment.