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());