#if HAVE_FFI #include "data.h" #include "base_object-inl.h" #include "node_errors.h" #include "util.h" #include "v8.h" #include #include #include #include #include using v8::ArrayBuffer; using v8::ArrayBufferView; using v8::BackingStore; using v8::BigInt; using v8::Context; using v8::FunctionCallbackInfo; using v8::Integer; using v8::Isolate; using v8::Just; using v8::JustVoid; using v8::Local; using v8::Maybe; using v8::MaybeLocal; using v8::NewStringType; using v8::Nothing; using v8::Number; using v8::Object; using v8::SharedArrayBuffer; using v8::String; using v8::Value; namespace node { namespace ffi { Maybe GetValidatedSize(Environment* env, Local value, const char* label) { if (!value->IsNumber()) { THROW_ERR_INVALID_ARG_VALUE(env, "The %s must be a number", label); return Nothing(); } double length = value.As()->Value(); if (!std::isfinite(length) || length < 0 || std::floor(length) != length) { THROW_ERR_INVALID_ARG_VALUE( env, "The %s must be a non-negative integer", label); return Nothing(); } if (length > static_cast(std::numeric_limits::max())) { THROW_ERR_OUT_OF_RANGE(env, "The %s is too large", label); return Nothing(); } return Just(static_cast(length)); } Maybe GetValidatedPointerAddress(Environment* env, Local value, const char* label) { if (!value->IsBigInt()) { THROW_ERR_INVALID_ARG_VALUE(env, "The %s must be a bigint", label); return Nothing(); } bool lossless; uint64_t address = value.As()->Uint64Value(&lossless); if (!lossless) { THROW_ERR_INVALID_ARG_VALUE( env, "The %s must be a non-negative bigint", label); return Nothing(); } if (address > static_cast(std::numeric_limits::max())) { THROW_ERR_INVALID_ARG_VALUE( env, "The %s exceeds the platform pointer range", label); return Nothing(); } return Just(static_cast(address)); } Maybe GetValidatedSignedInt(Environment* env, Local value, int64_t min, int64_t max, const char* type_name) { if (!value->IsNumber()) { THROW_ERR_INVALID_ARG_VALUE(env, "Value must be an %s", type_name); return Nothing(); } double number = value.As()->Value(); if (!std::isfinite(number) || std::floor(number) != number || number < min || number > max) { THROW_ERR_INVALID_ARG_VALUE(env, "Value must be an %s", type_name); return Nothing(); } return Just(static_cast(number)); } Maybe GetValidatedUnsignedInt(Environment* env, Local value, uint64_t max, const char* type_name) { if (!value->IsNumber()) { THROW_ERR_INVALID_ARG_VALUE(env, "Value must be a %s", type_name); return Nothing(); } double number = value.As()->Value(); if (!std::isfinite(number) || std::floor(number) != number || number < 0 || number > static_cast(max)) { THROW_ERR_INVALID_ARG_VALUE(env, "Value must be a %s", type_name); return Nothing(); } return Just(static_cast(number)); } Maybe ValidatePointerSpan(Environment* env, uintptr_t raw_ptr, size_t offset, size_t length, const char* error_message) { if (offset > std::numeric_limits::max() - raw_ptr) { THROW_ERR_INVALID_ARG_VALUE(env, error_message); return Nothing(); } uintptr_t start = raw_ptr + offset; if (length > 0 && length - 1 > std::numeric_limits::max() - start) { THROW_ERR_INVALID_ARG_VALUE(env, error_message); return Nothing(); } return JustVoid(); } Maybe ValidateBufferLength(Environment* env, size_t len) { if (len > Buffer::kMaxLength) { THROW_ERR_BUFFER_TOO_LARGE(env, "Buffer is too large"); return Nothing(); } return JustVoid(); } Maybe ValidateStringLength(Environment* env, size_t len) { if (len > static_cast(String::kMaxLength)) { THROW_ERR_STRING_TOO_LONG(env, "String is too long"); return Nothing(); } return JustVoid(); } Maybe> GetValidatedPointerAndOffset( Environment* env, const FunctionCallbackInfo& args) { uintptr_t raw_ptr; if (args.Length() < 1 || !GetValidatedPointerAddress(env, args[0], "pointer").To(&raw_ptr)) { return {}; } if (raw_ptr == 0) { THROW_ERR_FFI_INVALID_POINTER(env, "Cannot dereference a null pointer"); return {}; } size_t offset = 0; if (args.Length() > 1 && !args[1]->IsUndefined()) { if (!GetValidatedSize(env, args[1], "offset").To(&offset)) { return {}; } } if (ValidatePointerSpan( env, raw_ptr, offset, 1, "The pointer and offset exceed the platform address range") .IsNothing()) { return {}; } return Just(std::make_pair(reinterpret_cast(raw_ptr), offset)); } struct PointerOffsetAndValue { uint8_t* ptr; size_t offset; Local value; }; Maybe GetValidatedPointerOffsetAndValue( Environment* env, const FunctionCallbackInfo& args) { size_t offset; Local value; uintptr_t raw_ptr; if (args.Length() < 1 || !GetValidatedPointerAddress(env, args[0], "pointer").To(&raw_ptr)) { return {}; } if (raw_ptr == 0) { THROW_ERR_FFI_INVALID_POINTER(env, "Cannot dereference a null pointer"); return {}; } if (args.Length() < 2 || args[1]->IsUndefined()) { THROW_ERR_INVALID_ARG_VALUE(env, "Expected an offset argument"); return {}; } if (!GetValidatedSize(env, args[1], "offset").To(&offset)) { return {}; } if (ValidatePointerSpan( env, raw_ptr, offset, 1, "The pointer and offset exceed the platform address range") .IsNothing()) { return {}; } if (args.Length() < 3 || args[2]->IsUndefined()) { THROW_ERR_INVALID_ARG_VALUE(env, "Expected a value argument"); return {}; } value = args[2]; uint8_t* ptr = reinterpret_cast(static_cast(raw_ptr)); return Just(PointerOffsetAndValue{ptr, offset, value}); } template void GetValue(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); THROW_IF_INSUFFICIENT_PERMISSIONS(env, permission::PermissionScope::kFFI, ""); Isolate* isolate = env->isolate(); std::pair ptr_and_offset; if (!GetValidatedPointerAndOffset(env, args).To(&ptr_and_offset)) { return; } auto [ptr, offset] = ptr_and_offset; uintptr_t raw_ptr = reinterpret_cast(ptr); if (ValidatePointerSpan( env, raw_ptr, offset, sizeof(T), "The accessed range exceeds the platform address range") .IsNothing()) { return; } T value; std::memcpy(&value, ptr + offset, sizeof(value)); if constexpr (std::is_same_v || std::is_same_v || std::is_same_v) { args.GetReturnValue().Set(Integer::New(isolate, value)); } else if constexpr (std::is_same_v || std::is_same_v || std::is_same_v) { args.GetReturnValue().Set(Integer::NewFromUnsigned(isolate, value)); } else if constexpr (std::is_same_v) { args.GetReturnValue().Set(BigInt::New(isolate, value)); } else if constexpr (std::is_same_v) { args.GetReturnValue().Set(BigInt::NewFromUnsigned(isolate, value)); } else if constexpr (std::is_same_v || std::is_same_v) { args.GetReturnValue().Set(Number::New(isolate, value)); } else { UNREACHABLE(); } } template void SetValue(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); THROW_IF_INSUFFICIENT_PERMISSIONS(env, permission::PermissionScope::kFFI, ""); PointerOffsetAndValue data; if (!GetValidatedPointerOffsetAndValue(env, args).To(&data)) { return; } auto [ptr, offset, value] = data; uintptr_t raw_ptr = reinterpret_cast(ptr); if (ValidatePointerSpan( env, raw_ptr, offset, sizeof(T), "The accessed range exceeds the platform address range") .IsNothing()) { return; } T converted; Local context = env->context(); if constexpr (std::is_same_v) { int64_t validated; if (!GetValidatedSignedInt(env, value, INT8_MIN, INT8_MAX, "int8") .To(&validated)) { return; } converted = static_cast(validated); } else if constexpr (std::is_same_v) { uint64_t validated; if (!GetValidatedUnsignedInt(env, value, UINT8_MAX, "uint8") .To(&validated)) { return; } converted = static_cast(validated); } else if constexpr (std::is_same_v) { int64_t validated; if (!GetValidatedSignedInt(env, value, INT16_MIN, INT16_MAX, "int16") .To(&validated)) { return; } converted = static_cast(validated); } else if constexpr (std::is_same_v) { uint64_t validated; if (!GetValidatedUnsignedInt(env, value, UINT16_MAX, "uint16") .To(&validated)) { return; } converted = static_cast(validated); } else if constexpr (std::is_same_v) { int64_t validated; if (!GetValidatedSignedInt(env, value, INT32_MIN, INT32_MAX, "int32") .To(&validated)) { return; } converted = static_cast(validated); } else if constexpr (std::is_same_v) { uint64_t validated; if (!GetValidatedUnsignedInt(env, value, UINT32_MAX, "uint32") .To(&validated)) { return; } converted = static_cast(validated); } else if constexpr (std::is_same_v) { if (value->IsBigInt()) { bool lossless; converted = static_cast(value.As()->Int64Value(&lossless)); if (!lossless) { THROW_ERR_INVALID_ARG_VALUE(env, "Value must be an int64"); return; } } else if (value->IsNumber()) { int64_t validated; if (!GetValidatedSignedInt(env, value, -static_cast(kMaxSafeJsInteger), static_cast(kMaxSafeJsInteger), "int64") .To(&validated)) { return; } converted = static_cast(validated); } else { THROW_ERR_INVALID_ARG_VALUE(env, "Value must be a bigint or a number"); return; } } else if constexpr (std::is_same_v) { if (value->IsBigInt()) { bool lossless; converted = static_cast(value.As()->Uint64Value(&lossless)); if (!lossless) { THROW_ERR_INVALID_ARG_VALUE(env, "Value must be a uint64"); return; } } else if (value->IsNumber()) { uint64_t validated; if (!GetValidatedUnsignedInt( env, value, static_cast(kMaxSafeJsInteger), "uint64") .To(&validated)) { return; } converted = static_cast(validated); } else { THROW_ERR_INVALID_ARG_VALUE(env, "Value must be a bigint or a number"); return; } } else if constexpr (std::is_same_v || std::is_same_v) { MaybeLocal number = value->ToNumber(context); Local number_local; if (!number.ToLocal(&number_local)) { THROW_ERR_INVALID_ARG_VALUE(env, "Value must be a number"); return; } converted = static_cast(number_local->Value()); } else { UNREACHABLE(); } std::memcpy(ptr + offset, &converted, sizeof(converted)); } // Raw FFI memory helpers are low-level and unsafe. Pointer validity, // lifetime, and zero-copy writable views are the caller's responsibility. void GetInt8(const FunctionCallbackInfo& args) { GetValue(args); } void GetUint8(const FunctionCallbackInfo& args) { GetValue(args); } void GetInt16(const FunctionCallbackInfo& args) { GetValue(args); } void GetUint16(const FunctionCallbackInfo& args) { GetValue(args); } void GetInt32(const FunctionCallbackInfo& args) { GetValue(args); } void GetUint32(const FunctionCallbackInfo& args) { GetValue(args); } void GetInt64(const FunctionCallbackInfo& args) { GetValue(args); } void GetUint64(const FunctionCallbackInfo& args) { GetValue(args); } void GetFloat32(const FunctionCallbackInfo& args) { GetValue(args); } void GetFloat64(const FunctionCallbackInfo& args) { GetValue(args); } void SetInt8(const FunctionCallbackInfo& args) { SetValue(args); } void SetUint8(const FunctionCallbackInfo& args) { SetValue(args); } void SetInt16(const FunctionCallbackInfo& args) { SetValue(args); } void SetUint16(const FunctionCallbackInfo& args) { SetValue(args); } void SetInt32(const FunctionCallbackInfo& args) { SetValue(args); } void SetUint32(const FunctionCallbackInfo& args) { SetValue(args); } void SetInt64(const FunctionCallbackInfo& args) { SetValue(args); } void SetUint64(const FunctionCallbackInfo& args) { SetValue(args); } void SetFloat32(const FunctionCallbackInfo& args) { SetValue(args); } void SetFloat64(const FunctionCallbackInfo& args) { SetValue(args); } void ToString(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); Isolate* isolate = env->isolate(); // Raw FFI memory helpers are low-level and unsafe. Pointer validity, // lifetime, and zero-copy writable views are the caller's responsibility. // `toString()` requires a valid NUL-terminated C string pointer. THROW_IF_INSUFFICIENT_PERMISSIONS(env, permission::PermissionScope::kFFI, ""); if (args.Length() < 1 || !args[0]->IsBigInt()) { THROW_ERR_INVALID_ARG_TYPE(env, "The first argument must be a bigint"); return; } uintptr_t ptr; if (!GetValidatedPointerAddress(env, args[0], "first argument").To(&ptr)) { return; } if (ptr == 0) { args.GetReturnValue().SetNull(); return; } const char* str = reinterpret_cast(ptr); size_t len = std::strlen(str); if (ValidateStringLength(env, len).IsNothing()) { return; } Local out; if (!String::NewFromUtf8(isolate, str, NewStringType::kNormal, len) .ToLocal(&out)) { return; } args.GetReturnValue().Set(out); } void ToBuffer(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); Isolate* isolate = env->isolate(); THROW_IF_INSUFFICIENT_PERMISSIONS(env, permission::PermissionScope::kFFI, ""); // `copy === false` exposes a zero-copy writable view over foreign memory. // This is intentionally unsafe and the caller must guarantee pointer // validity, lifetime, and bounds. if (args.Length() < 1 || !args[0]->IsBigInt()) { THROW_ERR_INVALID_ARG_TYPE(env, "The first argument must be a bigint"); return; } uintptr_t ptr; if (!GetValidatedPointerAddress(env, args[0], "first argument").To(&ptr)) { return; } size_t len; if (args.Length() < 2 || !GetValidatedSize(env, args[1], "length").To(&len)) { return; } if (ptr == 0 && len > 0) { THROW_ERR_FFI_INVALID_POINTER(env, "Cannot create a buffer from a null pointer"); return; } if (ValidatePointerSpan( env, ptr, 0, len, "The pointer and length exceed the platform address range") .IsNothing()) { return; } if (ValidateBufferLength(env, len).IsNothing()) { return; } Local buf; if (args.Length() < 3 || args[2]->IsUndefined() || args[2]->BooleanValue(isolate)) { if (!Buffer::Copy(env, reinterpret_cast(ptr), len).ToLocal(&buf)) { return; } } else { if (!Buffer::New( env, reinterpret_cast(ptr), len, [](char* data, void* hint) {}, nullptr) .ToLocal(&buf)) { return; } } args.GetReturnValue().Set(buf); } void ToArrayBuffer(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); Isolate* isolate = env->isolate(); THROW_IF_INSUFFICIENT_PERMISSIONS(env, permission::PermissionScope::kFFI, ""); if (args.Length() < 1 || !args[0]->IsBigInt()) { THROW_ERR_INVALID_ARG_TYPE(env, "The first argument must be a bigint"); return; } uintptr_t ptr; if (!GetValidatedPointerAddress(env, args[0], "first argument").To(&ptr)) { return; } size_t len; if (args.Length() < 2 || !GetValidatedSize(env, args[1], "length").To(&len)) { return; } if (ptr == 0 && len > 0) { THROW_ERR_FFI_INVALID_POINTER( env, "Cannot create an ArrayBuffer from a null pointer"); return; } if (ValidatePointerSpan( env, ptr, 0, len, "The pointer and length exceed the platform address range") .IsNothing()) { return; } if (ValidateBufferLength(env, len).IsNothing()) { return; } Local ab; if (args.Length() < 3 || args[2]->IsUndefined() || args[2]->BooleanValue(isolate)) { std::unique_ptr store = ArrayBuffer::NewBackingStore(isolate, len); memcpy(store->Data(), reinterpret_cast(ptr), len); ab = ArrayBuffer::New(isolate, std::move(store)); } else { std::unique_ptr store = ArrayBuffer::NewBackingStore( reinterpret_cast(ptr), len, [](void* data, size_t length, void* deleter_data) {}, nullptr); ab = ArrayBuffer::New(isolate, std::move(store)); } args.GetReturnValue().Set(ab); } void ExportBytes(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); THROW_IF_INSUFFICIENT_PERMISSIONS(env, permission::PermissionScope::kFFI, ""); if (args.Length() < 1) { THROW_ERR_INVALID_ARG_TYPE( env, "The first argument must be a Buffer, ArrayBuffer, or ArrayBufferView"); return; } // This needs to be kept alive until the data // is actually copied. ArrayBufferViewContents view; if (args[0]->IsArrayBuffer() || args[0]->IsSharedArrayBuffer() || args[0]->IsArrayBufferView()) { view.ReadValue(args[0]); if (view.WasDetached()) { THROW_ERR_INVALID_ARG_VALUE(env, "Invalid ArrayBufferView backing store"); return; } } else { THROW_ERR_INVALID_ARG_TYPE( env, "The first argument must be a Buffer, ArrayBuffer, or ArrayBufferView"); return; } uintptr_t ptr; if (args.Length() < 2 || !GetValidatedPointerAddress(env, args[1], "pointer").To(&ptr)) { return; } size_t len; if (args.Length() < 3 || !GetValidatedSize(env, args[2], "length").To(&len)) { return; } if (len < view.length()) { THROW_ERR_OUT_OF_RANGE(env, "The length must be >= source byte length"); return; } if (ptr == 0 && view.length() > 0) { THROW_ERR_FFI_INVALID_POINTER(env, "Cannot create a buffer from a null pointer"); return; } if (ValidatePointerSpan( env, ptr, 0, len, "The pointer and length exceed the platform address range") .IsNothing()) { return; } std::memcpy(reinterpret_cast(ptr), view.data(), view.length()); } void GetRawPointer(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); Isolate* isolate = env->isolate(); THROW_IF_INSUFFICIENT_PERMISSIONS(env, permission::PermissionScope::kFFI, ""); if (args.Length() < 1) { THROW_ERR_INVALID_ARG_TYPE( env, "The first argument must be a Buffer, ArrayBuffer, or ArrayBufferView"); return; } uintptr_t ptr = 0; size_t offset = 0; std::shared_ptr store; if (args[0]->IsArrayBuffer()) { store = args[0].As()->GetBackingStore(); } else if (args[0]->IsSharedArrayBuffer()) { store = args[0].As()->GetBackingStore(); } else if (args[0]->IsArrayBufferView()) { // Access the store here to ensure that it exists. Small typed arrays // may not have a store until this point and can instead be stored // entirely in-heap. store = args[0].As()->Buffer()->GetBackingStore(); offset = args[0].As()->ByteOffset(); } else { THROW_ERR_INVALID_ARG_TYPE(env, "The first argument must be a Buffer, " "ArrayBuffer, or ArrayBufferView"); return; } // WARNING: There is no inherent guarantee that the pointer returned // from this function will be valid beyond the lifetime of the BackingStore // instance! if (!store) { THROW_ERR_INVALID_ARG_VALUE(env, "Invalid ArrayBuffer backing store"); return; } ptr = reinterpret_cast(store->Data()) + offset; args.GetReturnValue().Set( BigInt::NewFromUnsigned(isolate, static_cast(ptr))); } } // namespace ffi } // namespace node #endif // HAVE_FFI