|
@@ -0,0 +1,214 @@
|
|
|
+# HG changeset patch
|
|
|
+# User Steve Fink <sfink@mozilla.com>
|
|
|
+# Date 1725418656 0
|
|
|
+# Node ID 477a37c49b20b34b3e94d9792671de2bf260f949
|
|
|
+# Parent 92c355f44c71f78d04f1ad3103575fc6f9b63828
|
|
|
+Bug 1912471 - Disallow deserializing structured clone buffers with transferables more than once r=iain
|
|
|
+
|
|
|
+Differential Revision: https://phabricator.services.mozilla.com/D220644
|
|
|
+
|
|
|
+diff --git a/js/public/StructuredClone.h b/js/public/StructuredClone.h
|
|
|
+--- a/js/public/StructuredClone.h
|
|
|
++++ b/js/public/StructuredClone.h
|
|
|
+@@ -516,16 +516,17 @@ class JS_PUBLIC_API(JSAutoStructuredClon
|
|
|
+ #define JS_SCTAG_USER_MIN ((uint32_t) 0xFFFF8000)
|
|
|
+ #define JS_SCTAG_USER_MAX ((uint32_t) 0xFFFFFFFF)
|
|
|
+
|
|
|
+ #define JS_SCERR_RECURSION 0
|
|
|
+ #define JS_SCERR_TRANSFERABLE 1
|
|
|
+ #define JS_SCERR_DUP_TRANSFERABLE 2
|
|
|
+ #define JS_SCERR_UNSUPPORTED_TYPE 3
|
|
|
+ #define JS_SCERR_SHMEM_TRANSFERABLE 4
|
|
|
++#define JS_SCERR_TRANSFERABLE_TWICE 5
|
|
|
+
|
|
|
+ JS_PUBLIC_API(bool)
|
|
|
+ JS_ReadUint32Pair(JSStructuredCloneReader* r, uint32_t* p1, uint32_t* p2);
|
|
|
+
|
|
|
+ JS_PUBLIC_API(bool)
|
|
|
+ JS_ReadBytes(JSStructuredCloneReader* r, void* p, size_t len);
|
|
|
+
|
|
|
+ JS_PUBLIC_API(bool)
|
|
|
+diff --git a/js/src/jit-test/tests/structured-clone/transferable-cleanup.js.1912471.later b/js/src/jit-test/tests/structured-clone/transferable-cleanup.js.1912471.later
|
|
|
+new file mode 100644
|
|
|
+--- /dev/null
|
|
|
++++ b/js/src/jit-test/tests/structured-clone/transferable-cleanup.js.1912471.later
|
|
|
+@@ -0,0 +1,39 @@
|
|
|
++--- transferable-cleanup.js
|
|
|
+++++ transferable-cleanup.js
|
|
|
++@@ -155,26 +155,36 @@ function testMultiWithDeserializeReadTra
|
|
|
++ ], "serialize " + desc);
|
|
|
++ obj.log = null;
|
|
|
++
|
|
|
++ try {
|
|
|
++ let clone = deserialize(s);
|
|
|
++ } catch (e) {
|
|
|
++ assertEq(e.message.includes("invalid transferable"), true);
|
|
|
++ }
|
|
|
+++
|
|
|
+++ try {
|
|
|
+++ // This fails without logging anything, since the re-transfer will be caught
|
|
|
+++ // by looking at its header before calling any callbacks.
|
|
|
+++ let clone = deserialize(s);
|
|
|
+++ } catch (e) {
|
|
|
+++ assertEq(e.message.includes("cannot transfer twice"), true);
|
|
|
+++ }
|
|
|
+++
|
|
|
++ s = null;
|
|
|
++ gc();
|
|
|
++ printTrace(arguments.callee.name, g, BASE, obj.log, "deserialize");
|
|
|
++ assertEq("" + obj.log, "" + [
|
|
|
++ // readTransfer(obj) then readTransfer(obj3) which fails.
|
|
|
++ BASE + 1, "R", BASE + 3, "R",
|
|
|
++ // obj2 has not been read at all because we errored out during readTransferMap(),
|
|
|
++ // which comes before the main reading. obj transfer data is now owned by its
|
|
|
++ // clone. obj3 transfer data was not successfully handed over to a new object,
|
|
|
++ // so it is still owned by the clone buffer and must be discarded with freeTransfer.
|
|
|
+++ // 'F' means the data is freed.
|
|
|
++ BASE + 3, "F",
|
|
|
++ ], "deserialize " + desc);
|
|
|
++ obj.log = null;
|
|
|
++ }
|
|
|
++
|
|
|
++ function testMultiWithDeserializeReadTransferError() {
|
|
|
++ const desc = "write 3 objects, transfer obj1 and obj3 only, fail during readTransfer(obj3)";
|
|
|
++ testMultiWithDeserializeReadTransferErrorHelper(globalThis, 800, desc);
|
|
|
+diff --git a/js/src/js.msg b/js/src/js.msg
|
|
|
+--- a/js/src/js.msg
|
|
|
++++ b/js/src/js.msg
|
|
|
+@@ -435,16 +435,17 @@ MSG_DEF(JSMSG_PROXY_REVOKED, 0
|
|
|
+ MSG_DEF(JSMSG_PROXY_ARG_REVOKED, 1, JSEXN_TYPEERR, "argument {0} cannot be a revoked proxy")
|
|
|
+ MSG_DEF(JSMSG_BAD_TRAP, 1, JSEXN_TYPEERR, "proxy handler's {0} trap wasn't undefined, null, or callable")
|
|
|
+
|
|
|
+ // Structured cloning
|
|
|
+ MSG_DEF(JSMSG_SC_BAD_CLONE_VERSION, 0, JSEXN_ERR, "unsupported structured clone version")
|
|
|
+ MSG_DEF(JSMSG_SC_BAD_SERIALIZED_DATA, 1, JSEXN_INTERNALERR, "bad serialized structured data ({0})")
|
|
|
+ MSG_DEF(JSMSG_SC_DUP_TRANSFERABLE, 0, JSEXN_TYPEERR, "duplicate transferable for structured clone")
|
|
|
+ MSG_DEF(JSMSG_SC_NOT_TRANSFERABLE, 0, JSEXN_TYPEERR, "invalid transferable array for structured clone")
|
|
|
++MSG_DEF(JSMSG_SC_TRANSFERABLE_TWICE, 0, JSEXN_TYPEERR, "structured clone cannot transfer twice")
|
|
|
+ MSG_DEF(JSMSG_SC_UNSUPPORTED_TYPE, 0, JSEXN_TYPEERR, "unsupported type for structured data")
|
|
|
+ MSG_DEF(JSMSG_SC_NOT_CLONABLE, 1, JSEXN_TYPEERR, "{0} cannot be cloned in this context")
|
|
|
+ MSG_DEF(JSMSG_SC_SAB_DISABLED, 0, JSEXN_TYPEERR, "SharedArrayBuffer not cloned - shared memory disabled in receiver")
|
|
|
+ MSG_DEF(JSMSG_SC_SAB_REFCNT_OFLO, 0, JSEXN_TYPEERR, "SharedArrayBuffer has too many references")
|
|
|
+ MSG_DEF(JSMSG_SC_SHMEM_TRANSFERABLE, 0, JSEXN_TYPEERR, "Shared memory objects must not be in the transfer list")
|
|
|
+ MSG_DEF(JSMSG_SC_SHMEM_POLICY, 0, JSEXN_TYPEERR, "Policy object must forbid cloning shared memory objects cross-process")
|
|
|
+
|
|
|
+ // Debugger
|
|
|
+diff --git a/js/src/vm/StructuredClone.cpp b/js/src/vm/StructuredClone.cpp
|
|
|
+--- a/js/src/vm/StructuredClone.cpp
|
|
|
++++ b/js/src/vm/StructuredClone.cpp
|
|
|
+@@ -137,29 +137,33 @@ enum StructuredDataType : uint32_t {
|
|
|
+ SCTAG_TRANSFER_MAP_STORED_ARRAY_BUFFER,
|
|
|
+ SCTAG_TRANSFER_MAP_END_OF_BUILTIN_TYPES,
|
|
|
+
|
|
|
+ SCTAG_END_OF_BUILTIN_TYPES
|
|
|
+ };
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Format of transfer map:
|
|
|
+- * <SCTAG_TRANSFER_MAP_HEADER, TransferableMapHeader(UNREAD|TRANSFERRED)>
|
|
|
+- * numTransferables (64 bits)
|
|
|
+- * array of:
|
|
|
+- * <SCTAG_TRANSFER_MAP_*, TransferableOwnership>
|
|
|
+- * pointer (64 bits)
|
|
|
+- * extraData (64 bits), eg byte length for ArrayBuffers
|
|
|
++ * - <SCTAG_TRANSFER_MAP_HEADER, UNREAD|TRANSFERRING|TRANSFERRED>
|
|
|
++ * - numTransferables (64 bits)
|
|
|
++ * - array of:
|
|
|
++ * - <SCTAG_TRANSFER_MAP_*, TransferableOwnership> pointer (64
|
|
|
++ * bits)
|
|
|
++ * - extraData (64 bits), eg byte length for ArrayBuffers
|
|
|
++ * - any data written for custom transferables
|
|
|
+ */
|
|
|
+
|
|
|
+-// Data associated with an SCTAG_TRANSFER_MAP_HEADER that tells whether the
|
|
|
+-// contents have been read out yet or not.
|
|
|
++// contents have been read out yet or not. TRANSFERRING is for the case where we
|
|
|
++// have started but not completed reading, which due to errors could mean that
|
|
|
++// there are things still owned by the clone buffer that need to be released, so
|
|
|
++// discarding should not just be skipped.
|
|
|
+ enum TransferableMapHeader {
|
|
|
+- SCTAG_TM_UNREAD = 0,
|
|
|
+- SCTAG_TM_TRANSFERRED
|
|
|
++ SCTAG_TM_UNREAD = 0,
|
|
|
++ SCTAG_TM_TRANSFERRING,
|
|
|
++ SCTAG_TM_TRANSFERRED
|
|
|
+ };
|
|
|
+
|
|
|
+ static inline uint64_t
|
|
|
+ PairToUInt64(uint32_t tag, uint32_t data)
|
|
|
+ {
|
|
|
+ return uint64_t(data) | (uint64_t(tag) << 32);
|
|
|
+ }
|
|
|
+
|
|
|
+@@ -592,16 +596,20 @@ ReportDataCloneError(JSContext* cx,
|
|
|
+ case JS_SCERR_UNSUPPORTED_TYPE:
|
|
|
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_SC_UNSUPPORTED_TYPE);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case JS_SCERR_SHMEM_TRANSFERABLE:
|
|
|
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_SC_SHMEM_TRANSFERABLE);
|
|
|
+ break;
|
|
|
+
|
|
|
++ case JS_SCERR_TRANSFERABLE_TWICE:
|
|
|
++ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JS_SCERR_TRANSFERABLE_TWICE);
|
|
|
++ break;
|
|
|
++
|
|
|
+ default:
|
|
|
+ MOZ_CRASH("Unkown errorId");
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ bool
|
|
|
+ WriteStructuredClone(JSContext* cx, HandleValue v, JSStructuredCloneData* bufp,
|
|
|
+@@ -2413,18 +2421,30 @@ JSStructuredCloneReader::readTransferMap
|
|
|
+ {
|
|
|
+ JSContext* cx = context();
|
|
|
+ auto headerPos = in.tell();
|
|
|
+
|
|
|
+ uint32_t tag, data;
|
|
|
+ if (!in.getPair(&tag, &data))
|
|
|
+ return in.reportTruncated();
|
|
|
+
|
|
|
+- if (tag != SCTAG_TRANSFER_MAP_HEADER || TransferableMapHeader(data) == SCTAG_TM_TRANSFERRED)
|
|
|
++ auto transferState = static_cast<TransferableMapHeader>(data);
|
|
|
++
|
|
|
++ if (tag != SCTAG_TRANSFER_MAP_HEADER ||
|
|
|
++ transferState == SCTAG_TM_TRANSFERRED) {
|
|
|
+ return true;
|
|
|
++ }
|
|
|
++
|
|
|
++ if (transferState == SCTAG_TM_TRANSFERRING) {
|
|
|
++ ReportDataCloneError(cx, callbacks, JS_SCERR_TRANSFERABLE_TWICE); // ,closure);
|
|
|
++ return false;
|
|
|
++ }
|
|
|
++
|
|
|
++ headerPos.write(
|
|
|
++ PairToUInt64(SCTAG_TRANSFER_MAP_HEADER, SCTAG_TM_TRANSFERRING));
|
|
|
+
|
|
|
+ uint64_t numTransferables;
|
|
|
+ MOZ_ALWAYS_TRUE(in.readPair(&tag, &data));
|
|
|
+ if (!in.read(&numTransferables))
|
|
|
+ return false;
|
|
|
+
|
|
|
+ for (uint64_t i = 0; i < numTransferables; i++) {
|
|
|
+ auto pos = in.tell();
|
|
|
+@@ -2502,17 +2522,17 @@ JSStructuredCloneReader::readTransferMap
|
|
|
+ if (!allObjs.append(ObjectValue(*obj)))
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Mark the whole transfer map as consumed.
|
|
|
+ #ifdef DEBUG
|
|
|
+ SCInput::getPair(headerPos.peek(), &tag, &data);
|
|
|
+ MOZ_ASSERT(tag == SCTAG_TRANSFER_MAP_HEADER);
|
|
|
+- MOZ_ASSERT(TransferableMapHeader(data) != SCTAG_TM_TRANSFERRED);
|
|
|
++ MOZ_ASSERT(TransferableMapHeader(data) == SCTAG_TM_TRANSFERRING);
|
|
|
+ #endif
|
|
|
+ headerPos.write(PairToUInt64(SCTAG_TRANSFER_MAP_HEADER, SCTAG_TM_TRANSFERRED));
|
|
|
+
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ JSObject*
|
|
|
+ JSStructuredCloneReader::readSavedFrame(uint32_t principalsTag)
|