Commit 1c12ff16675 for nodejs
commit 1c12ff166756c8ddd830cb4742953314065fcacc
Author: Christian Aurich Zanettini Martins <christian.aurichzm@gmail.com>
Date: Thu Oct 22 15:49:29 2026 -0300
src: avoid truncating Blob sizes to 32 bits
Blob sizes cross between C++ and JavaScript as uint32 values, while the
underlying DataQueue tracks them as uint64. openAsBlob() on a 5 GiB
file therefore returned a Blob of size 1073741824.
Reporting the real size is not enough on its own. The inbound direction
assumes a uint32 as well, so a correct size reaches CHECK(IsUint32())
in ToSlice() and StoreDataObject() and aborts blob.slice() and
URL.createObjectURL(). Pass the sizes as doubles and keep them as
uint64_t in C++. Everything below this boundary is already 64-bit, so
this only stops the narrowing at the edge.
Signed-off-by: Christian Aurich Zanettini Martins <christian.aurichzm@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/66207
Fixes: https://github.com/nodejs/node/issues/52585
Refs: https://github.com/nodejs/node/pull/52588
Reviewed-By: Filip Skokan <panva.ip@gmail.com>
diff --git a/src/node_blob.cc b/src/node_blob.cc
index a04bc2be0ef..ab4267fb81f 100644
--- a/src/node_blob.cc
+++ b/src/node_blob.cc
@@ -33,6 +33,7 @@ using v8::Int32;
using v8::Isolate;
using v8::Local;
using v8::NewStringType;
+using v8::Number;
using v8::Object;
using v8::ObjectTemplate;
using v8::SnapshotCreator;
@@ -125,7 +126,7 @@ void BlobFromFilePath(const FunctionCallbackInfo<Value>& args) {
Blob::Create(env, DataQueue::CreateIdempotent(std::move(entries)))) {
Local<Value> vals[2]{
blob->object(),
- Uint32::NewFromUnsigned(env->isolate(), blob->length()),
+ Number::New(env->isolate(), static_cast<double>(blob->length())),
};
args.GetReturnValue().Set(
Array::New(env->isolate(), &vals[0], arraysize(vals)));
@@ -274,10 +275,10 @@ void Blob::ToSlice(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
Blob* blob;
ASSIGN_OR_RETURN_UNWRAP(&blob, args.This());
- CHECK(args[0]->IsUint32());
- CHECK(args[1]->IsUint32());
- size_t start = args[0].As<Uint32>()->Value();
- size_t end = args[1].As<Uint32>()->Value();
+ CHECK(args[0]->IsNumber());
+ CHECK(args[1]->IsNumber());
+ uint64_t start = static_cast<uint64_t>(args[0].As<Number>()->Value());
+ uint64_t end = static_cast<uint64_t>(args[1].As<Number>()->Value());
BaseObjectPtr<Blob> slice = blob->Slice(env, start, end);
if (slice) args.GetReturnValue().Set(slice->object());
}
@@ -286,9 +287,10 @@ void Blob::MemoryInfo(MemoryTracker* tracker) const {
tracker->TrackField("data_queue_", data_queue_, "std::shared_ptr<DataQueue>");
}
-BaseObjectPtr<Blob> Blob::Slice(Environment* env, size_t start, size_t end) {
- return Create(env,
- this->data_queue_->slice(start, static_cast<uint64_t>(end)));
+BaseObjectPtr<Blob> Blob::Slice(Environment* env,
+ uint64_t start,
+ uint64_t end) {
+ return Create(env, this->data_queue_->slice(start, end));
}
Blob::Blob(Environment* env,
@@ -459,7 +461,7 @@ void Blob::StoreDataObject(const FunctionCallbackInfo<Value>& args) {
CHECK(args[0]->IsString()); // ID key
CHECK(Blob::HasInstance(realm->env(), args[1])); // Blob
- CHECK(args[2]->IsUint32()); // Length
+ CHECK(args[2]->IsNumber()); // Length
CHECK(args[3]->IsString()); // Type
BlobBindingData* binding_data = realm->GetBindingData<BlobBindingData>();
@@ -469,7 +471,7 @@ void Blob::StoreDataObject(const FunctionCallbackInfo<Value>& args) {
Blob* blob;
ASSIGN_OR_RETURN_UNWRAP(&blob, args[1]);
- size_t length = args[2].As<Uint32>()->Value();
+ uint64_t length = static_cast<uint64_t>(args[2].As<Number>()->Value());
Utf8Value type(isolate, args[3]);
binding_data->store_data_object(
@@ -526,9 +528,10 @@ void Blob::GetDataObject(const FunctionCallbackInfo<Value>& args) {
return;
}
- Local<Value> values[] = {stored.blob->object(),
- Uint32::NewFromUnsigned(isolate, stored.length),
- type};
+ Local<Value> values[] = {
+ stored.blob->object(),
+ Number::New(isolate, static_cast<double>(stored.length)),
+ type};
args.GetReturnValue().Set(Array::New(isolate, values, arraysize(values)));
}
@@ -540,7 +543,9 @@ void BlobBindingData::StoredDataObject::MemoryInfo(
}
BlobBindingData::StoredDataObject::StoredDataObject(
- const BaseObjectPtr<Blob>& blob_, size_t length_, const std::string& type_)
+ const BaseObjectPtr<Blob>& blob_,
+ uint64_t length_,
+ const std::string& type_)
: blob(blob_), length(length_), type(type_) {}
BlobBindingData::BlobBindingData(Realm* realm, Local<Object> wrap)
diff --git a/src/node_blob.h b/src/node_blob.h
index 06d3a151cfa..a3016979dfe 100644
--- a/src/node_blob.h
+++ b/src/node_blob.h
@@ -51,9 +51,9 @@ class Blob : public BaseObject {
SET_MEMORY_INFO_NAME(Blob)
SET_SELF_SIZE(Blob)
- BaseObjectPtr<Blob> Slice(Environment* env, size_t start, size_t end);
+ BaseObjectPtr<Blob> Slice(Environment* env, uint64_t start, uint64_t end);
- inline size_t length() const { return this->data_queue_->size().value(); }
+ inline uint64_t length() const { return this->data_queue_->size().value(); }
class BlobTransferData : public worker::TransferData {
public:
@@ -129,13 +129,13 @@ class BlobBindingData : public SnapshotableObject {
struct StoredDataObject : public MemoryRetainer {
BaseObjectPtr<Blob> blob;
- size_t length;
+ uint64_t length;
std::string type;
StoredDataObject() = default;
StoredDataObject(const BaseObjectPtr<Blob>& blob_,
- size_t length_,
+ uint64_t length_,
const std::string& type_);
void MemoryInfo(MemoryTracker* tracker) const override;
diff --git a/test/pummel/test-blob-large-file-backed.js b/test/pummel/test-blob-large-file-backed.js
new file mode 100644
index 00000000000..e9204944be5
--- /dev/null
+++ b/test/pummel/test-blob-large-file-backed.js
@@ -0,0 +1,49 @@
+'use strict';
+
+// This tests that a file-backed Blob whose size exceeds the range of a 32-bit
+// unsigned integer reports its real size, can be sliced past that boundary,
+// and survives a round trip through URL.createObjectURL().
+
+const common = require('../common');
+
+common.skipIf32Bits();
+
+const assert = require('assert');
+const fs = require('fs');
+const { resolveObjectURL } = require('buffer');
+
+const tmpdir = require('../common/tmpdir');
+tmpdir.refresh();
+
+const filepath = tmpdir.resolve('large-blob.bin');
+const size = 5 * 1024 * 1024 * 1024; // 5GB, sparse
+const message = 'Large File';
+
+try {
+ const fd = fs.openSync(filepath, 'w+');
+ fs.ftruncateSync(fd, size);
+ const buf = Buffer.from(message);
+ fs.writeSync(fd, buf, 0, buf.length, size - buf.length);
+ fs.closeSync(fd);
+} catch (e) {
+ if (e.code !== 'ENOSPC') {
+ throw e;
+ }
+ common.skip('insufficient disk space');
+}
+
+(async () => {
+ const blob = await fs.openAsBlob(filepath);
+ assert.strictEqual(blob.size, size);
+
+ const tail = blob.slice(size - message.length, size);
+ assert.strictEqual(tail.size, message.length);
+ assert.strictEqual(await tail.text(), message);
+
+ const url = URL.createObjectURL(blob);
+ try {
+ assert.strictEqual(resolveObjectURL(url).size, size);
+ } finally {
+ URL.revokeObjectURL(url);
+ }
+})().then(common.mustCall());