Pinned RTCEncodedFrame.data detach failure causes native WebRTC frame use-after-free
Categories
(Core :: WebRTC, defect)
Tracking
()
People
(Reporter: deayzl, Unassigned)
Details
(Keywords: csectype-uaf, reporter-external, sec-high, Whiteboard: [client-bounty-form])
Attachments
(4 files)
Summary
Firefox exposes RTCEncodedVideoFrame.data / RTCEncodedAudioFrame.data as a SpiderMonkey ArrayBuffer backed directly by libwebrtc-owned encoded-frame storage via JS::NewArrayBufferWithUserOwnedContents().
That storage must be detached before the native owner frees it, but RTCEncodedFrameBase::DetachData() ignores the fallible return value from JS::DetachArrayBuffer().
If web content pins a typed-array view of frame.data while worker GC collects the dropped RTCEncodedFrame, JS::DetachArrayBuffer() fails with RangeError: attempting to change pinned length of ArrayBuffer.
Firefox then still destroys the native libwebrtc frame, leaving the retained JS view attached to freed native EncodedImageBuffer memory.
The attached arbitrary-HTML repro uses WebRTC encoded transforms plus worker OPFS FileSystemSyncAccessHandle.write() as the length-pinning primitive and native stale-pointer read sink.
Reproduction Steps
tested firefox commit version: ef00d01b35bdd0f6bd1b444f78c693f246b7f58b
Build an ASAN firefox with:
mk_add_options MOZ_OBJDIR=/home/slave/firefox-bug-bounty/obj-x86_64-pc-linux-gnu-asan-browser
ac_add_options --enable-project=browser
ac_add_options --enable-address-sanitizer
ac_add_options --disable-install-strip
ac_add_options --disable-jemalloc
ac_add_options --disable-crashreporter
ac_add_options --disable-profiling
# Keep stack use reasonable while retaining useful line information.
ac_add_options --enable-optimize="-O1"
ac_add_options --enable-debug-symbols=-gline-tables-only
# Optional but useful for GC-sensitive SpiderMonkey reproduction work.
ac_add_options --enable-gczeal
Serve the attached html and js:
python3 -m http.server 8125 --bind 127.0.0.1
Execute:
rm -rf /tmp/ff-asan-webrtc-native-sink
mkdir -p /tmp/ff-asan-webrtc-native-sink2
ASAN_OPTIONS='detect_leaks=0:abort_on_error=1:handle_segv=2' \
/home/slave/firefox-bug-bounty/obj-x86_64-pc-linux-gnu-asan-browser/dist/bin/firefox \
--no-remote \
--profile /tmp/ff-asan-webrtc-native-sink2 \
'http://127.0.0.1:8125/dual-opfs-native-sink.html'
Result:
=================================================================
==2116684==ERROR: AddressSanitizer: heap-use-after-free on address 0x7630dc6db800 at pc 0x58022c77e6d9 bp 0x7630d4d2aa10 sp 0x7630d4d2a1e0
READ of size 1048576 at 0x7630dc6db800 thread T40
#0 0x58022c77e6d8 in write /builds/worker/fetches/llvm-project/compiler-rt/lib/asan/../sanitizer_common/sanitizer_common_interceptors.inc:1137:16
#1 0x7a3129cdd3ed in pt_Write /home/slave/firefox-bug-bounty/obj-x86_64-pc-linux-gnu-asan-browser/config/external/nspr/pr/./../../../../../firefox/nsprpub/pr/src/pthreads/ptio.c:1110:11
#2 0x76310f25e396 in (anonymous namespace)::interposedWrite(PRFileDesc*, void const*, int) /home/slave/firefox-bug-bounty/firefox/xpcom/build/NSPRInterposer.cpp:102:10
#3 0x76310dc014f9 in nsFileStreamBase::Write(char const*, unsigned int, unsigned int*) /home/slave/firefox-bug-bounty/obj-x86_64-pc-linux-gnu-asan-browser/netwerk/base/./../../../firefox/netwerk/base/nsFileStreams.cpp:272:17
#4 0x76311815b3f9 in Write /home/slave/firefox-bug-bounty/obj-x86_64-pc-linux-gnu-asan-browser/dist/include/nsFileStreams.h:273:30
...
#21 0x7a3129ce5be5 in _pt_root /home/slave/firefox-bug-bounty/obj-x86_64-pc-linux-gnu-asan-browser/config/external/nspr/pr/./../../../../../firefox/nsprpub/pr/src/pthreads/ptthread.c:190:3
#22 0x58022c7fc6a6 in asan_thread_start(void*) /builds/worker/fetches/llvm-project/compiler-rt/lib/asan/asan_interceptors.cpp:239:28
#23 0x7a3129a9caa3 in start_thread nptl/pthread_create.c:447:8
#24 0x7a3129b29c6b in clone3 misc/../sysdeps/unix/sysv/linux/x86_64/clone3.S:78
0x7630dc6db800 is located 0 bytes inside of 1048576-byte region [0x7630dc6db800,0x7630dc7db800)
freed by thread T29 here:
#0 0x58022c8002b6 in free /builds/worker/fetches/llvm-project/compiler-rt/lib/asan/asan_malloc_linux.cpp:51:3
#1 0x7631176672db in operator delete[] /home/slave/firefox-bug-bounty/obj-x86_64-pc-linux-gnu-asan-browser/dist/include/mozilla/cxxalloc.h:73:10
#2 0x7631176672db in operator()<unsigned char> /home/slave/.mozbuild/sysroot-x86_64-linux-gnu/usr/lib/gcc/x86_64-linux-gnu/10/../../../../include/c++/10/bits/unique_ptr.h:120:4
#3 0x7631176672db in ~unique_ptr /home/slave/.mozbuild/sysroot-x86_64-linux-gnu/usr/lib/gcc/x86_64-linux-gnu/10/../../../../include/c++/10/bits/unique_ptr.h:612:4
#4 0x7631176672db in ~BufferT /home/slave/firefox-bug-bounty/firefox/third_party/libwebrtc/rtc_base/buffer.h:141:43
#5 0x7631176672db in ~EncodedImageBuffer /home/slave/firefox-bug-bounty/firefox/third_party/libwebrtc/api/video/encoded_image.h:53:18
...
#62 0x76311468d4c5 in bool mozilla::dom::binding_detail::GenericMethod<mozilla::dom::binding_detail::NormalThisPolicy, mozilla::dom::binding_detail::ThrowExceptions>(JSContext*, unsigned int, JS::Value*) /home/slave/firefox-bug-bounty/obj-x86_64-pc-linux-gnu-asan-browser/dom/bindings/./../../../firefox/dom/bindings/BindingUtils.cpp:3217:13
previously allocated by thread T35 here:
#0 0x58022c800554 in malloc /builds/worker/fetches/llvm-project/compiler-rt/lib/asan/asan_malloc_linux.cpp:67:3
#1 0x58022c85345d in moz_xmalloc /home/slave/firefox-bug-bounty/obj-x86_64-pc-linux-gnu-asan-browser/memory/mozalloc/./../../../firefox/memory/mozalloc/mozalloc.cpp:48:15
#2 0x763117663ac6 in operator new[] /home/slave/firefox-bug-bounty/obj-x86_64-pc-linux-gnu-asan-browser/dist/include/mozilla/cxxalloc.h:42:10
#3 0x763117663ac6 in BufferT /home/slave/firefox-bug-bounty/firefox/third_party/libwebrtc/rtc_base/buffer.h:408:31
#4 0x763117663ac6 in CreateUninitializedWithSize /home/slave/firefox-bug-bounty/firefox/third_party/libwebrtc/rtc_base/buffer.h:77:12
#5 0x763117663ac6 in EncodedImageBuffer /home/slave/firefox-bug-bounty/obj-x86_64-pc-linux-gnu-asan-browser/third_party/libwebrtc/api/video/encoded_image_gn/./../../../../../../firefox/third_party/libwebrtc/api/video/encoded_image.cc:27:15
...
#42 0x58022c7fc6a6 in asan_thread_start(void*) /builds/worker/fetches/llvm-project/compiler-rt/lib/asan/asan_interceptors.cpp:239:28
Thread T40 created by T0 (Isolated Web Co) here:
#0 0x58022c7e4851 in pthread_create /builds/worker/fetches/llvm-project/compiler-rt/lib/asan/asan_interceptors.cpp:250:3
#1 0x7a3129cd5a00 in _PR_CreateThread /home/slave/firefox-bug-bounty/obj-x86_64-pc-linux-gnu-asan-browser/config/external/nspr/pr/./../../../../../firefox/nsprpub/pr/src/pthreads/ptthread.c:428:10
#2 0x7a3129cc173e in PR_CreateThread /home/slave/firefox-bug-bounty/obj-x86_64-pc-linux-gnu-asan-browser/config/external/nspr/pr/./../../../../../firefox/nsprpub/pr/src/pthreads/ptthread.c:495:10
#3 0x76310f1fe7fc in nsThread::Init(nsTSubstring<char> const&) /home/slave/firefox-bug-bounty/obj-x86_64-pc-linux-gnu-asan-browser/xpcom/threads/./../../../firefox/xpcom/threads/nsThread.cpp:623:20
#4 0x76310f20bf82 in nsThreadManager::NewNamedThread(nsTSubstring<char> const&, nsIThreadManager::ThreadCreationOptions, nsIThread**) /home/slave/firefox-bug-bounty/obj-x86_64-pc-linux-gnu-asan-browser/xpcom/threads/./../../../firefox/xpcom/threads/nsThreadManager.cpp:618:22
#5 0x76310f2185c7 in NS_NewNamedThread(nsTSubstring<char> const&, nsIThread**, already_AddRefed<nsIRunnable>, nsIThreadManager::ThreadCreationOptions) /home/slave/firefox-bug-bounty/obj-x86_64-pc-linux-gnu-asan-browser/xpcom/threads/./../../../firefox/xpcom/threads/nsThreadUtils.cpp:186:57
...
#38 0x7a3129a2a1c9 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
#39 0x7a3129a2a28a in __libc_start_main csu/../csu/libc-start.c:360:3
#40 0x58022c75d448 in _start (/home/slave/firefox-bug-bounty/obj-x86_64-pc-linux-gnu-asan-browser/dist/bin/firefox+0xb8448) (BuildId: d400de8eb99e19a70f3700a7436f261f)
Thread T29 created by T0 (Isolated Web Co) here:
#0 0x58022c7e4851 in pthread_create /builds/worker/fetches/llvm-project/compiler-rt/lib/asan/asan_interceptors.cpp:250:3
#1 0x7a3129cd5a00 in _PR_CreateThread /home/slave/firefox-bug-bounty/obj-x86_64-pc-linux-gnu-asan-browser/config/external/nspr/pr/./../../../../../firefox/nsprpub/pr/src/pthreads/ptthread.c:428:10
#2 0x7a3129cc173e in PR_CreateThread /home/slave/firefox-bug-bounty/obj-x86_64-pc-linux-gnu-asan-browser/config/external/nspr/pr/./../../../../../firefox/nsprpub/pr/src/pthreads/ptthread.c:495:10
...
#52 0x7a3129a2a1c9 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
#53 0x7a3129a2a28a in __libc_start_main csu/../csu/libc-start.c:360:3
#54 0x58022c75d448 in _start (/home/slave/firefox-bug-bounty/obj-x86_64-pc-linux-gnu-asan-browser/dist/bin/firefox+0xb8448) (BuildId: d400de8eb99e19a70f3700a7436f261f)
Thread T35 created by T0 (Isolated Web Co) here:
#0 0x58022c7e4851 in pthread_create /builds/worker/fetches/llvm-project/compiler-rt/lib/asan/asan_interceptors.cpp:250:3
#1 0x7a3129cd5a00 in _PR_CreateThread /home/slave/firefox-bug-bounty/obj-x86_64-pc-linux-gnu-asan-browser/config/external/nspr/pr/./../../../../../firefox/nsprpub/pr/src/pthreads/ptthread.c:428:10
#2 0x7a3129cc173e in PR_CreateThread /home/slave/firefox-bug-bounty/obj-x86_64-pc-linux-gnu-asan-browser/config/external/nspr/pr/./../../../../../firefox/nsprpub/pr/src/pthreads/ptthread.c:495:10
#3 0x76310f1fe7fc in nsThread::Init(nsTSubstring<char> const&) /home/slave/firefox-bug-bounty/obj-x86_64-pc-linux-gnu-asan-browser/xpcom/threads/./../../../firefox/xpcom/threads/nsThread.cpp:623:20
#4 0x76310f20bf82 in nsThreadManager::NewNamedThread(nsTSubstring<char> const&, nsIThreadManager::ThreadCreationOptions, nsIThread**) /home/slave/firefox-bug-bounty/obj-x86_64-pc-linux-gnu-asan-browser/xpcom/threads/./../../../firefox/xpcom/threads/nsThreadManager.cpp:618:22
...
#36 0x7a3129a2a1c9 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
#37 0x7a3129a2a28a in __libc_start_main csu/../csu/libc-start.c:360:3
#38 0x58022c75d448 in _start (/home/slave/firefox-bug-bounty/obj-x86_64-pc-linux-gnu-asan-browser/dist/bin/firefox+0xb8448) (BuildId: d400de8eb99e19a70f3700a7436f261f)
SUMMARY: AddressSanitizer: heap-use-after-free /home/slave/firefox-bug-bounty/obj-x86_64-pc-linux-gnu-asan-browser/config/external/nspr/pr/./../../../../../firefox/nsprpub/pr/src/pthreads/ptio.c:1110:11 in pt_Write
Shadow bytes around the buggy address:
0x7630dc6db580: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7630dc6db600: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7630dc6db680: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7630dc6db700: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7630dc6db780: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x7630dc6db800:[00]00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7630dc6db880: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7630dc6db900: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7630dc6db980: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7630dc6dba00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7630dc6dba80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
==2116684==ABORTING
| Reporter | ||
Comment 1•4 months ago
|
||
| Reporter | ||
Comment 2•4 months ago
|
||
| Reporter | ||
Comment 3•4 months ago
|
||
| Reporter | ||
Comment 4•4 months ago
|
||
I'll add root cause analysis on this bug later.
Updated•4 months ago
|
Comment 5•4 months ago
|
||
Please don't put the URL of the Firefox GitHub repository in the URL field. It is not helpful.
Updated•4 months ago
|
| Reporter | ||
Comment 6•4 months ago
|
||
Root Cause Analysis
analyzed firefox commit version: fc12c6f52aea47b9ca635b0ab71315ffbeb8c2f9
The receiver WebRTC transform worker receives an RTCEncodedVideoFrame:
(When transforming a frame, it is wrapped as a RTCEncodedVideoFrame)
// dom/media/webrtc/jsapi/RTCRtpScriptTransformer.cpp
void RTCRtpScriptTransformer::TransformFrame(
std::unique_ptr<webrtc::TransformableFrameInterface> aFrame) {
...
RefPtr<RTCEncodedFrameBase> domFrame;
if (*mVideo) {
...
domFrame = new RTCEncodedVideoFrame(mGlobal, std::move(aFrame),
++mLastEnqueuedFrameCounter, this,
mTimestampMaker);
} else {
domFrame = new RTCEncodedAudioFrame(mGlobal, std::move(aFrame),
++mLastEnqueuedFrameCounter, this,
mTimestampMaker);
}
mReadableSource->Enqueue(domFrame);
}
(RTCEncodedFrameBase creates a JS ArrayBuffer pointing at native WebRTC payload memory)
// dom/media/webrtc/jsapi/RTCEncodedFrameBase.cpp
RTCEncodedFrameBase::RTCEncodedFrameBase(nsIGlobalObject* aGlobal,
RTCEncodedFrameState& aState,
RTCRtpScriptTransformer* aOwner)
: mGlobal(aGlobal), mOwner(aOwner), mState(aState), mData(nullptr) {
...
if (mState.mFrame->GetData().data()) {
// Avoid a copy
mData = JS::NewArrayBufferWithUserOwnedContents(
jsapi.cx(), mState.mFrame->GetData().size(),
(void*)(mState.mFrame->GetData().data()));
} else {
mData = JS::NewArrayBuffer(jsapi.cx(), 0);
}
}
The worker of receiver saves new Uint8Array(frame.data) and drops JS references to the frame wrapper:
(new Uint8Array(frame.data) retains the ArrayBuffer)
onrtctransform = event => {
...
let result = await reader.read();
...
let frame = result.value;
const id = ++received;
const view = new Uint8Array(frame.data);
result.value = null;
result = null;
frame = null;
retained.push(view)
The worker of receiver queues allocation pressure to make GC happen, then calls FileSystemSyncAccessHandle.write(view). ProcessTypedArraysFixed() pins the view length, and OPFS enters syncLoop.Run() before the pin is released:
// dom/fs/api/FileSystemSyncAccessHandle.cpp
uint64_t FileSystemSyncAccessHandle::ReadOrWrite(
const AllowSharedBufferSource& aBuffer,
const FileSystemReadWriteOptions& aOptions, const bool aRead,
ErrorResult& aRv) {
...
AutoSyncLoopHolder syncLoop(mWorkerRef->Private(), Canceling);
nsCOMPtr<nsISerialEventTarget> syncLoopTarget =
syncLoop.GetSerialEventTarget();
QM_TRY(MOZ_TO_RESULT(syncLoopTarget), [&aRv](nsresult) {
aRv.ThrowInvalidStateError("Worker is shutting down");
return 0;
});
uint64_t totalCount = 0;
ProcessTypedArraysFixed(aBuffer, [&](const Span<uint8_t> aData) {
InvokeAsync(
mIOTaskQueue, __func__,
[selfHolder = quota::TargetPtrHolder(this), aData,
use_offset = aOptions.mAt.WasPassed(), offset, aRead, syncLoopTarget,
&totalCount]() {
...
})
->Then(syncLoopTarget, __func__,
[this, &syncLoopTarget](
const BoolPromise::ResolveOrRejectValue& aValue) {
MOZ_ASSERT(mWorkerRef);
mWorkerRef->Private()->AssertIsOnWorkerThread();
mWorkerRef->Private()->StopSyncLoop(syncLoopTarget, NS_OK);
});
MOZ_ALWAYS_SUCCEEDS(syncLoop.Run());
});
return totalCount;
}
// dom/bindings/TypedArray.h
template <typename T, typename Processor,
std::enable_if_t<is_dom_union_with_typedarray_members<T>, int> = 0>
[[nodiscard]] auto ProcessTypedArraysFixed(const T& aUnion,
Processor&& aProcessor) {
return binding_detail::ApplyToTypedArrays(
aUnion, [&](const auto& aTypedArray) {
return aTypedArray.ProcessFixedData(
std::forward<Processor>(aProcessor));
});
}
template <bool AllowLargeTypedArrays = false, typename Processor>
[[nodiscard]] ProcessReturnType<Processor> ProcessFixedData(
Processor&& aProcessor) const {
...
LengthPinner pinner(this); // pin length
return CallProcessor(GetCurrentData<AllowLargeTypedArrays>(),
std::forward<Processor>(aProcessor));
}
The worker sync loop processes events and GC/CC collects the dropped RTCEncodedVideoFrame then RTCEncodedFrameBase::~RTCEncodedFrameBase() calls DetachData():
// dom/workers/WorkerPrivate.cpp
nsresult WorkerPrivate::RunCurrentSyncLoop() {
...
if (normalRunnablesPending) {
...
if (GetCurrentEventLoopGlobal()) {
...
JS_MaybeGC(cx);
}
}
}
}
...
}
// dom/workers/RuntimeService.cpp
class WorkerJSRuntime final : public mozilla::CycleCollectedJSRuntime {
...
virtual void CustomGCCallback(JSGCStatus aStatus) override {
...
if (aStatus == JSGC_END) {
bool collectedAnything =
nsCycleCollector_collect(CCReason::GC_FINISHED, nullptr); // collect `RTCEncodedVideoFrame`
mWorkerPrivate->SetCCCollectedAnything(collectedAnything);
}
}
...
// dom/media/webrtc/jsapi/RTCEncodedFrameBase.cpp
RTCEncodedFrameBase::~RTCEncodedFrameBase() {
DetachData();
mData = nullptr;
mozilla::DropJSObjects(this);
}
void RTCEncodedFrameBase::DetachData() {
// We might have handled this in unlink already
if (mGlobal && mData) {
AutoJSAPI jsapi;
if (NS_WARN_IF(!jsapi.Init(mGlobal))) {
return;
}
JS::Rooted<JSObject*> rootedData(jsapi.cx(), mData);
if (rootedData) {
JS::DetachArrayBuffer(jsapi.cx(), rootedData);
} // ignore failure of `JS::DetachArrayBuffer`
}
}
JS::DetachArrayBuffer() fails because the retained view is length-pinned, and reports RangeError: attempting to change pinned length of ArrayBuffer:
// js/src/vm/ArrayBufferObject.cpp
JS_PUBLIC_API bool JS::DetachArrayBuffer(JSContext* cx, HandleObject obj) {
...
if (unwrappedBuffer->isLengthPinned()) { // true
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_ARRAYBUFFER_LENGTH_PINNED);
return false;
}
AutoRealm ar(cx, unwrappedBuffer);
ArrayBufferObject::detach(cx, unwrappedBuffer);
return true;
}
// js/public/friend/ErrorNumbers.msg
MSG_DEF(JSMSG_ARRAYBUFFER_LENGTH_PINNED, 0, JSEXN_RANGEERR, "attempting to change pinned length of ArrayBuffer")
RTCEncodedFrameBase::DetachData() ignores the failed detach and teardown continues.
RTCEncodedFrameState::~RTCEncodedFrameState() destroys the native WebRTC frame, freeing the libwebrtc EncodedImageBuffer:
// dom/media/webrtc/jsapi/RTCEncodedFrameBase.h
struct RTCEncodedFrameState {
std::unique_ptr<webrtc::TransformableFrameInterface> mFrame;
uint64_t mCounter = 0;
unsigned long mTimestamp = 0;
...
// dom/media/webrtc/jsapi/RTCEncodedFrameBase.cpp
RTCEncodedFrameState::~RTCEncodedFrameState() = default;
// third_party/libwebrtc/modules/rtp_rtcp/source/frame_object.h
class RtpFrameObject : public EncodedFrame {
...
private:
// Reference for mutable access.
scoped_refptr<EncodedImageBuffer> image_buffer_;
...
The retained JS view still has byteLength == 1048576 and still points at the freed native payload.
The repro passes that retained view into OPFS; the native file write path reads 1 MiB from the stale pointer and ASAN reports heap-use-after-free:
(OPFS creates NS_NewByteInputStream(..., AsChars(aData), NS_ASSIGNMENT_DEPEND) and NS_ASSIGNMENT_DEPEND refers directly to the caller’s buffer)
// dom/fs/api/FileSystemSyncAccessHandle.cpp
uint64_t FileSystemSyncAccessHandle::ReadOrWrite(
const AllowSharedBufferSource& aBuffer,
const FileSystemReadWriteOptions& aOptions, const bool aRead,
ErrorResult& aRv) {
...
QM_TRY(MOZ_TO_RESULT(NS_NewByteInputStream(
getter_AddRefs(inputStream), AsChars(aData),
NS_ASSIGNMENT_DEPEND)),
CreateAndRejectBoolPromise);
// xpcom/io/nsStringStream.cpp
NS_IMETHODIMP
nsStringInputStream::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure,
uint32_t aCount, uint32_t* aResult) {
...
nsresult rv = aWriter(this, aClosure, source->Data().Elements() + offset, 0,
aCount, aResult);
...
// xpcom/build/NSPRInterposer.cpp
int32_t PR_CALLBACK interposedWrite(PRFileDesc* aFd, const void* aBuf,
int32_t aAmt) {
// If we don't have a valid original function pointer something is very wrong.
NS_ASSERTION(sWriteFn, "NSPR IO Interposing: sWriteFn is NULL");
NSPRIOAutoObservation timer(mozilla::IOInterposeObserver::OpWrite, aFd);
return sWriteFn(aFd, aBuf, aAmt); // heap-use-after-free
}
Here's precise summary:
first write(view):
pins ArrayBuffer length
enters worker sync loop
GC/CC runs
DetachArrayBuffer fails because pinned
native frame is freed anyway
view survives with stale pointer
later native use of view:
reads freed native frame memory -> UAF
| Reporter | ||
Comment 7•4 months ago
|
||
any update on this bug?
Updated•4 months ago
|
Updated•4 months ago
|
Updated•1 month ago
|
Description
•