Browse Source

chore: cherry-pick 4794770cf175 from chromium (#27394)

* chore: cherry-pick 4794770cf175 from chromium

* update patches
tosmolka 4 years ago
parent
commit
1897909a50
2 changed files with 327 additions and 0 deletions
  1. 1 0
      patches/chromium/.patches
  2. 326 0
      patches/chromium/cherry-pick-4794770cf175.patch

+ 1 - 0
patches/chromium/.patches

@@ -126,6 +126,7 @@ cherry-pick-2d18de63acf1.patch
 only_zero_out_cross-origin_audio_that_doesn_t_get_played_out.patch
 fix_setparentacessibile_crash_win.patch
 cherry-pick-19aeffd4d93f.patch
+cherry-pick-4794770cf175.patch
 cherry-pick-79440c3a0675.patch
 cherry-pick-d866af575997.patch
 cherry-pick-da9b5ec032ad.patch

+ 326 - 0
patches/chromium/cherry-pick-4794770cf175.patch

@@ -0,0 +1,326 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Adam Rice <[email protected]>
+Date: Fri, 4 Dec 2020 10:19:12 +0000
+Subject: Correctly handle detach during (de)compression
+
+Sometimes CompressionStream and DecompressionStream enqueue multiple
+output chunks for a single input chunk. When this happens, JavaScript
+code can detach the input ArrayBuffer while the stream is processing it.
+This will cause an error when zlib tries to read the buffer again
+afterwards.
+
+To prevent this, buffer output chunks until the entire input chunk has
+been processed, and then enqueue them all at once.
+
+Bug: 1151298
+Change-Id: I03fca26fc641d54b09067e3994b76ee8efca6839
+Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2567539
+Commit-Queue: Adam Rice <[email protected]>
+Reviewed-by: Yutaka Hirano <[email protected]>
+Cr-Commit-Position: refs/heads/master@{#833659}
+
+diff --git a/third_party/blink/renderer/modules/compression/deflate_transformer.cc b/third_party/blink/renderer/modules/compression/deflate_transformer.cc
+index e663e563d58e90cdce5185636d1b1aa75491195c..e806b84afbb6cd82c53832e0c0b455ae0767a34a 100644
+--- a/third_party/blink/renderer/modules/compression/deflate_transformer.cc
++++ b/third_party/blink/renderer/modules/compression/deflate_transformer.cc
+@@ -111,6 +111,10 @@ void DeflateTransformer::Deflate(const uint8_t* start,
+   // Zlib treats this pointer as const, so this cast is safe.
+   stream_.next_in = const_cast<uint8_t*>(start);
+ 
++  // enqueue() may execute JavaScript which may invalidate the input buffer. So
++  // accumulate all the output before calling enqueue().
++  HeapVector<Member<DOMUint8Array>, 1u> buffers;
++
+   do {
+     stream_.avail_out = out_buffer_.size();
+     stream_.next_out = out_buffer_.data();
+@@ -120,16 +124,21 @@ void DeflateTransformer::Deflate(const uint8_t* start,
+ 
+     wtf_size_t bytes = out_buffer_.size() - stream_.avail_out;
+     if (bytes) {
+-      controller->enqueue(
+-          script_state_,
+-          ScriptValue::From(script_state_,
+-                            DOMUint8Array::Create(out_buffer_.data(), bytes)),
+-          exception_state);
+-      if (exception_state.HadException()) {
+-        return;
+-      }
++      buffers.push_back(DOMUint8Array::Create(out_buffer_.data(), bytes));
+     }
+   } while (stream_.avail_out == 0);
++
++  DCHECK_EQ(stream_.avail_in, 0u);
++
++  // JavaScript may be executed inside this loop, however it is safe because
++  // |buffers| is a local variable that JavaScript cannot modify.
++  for (DOMUint8Array* buffer : buffers) {
++    controller->enqueue(script_state_, ScriptValue::From(script_state_, buffer),
++                        exception_state);
++    if (exception_state.HadException()) {
++      return;
++    }
++  }
+ }
+ 
+ void DeflateTransformer::Trace(Visitor* visitor) const {
+diff --git a/third_party/blink/renderer/modules/compression/inflate_transformer.cc b/third_party/blink/renderer/modules/compression/inflate_transformer.cc
+index f348a5086e4254bc972dd321109d9fbf676f1d5e..29667b5752250466cd71542ab8868d6e14961e81 100644
+--- a/third_party/blink/renderer/modules/compression/inflate_transformer.cc
++++ b/third_party/blink/renderer/modules/compression/inflate_transformer.cc
+@@ -20,6 +20,7 @@
+ #include "third_party/blink/renderer/platform/bindings/exception_state.h"
+ #include "third_party/blink/renderer/platform/bindings/to_v8.h"
+ #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
++#include "third_party/blink/renderer/platform/wtf/vector.h"
+ #include "v8/include/v8.h"
+ 
+ namespace blink {
+@@ -93,11 +94,15 @@ ScriptPromise InflateTransformer::Flush(
+     TransformStreamDefaultController* controller,
+     ExceptionState& exception_state) {
+   DCHECK(!was_flush_called_);
++  was_flush_called_ = true;
+   Inflate(nullptr, 0u, IsFinished(true), controller, exception_state);
+   inflateEnd(&stream_);
+-  was_flush_called_ = true;
+   out_buffer_.clear();
+ 
++  if (exception_state.HadException()) {
++    return ScriptPromise();
++  }
++
+   if (!reached_end_) {
+     exception_state.ThrowTypeError("Compressed input was truncated.");
+   }
+@@ -121,12 +126,22 @@ void InflateTransformer::Inflate(const uint8_t* start,
+   // Zlib treats this pointer as const, so this cast is safe.
+   stream_.next_in = const_cast<uint8_t*>(start);
+ 
++  // enqueue() may execute JavaScript which may invalidate the input buffer. So
++  // accumulate all the output before calling enqueue().
++  HeapVector<Member<DOMUint8Array>, 1u> buffers;
++
+   do {
+     stream_.avail_out = out_buffer_.size();
+     stream_.next_out = out_buffer_.data();
+     const int err = inflate(&stream_, finished ? Z_FINISH : Z_NO_FLUSH);
+     if (err != Z_OK && err != Z_STREAM_END && err != Z_BUF_ERROR) {
+       DCHECK_NE(err, Z_STREAM_ERROR);
++
++      EnqueueBuffers(controller, std::move(buffers), exception_state);
++      if (exception_state.HadException()) {
++        return;
++      }
++
+       if (err == Z_DATA_ERROR) {
+         exception_state.ThrowTypeError(
+             String("The compressed data was not valid: ") + stream_.msg + ".");
+@@ -138,25 +153,44 @@ void InflateTransformer::Inflate(const uint8_t* start,
+ 
+     wtf_size_t bytes = out_buffer_.size() - stream_.avail_out;
+     if (bytes) {
+-      controller->enqueue(
+-          script_state_,
+-          ScriptValue::From(script_state_,
+-                            DOMUint8Array::Create(out_buffer_.data(), bytes)),
+-          exception_state);
+-      if (exception_state.HadException()) {
+-        return;
+-      }
++      buffers.push_back(DOMUint8Array::Create(out_buffer_.data(), bytes));
+     }
+ 
+     if (err == Z_STREAM_END) {
+       reached_end_ = true;
+-      if (stream_.next_in < start + length) {
++      const bool junk_found = stream_.avail_in > 0;
++
++      EnqueueBuffers(controller, std::move(buffers), exception_state);
++      if (exception_state.HadException()) {
++        return;
++      }
++
++      if (junk_found) {
+         exception_state.ThrowTypeError(
+             "Junk found after end of compressed data.");
+       }
+       return;
+     }
+   } while (stream_.avail_out == 0);
++
++  DCHECK_EQ(stream_.avail_in, 0u);
++
++  EnqueueBuffers(controller, std::move(buffers), exception_state);
++}
++
++void InflateTransformer::EnqueueBuffers(
++    TransformStreamDefaultController* controller,
++    HeapVector<Member<DOMUint8Array>, 1u> buffers,
++    ExceptionState& exception_state) {
++  // JavaScript may be executed inside this loop, however it is safe because
++  // |buffers| is a local variable that JavaScript cannot modify.
++  for (DOMUint8Array* buffer : buffers) {
++    controller->enqueue(script_state_, ScriptValue::From(script_state_, buffer),
++                        exception_state);
++    if (exception_state.HadException()) {
++      return;
++    }
++  }
+ }
+ 
+ void InflateTransformer::Trace(Visitor* visitor) const {
+diff --git a/third_party/blink/renderer/modules/compression/inflate_transformer.h b/third_party/blink/renderer/modules/compression/inflate_transformer.h
+index 290e91808dfcc16ed7ff6ec9e6e42eb412dfc969..c5df437f1684e6ca1201d9b3d32dff19903a0b5e 100644
+--- a/third_party/blink/renderer/modules/compression/inflate_transformer.h
++++ b/third_party/blink/renderer/modules/compression/inflate_transformer.h
+@@ -41,6 +41,10 @@ class InflateTransformer final : public TransformStreamTransformer {
+                TransformStreamDefaultController*,
+                ExceptionState&);
+ 
++  void EnqueueBuffers(TransformStreamDefaultController*,
++                      HeapVector<Member<DOMUint8Array>, 1u> buffers,
++                      ExceptionState&);
++
+   Member<ScriptState> script_state_;
+ 
+   z_stream stream_;
+diff --git a/third_party/blink/web_tests/external/wpt/compression/compression-with-detach.tentative.any.js b/third_party/blink/web_tests/external/wpt/compression/compression-with-detach.tentative.any.js
+new file mode 100644
+index 0000000000000000000000000000000000000000..786bba21c800ca9f067a6d033f0345a52bfbb218
+--- /dev/null
++++ b/third_party/blink/web_tests/external/wpt/compression/compression-with-detach.tentative.any.js
+@@ -0,0 +1,55 @@
++// META: global=window,worker
++// META: script=resources/concatenate-stream.js
++
++'use strict';
++
++const kInputLength = 500000;
++
++function createLargeRandomInput() {
++  const buffer = new ArrayBuffer(kInputLength);
++  // The getRandomValues API will only let us get 65536 bytes at a time, so call
++  // it multiple times.
++  const kChunkSize = 65536;
++  for (let offset = 0; offset < kInputLength; offset += kChunkSize) {
++    const length =
++        offset + kChunkSize > kInputLength ? kInputLength - offset : kChunkSize;
++    const view = new Uint8Array(buffer, offset, length);
++    crypto.getRandomValues(view);
++  }
++  return new Uint8Array(buffer);
++}
++
++function decompress(view) {
++  const ds = new DecompressionStream('deflate');
++  const writer = ds.writable.getWriter();
++  writer.write(view);
++  writer.close();
++  return concatenateStream(ds.readable);
++}
++
++promise_test(async () => {
++  const input = createLargeRandomInput();
++  const inputCopy = input.slice(0, input.byteLength);
++  const cs = new CompressionStream('deflate');
++  const writer = cs.writable.getWriter();
++  writer.write(input);
++  writer.close();
++  // Object.prototype.then will be looked up synchronously when the promise
++  // returned by read() is resolved.
++  Object.defineProperty(Object.prototype, 'then', {
++    get() {
++      // Cause input to become detached and unreferenced.
++      try {
++        postMessage(undefined, 'nowhere', [input.buffer]);
++      } catch (e) {
++        // It's already detached.
++      }
++    }
++  });
++  const output = await concatenateStream(cs.readable);
++  // Perform the comparison as strings since this is reasonably fast even when
++  // JITted JavaScript is running under an emulator.
++  assert_equals(
++      inputCopy.toString(), (await decompress(output)).toString(),
++      'decompressing the output should return the input');
++}, 'data should be correctly compressed even if input is detached partway');
+diff --git a/third_party/blink/web_tests/external/wpt/compression/decompression-with-detach.tentative.any.js b/third_party/blink/web_tests/external/wpt/compression/decompression-with-detach.tentative.any.js
+new file mode 100644
+index 0000000000000000000000000000000000000000..a2f8bda09148f0d323022b1f93be78d83c4aa654
+--- /dev/null
++++ b/third_party/blink/web_tests/external/wpt/compression/decompression-with-detach.tentative.any.js
+@@ -0,0 +1,41 @@
++// META: global=window,worker
++// META: script=resources/concatenate-stream.js
++
++'use strict';
++
++const kInputLength = 1000000;
++
++async function createLargeCompressedInput() {
++  const cs = new CompressionStream('deflate');
++  // The input has to be large enough that it won't fit in a single chunk when
++  // decompressed.
++  const writer = cs.writable.getWriter();
++  writer.write(new Uint8Array(kInputLength));
++  writer.close();
++  return concatenateStream(cs.readable);
++}
++
++promise_test(async () => {
++  const input = await createLargeCompressedInput();
++  const ds = new DecompressionStream('deflate');
++  const writer = ds.writable.getWriter();
++  writer.write(input);
++  writer.close();
++  // Object.prototype.then will be looked up synchronously when the promise
++  // returned by read() is resolved.
++  Object.defineProperty(Object.prototype, 'then', {
++    get() {
++      // Cause input to become detached and unreferenced.
++      try {
++        postMessage(undefined, 'nowhere', [input.buffer]);
++      } catch (e) {
++        // It's already detached.
++      }
++    }
++  });
++  const output = await concatenateStream(ds.readable);
++  // If output successfully decompressed and gave the right length, we can be
++  // reasonably confident that no data corruption happened.
++  assert_equals(
++      output.byteLength, kInputLength, 'output should be the right length');
++}, 'data should be correctly decompressed even if input is detached partway');
+diff --git a/third_party/blink/web_tests/external/wpt/compression/resources/concatenate-stream.js b/third_party/blink/web_tests/external/wpt/compression/resources/concatenate-stream.js
+new file mode 100644
+index 0000000000000000000000000000000000000000..a35bb1416e754893e331c0089d97720ae3b5af8e
+--- /dev/null
++++ b/third_party/blink/web_tests/external/wpt/compression/resources/concatenate-stream.js
+@@ -0,0 +1,25 @@
++'use strict';
++
++// Read all the chunks from a stream that returns BufferSource objects and
++// concatenate them into a single Uint8Array.
++async function concatenateStream(readableStream) {
++  const reader = readableStream.getReader();
++  let totalSize = 0;
++  const buffers = [];
++  while (true) {
++    const { value, done } = await reader.read();
++    if (done) {
++      break;
++    }
++    buffers.push(value);
++    totalSize += value.byteLength;
++  }
++  reader.releaseLock();
++  const concatenated = new Uint8Array(totalSize);
++  let offset = 0;
++  for (const buffer of buffers) {
++    concatenated.set(buffer, offset);
++    offset += buffer.byteLength;
++  }
++  return concatenated;
++}