Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chromium issues分析(001-005) #89

Open
xinali opened this issue Mar 7, 2024 · 0 comments
Open

chromium issues分析(001-005) #89

xinali opened this issue Mar 7, 2024 · 0 comments
Labels

Comments

@xinali
Copy link
Owner

xinali commented Mar 7, 2024

chromium issues分析(001-005)

update: 2024.03.21

001-issue-40073847

issue 40073847
模块: UI>Accessibility

问题及分析

RenderFrameImpl::FrameDetached会释放不再使用的render_frame

void RenderFrameImpl::FrameDetached() {  
  // We need to clean up subframes by removing them from the map and deleting  
  // the RenderFrameImpl.  In contrast, the main frame is owned by its  
  // containing RenderViewHost (so that they have the same lifetime), so only  
  // removal from the map is needed and no deletion.  
  auto it = g_frame_map.Get().find(frame_);  
  CHECK(it != g_frame_map.Get().end());  
  CHECK_EQ(it->second, this);  
  g_frame_map.Get().erase(it);  
  
  // RenderAccessibilityManager keeps a reference to the RenderFrame that owns  
  // it, so we need to clear the pointer to prevent invalid access after the  
  // frame gets closed and deleted.  
  render_accessibility_manager_.reset();  
  
  // |frame_| may not be referenced after this, so clear the pointer since  
  // the actual WebLocalFrame may not be deleted immediately and other methods  
  // may try to access it.  
  frame_->Close();  
  frame_ = nullptr;  
  
  if (mhtml_body_loader_client_) {  
    mhtml_body_loader_client_->Detach();  
    mhtml_body_loader_client_.reset();  
  }  
  
  delete this; // [0]  
  // Object is invalid after this point.  
}

释放完了之后ReadAnythingAppController::OnActiveAXTreeIDChanged使用,导致UAF,修复代码主要是添加了在调用render_frame之前的判断

复现步骤

  1. tab页中打开: chrome-untrusted://read-anything-side-panel.top-chrome/
  2. 后退,即可在命令行中发现出现了溢出

修复方法:在代码中增加对render_frame指针的判断,为空则返回

这类漏洞的寻找方法总结:

  1. FrameDetached处设置栈回溯日志
  2. 寻找类似于content::RenderFrame* render_frame这样的代码,render_frame作为其参数或者类成员
  3. 在调用render_frame处添加日志输出,查看触发条件
  4. 想办法在调用前触发Detached释放对应的render_frame
  5. 访问render_frame触发UAF

002-issue-40936128

issue-4093612

模块: UI>Accessibility>ReadingMode

问题及分析

40073847修复的基础上发现的漏洞,在RenderFrameImpl::FrameDetached释放指针之后,并没有直接删除变量render_frame,所以在做render_frame指针判空的时候,其并不为空,该类问题为raw_ptr问题,当判断为非空再次访问UAF
复现步骤

  1. Run ./out/Default/chrome --enable-features=ReadAnything 2>&1 | tools/valgrind/asan/asan_symbolize.py
  2. 在新tab页访问: chrome-untrusted://read-anything-side-panel.top-chrome/
  3. 在同样的tab页面中访问 "https://google.com"

修复方法:不再使用render_frame指针,而是使用render_frame_id来找到对应的render_frame,如果不是则返回

该漏洞为典型问题,应该很多这种类型的漏洞
该问题类似于该代码

void test() {  
  int *pa = new int(42);  
  raw_ptr<int> raw_a(pa);  
  delete pa;  
  
  if (!raw_a) {  
    LOG(INFO) << "ok...";  
  } else {  
    LOG(ERROR) << *raw_a;  
  }
} 

以上两个问题主因都是因为ReadAnythingAppController存放了render_frame的裸指针,在对裸指针使用不当时导致内存问题,
触发的条件都是需要在使用render_frame之前释放其所占用内存

40073847:直接后退,释放render_frame
40936128:在打开的chrome-untrusted://read-anything-side-panel.top-chrome/页面,直接访问另一个url,触发释放render_frame

003-issue-40936633

issue 40936633
模块: UI>Browser>TopChrome>SidePanel

问题及分析

这个漏洞依然存在于阅读模式,在类ReadAnythingUntrustedPageHandler中定义了成员变量raw_ptr<Browser> browser_

void ReadAnythingUntrustedPageHandler::LogTextStyle() {
  // 判断browser_是否为空,非空则调用其来获取profile
  // 由于browser_为裸指针,所以在Browser被析构函数析构后,browser_很有可能为非空
  // 跟上面的render_frame类似,需要在调用前,想办法将browser_析构,render_frame可以通过刷新页面来析构
  // browser_不确定
  if (!browser_ || !browser_->profile()->GetPrefs()) {
    return;
  }

  // This is called when the side panel closes, so retrieving the values from
  // preferences won't happen very often.
  PrefService* prefs = browser_->profile()->GetPrefs();
  int maximum_font_scale_logging =
      GetNormalizedFontScale(kReadAnythingMaximumFontScale);
  double font_scale =
      prefs->GetDouble(prefs::kAccessibilityReadAnythingFontScale);

复现步骤

  1. Start a server at the folder of poc.html
  2. ./Chromium --user-data-dir=./tmp http://127.0.0.1:8605/poc.html about:blank
  3. Open the SidePanel, choose ReadAnything, then drag and merge the poc.html, close the SidePanel

这个需要调试,不能确定browser_什么时候会被析构,这个可能需要详细调试一下,因为browser_以后还会在别的组件上发生问题,需要理清其中的内容
修复方法:将browser_的指针类型由raw_ptr改为WeakPtr

004-issue-40076226

issue-40076226

模块: UI>Browser>ContentSuggestions

根据其中的复现叙述,总是没办法复现,但是可以通过以下方式复现

  1. ./out/Release/chrome --enable-features=Compose,ComposeNudge --user-dir=/tmp/data http://127.0.0.1:8000/poc.html about:blank
  2. 点击输入框,点击提示,再次点击输入框
  3. 点击提示,之后紧接着按Ctrl+W,之后关闭chrome,成功复现

这个问题没有办法直观看出,所以先复现,编译120.0.6090.0的代码

栈回溯

==332175==ERROR: AddressSanitizer: heap-use-after-free on address 0x5110006db540 at pc 0x7efdd4a24e5f bp 0x7ffe770eeeb0 sp 0x7ffe770eeea8
READ of size 1 at 0x5110006db540 thread T0 (chrome)
    #0 0x7efdd4a24e5e in base::internal::(anonymous namespace)::CrashImmediatelyOnUseAfterFree(unsigned long) base/memory/raw_ptr_asan_hooks.cc:53:17
    #1 0x7efdd4a248df in base::internal::(anonymous namespace)::SafelyUnwrapForDereference(unsigned long) base/memory/raw_ptr_asan_hooks.cc:76:5
    #2 0x55dc533ebf0f in BubbleContentsWrapper* base::internal::RawPtrHookableImpl<true>::SafelyUnwrapPtrForDereference<BubbleContentsWrapper>(BubbleContentsWrapper*) base/allocator/partition_allocator/src/partition_alloc/pointers/raw_ptr_hookable_impl.h:85:9
    #3 0x55dc535142a9 in base::raw_ptr<BubbleContentsWrapper, (partition_alloc::internal::RawPtrTraits)0>::GetForDereference() const base/allocator/partition_allocator/src/partition_alloc/pointers/raw_ptr.h:833:12
    #4 0x55dc53513964 in base::raw_ptr<BubbleContentsWrapper, (partition_alloc::internal::RawPtrTraits)0>::operator->() const base/allocator/partition_allocator/src/partition_alloc/pointers/raw_ptr.h:577:12
    #5 0x55dc5350f4b8 in WebUIBubbleDialogView::ClearContentsWrapper() chrome/browser/ui/views/bubble/webui_bubble_dialog_view.cc:81:3
    #6 0x55dc5350fde8 in WebUIBubbleDialogView::OnWidgetClosing(views::Widget*) chrome/browser/ui/views/bubble/webui_bubble_dialog_view.cc:94:3
    #7 0x7efd5af67aed in views::Widget::CloseNow() ui/views/widget/widget.cc:796:14
    #8 0x7efd5b0c7a4d in views::(anonymous namespace)::CloseWindow(aura::Window*) ui/views/widget/native_widget_aura.cc:1275:15
    #9 0x7efd5b183d52 in views::DesktopWindowTreeHostPlatform::CleanUpWindowList(void (*)(aura::Window*)) ui/views/widget/desktop_aura/desktop_window_tree_host_platform.cc:251:5
    #10 0x7efd5b0c79ef in views::Widget::CloseAllSecondaryWidgets() ui/views/widget/native_widget_aura.cc:1297:3
    #11 0x55dc44ec38bc in chrome::HandleAppExitingForPlatform() chrome/browser/lifetime/application_lifetime_aura.cc:50:3
    #12 0x55dc44051fe1 in chrome::OnAppExiting() chrome/browser/lifetime/application_lifetime_desktop.cc:270:3
    #13 0x55dc44050ff0 in chrome::(anonymous namespace)::ShutdownIfNoBrowsers() chrome/browser/lifetime/application_lifetime_desktop.cc:151:3
    #14 0x55dc44051faf in chrome::ShutdownIfNeeded() chrome/browser/lifetime/application_lifetime_desktop.cc:262:3
    #15 0x55dc4242d7e4 in BrowserProcessImpl::Unpin() chrome/browser/browser_process_impl.cc:1470:3
    #16 0x55dc4242d19c in BrowserProcessImpl::OnKeepAliveStateChanged(bool) chrome/browser/browser_process_impl.cc:1142:5
    #17 0x55dc366c8887 in KeepAliveRegistry::OnKeepAliveStateChanged(bool) components/keep_alive_registry/keep_alive_registry.cc:172:14
    #18 0x55dc366ca241 in KeepAliveRegistry::Unregister(KeepAliveOrigin, KeepAliveRestartOption) components/keep_alive_registry/keep_alive_registry.cc:160:5
    #19 0x55dc366d8a10 in ScopedKeepAlive::~ScopedKeepAlive() components/keep_alive_registry/scoped_keep_alive.cc:17:37
    #20 0x55dc3c0e25ca in std::__Cr::default_delete<ScopedKeepAlive>::operator()(ScopedKeepAlive*) const third_party/libc++/src/include/__memory/unique_ptr.h:68:5
    #21 0x55dc3c0e254f in std::__Cr::unique_ptr<ScopedKeepAlive, std::__Cr::default_delete<ScopedKeepAlive>>::reset(ScopedKeepAlive*) third_party/libc++/src/include/__memory/unique_ptr.h:297:7
    #22 0x55dc50d5b45f in Browser::UnregisterKeepAlive() chrome/browser/ui/browser.cc:1180:15
    #23 0x55dc50e5ad53 in BrowserList::RemoveBrowser(Browser*) chrome/browser/ui/browser_list.cc:115:12
    #24 0x55dc50d53faf in Browser::~Browser() chrome/browser/ui/browser.cc:590:3
    #25 0x55dc50d551d8 in Browser::~Browser() chrome/browser/ui/browser.cc:573:21
    #26 0x55dc50d7eee2 in std::__Cr::default_delete<Browser>::operator()(Browser*) const third_party/libc++/src/include/__memory/unique_ptr.h:68:5
    #27 0x55dc50d7ee5f in std::__Cr::unique_ptr<Browser, std::__Cr::default_delete<Browser>>::reset(Browser*) third_party/libc++/src/include/__memory/unique_ptr.h:297:7
    #28 0x55dc50d7ebe8 in std::__Cr::unique_ptr<Browser, std::__Cr::default_delete<Browser>>::~unique_ptr() third_party/libc++/src/include/__memory/unique_ptr.h:263:75
    #29 0x55dc5265d2e2 in BrowserView::~BrowserView() chrome/browser/ui/views/frame/browser_view.cc:1093:1
    #30 0x55dc5265d6b8 in BrowserView::~BrowserView() chrome/browser/ui/views/frame/browser_view.cc:1048:29
    #31 0x7efd5ae75a19 in views::View::~View() ui/views/view.cc:271:9
    #32 0x7efd5b002bf8 in views::NonClientFrameView::~NonClientFrameView() ui/views/window/non_client_view.cc:30:41
    #33 0x55dc52a5a59b in BrowserNonClientFrameView::~BrowserNonClientFrameView() chrome/browser/ui/views/frame/browser_non_client_frame_view.cc:69:1
    #34 0x55dc53ca4297 in OpaqueBrowserFrameView::~OpaqueBrowserFrameView() chrome/browser/ui/views/frame/opaque_browser_frame_view.cc:148:52
    #35 0x55dc53c028e8 in BrowserFrameViewLinux::~BrowserFrameViewLinux() chrome/browser/ui/views/frame/browser_frame_view_linux.cc:28:47

根据栈分析崩溃原因

// chrome/browser/ui/views/bubble/webui_bubble_dialog_view.cc
void WebUIBubbleDialogView::ClearContentsWrapper() {
  if (!contents_wrapper_)
    return;
  DCHECK_EQ(this, contents_wrapper_->GetHost().get()); <-------- [0]
  DCHECK_EQ(web_view_->web_contents(), contents_wrapper_->web_contents());
  web_view_->SetWebContents(nullptr);
  contents_wrapper_->web_contents()->WasHidden();
  contents_wrapper_->SetHost(nullptr);
  contents_wrapper_ = nullptr;
}

// chrome/browser/ui/views/bubble/webui_bubble_dialog_view.h
class WebUIBubbleDialogView : public views::WidgetObserver,
                              public views::BubbleDialogDelegateView,
                              public BubbleContentsWrapper::Host {
 //...
 private:
  // A handler to handle unhandled keyboard messages coming back from the
  // renderer process.
  views::UnhandledKeyboardEventHandler unhandled_keyboard_event_handler_;

  raw_ptr<BubbleContentsWrapper> contents_wrapper_;   <------- [1]
  raw_ptr<views::WebView> web_view_;
  absl::optional<gfx::Rect> bubble_anchor_;

  base::ScopedObservation<views::Widget, views::WidgetObserver>
      bubble_widget_observation_{this};

  base::WeakPtrFactory<WebUIBubbleDialogView> weak_factory_{this};
};


// chromium/src/chrome/browser/ui/views/compose/chrome_compose_dialog_controller.cc
void ChromeComposeDialogController::ShowComposeDialog(
    views::View* anchor_view,
    const gfx::RectF& element_bounds_in_screen) {
  if (!web_contents_) {
    return;
  }

  Profile* profile =
      Profile::FromBrowserContext(web_contents_->GetBrowserContext());
  bubble_wrapper_ = std::make_unique<BubbleContentsWrapperT<ComposeUI>>( <------ [2]
      GURL(kComposeURL), profile, IDS_COMPOSE_TITLE);
  bubble_wrapper_->ReloadWebContents();

  // This WebUI needs to know the calling BrowserContents so that the compose
  // request/result can be properly associated with the triggering form.
  bubble_wrapper_->GetWebUIController()->set_triggering_web_contents(
      web_contents_.get());
  auto bubble_view = std::make_unique<WebUIBubbleDialogView>( <------ [3]
      anchor_view, bubble_wrapper_.get(),
      gfx::ToRoundedRect(element_bounds_in_screen));

  // Allows the bubble bounds to escape the browser window.
  bubble_view->set_has_parent(false);

  auto weak_ptr = bubble_view->GetWeakPtr();
  views::BubbleDialogDelegateView::CreateBubble(std::move(bubble_view));

  weak_ptr->set_adjust_if_offscreen(true);
  weak_ptr->ShowUI();
}
  1. 代码[2]初始化ChromeComposeDialogController的成员变量bubble_wrapper_
  2. 代码[3]会将bubble_wrapper_传给WebUIBubbleDialogView的成员变量contents_wrapper_,其类型raw_ptr<BubbleContentsWrapper>
    正常的处理逻辑日志信息
[527978:527978:0315/151833.088083:ERROR:debug_logger.h(25)] WebUIBubbleDialogView destructor called
[527978:527978:0315/151835.971764:ERROR:debug_logger.h(25)] ChromeComposeDialogController destructor called
[527978:527978:0315/151839.479424:ERROR:debug_logger.h(25)] WebUIBubbleDialogView destructor called
[527978:527978:0315/151846.823749:ERROR:debug_logger.h(25)] ChromeComposeDialogController destructor called

WebUIBubbleDialogView destructor先被调用,然后ChromeComposeDialogController destructor被调用
如果ChromeComposeDialogController的析构函数在WebUIBubbleDialogView::ClearContentsWrapper之前被调用,就会造成UAF
成功触发的日志信息

➜  src git:(120.0.6090.0) ./out/release_asan/chrome --enable-features=Compose,ComposeNudge --enable-logging -v=0 --user-dir=/tmp/data http://127.0.0.1:8000/poc.html about:blank
[701973:701973:0320/163430.719429:ERROR:debug_logger.h(25)] WebUIBubbleDialogView destructor called
[701973:701973:0320/163440.479674:ERROR:debug_logger.h(25)] ChromeComposeDialogController destructor called
[701973:701973:0320/163441.231124:ERROR:debug_logger.h(25)] ChromeComposeDialogController destructor called

=================================================================
==701973==ERROR: AddressSanitizer: heap-use-after-free on address 0x5110006e16c0 at pc 0x7f4e0ea24e5f bp 0x7ffd3d68c290 sp 0x7ffd3d68c288
READ of size 1 at 0x5110006e16c0 thread T0 (chrome)
    #0 0x7f4e0ea24e5e in base::internal::(anonymous namespace)::CrashImmediatelyOnUseAfterFree(unsigned long) base/memory/raw_ptr_asan_hooks.cc:53:17
    #1 0x7f4e0ea248df in base::internal::(anonymous namespace)::SafelyUnwrapForDereference(unsigned long) base/memory/raw_ptr_asan_hooks.cc:76:5
    #2 0x55887e72736f in BubbleContentsWrapper* base::internal::RawPtrHookableImpl<true>::SafelyUnwrapPtrForDereference<BubbleContentsWrapper>(BubbleContentsWrapper*) base/allocator/partition_allocator/src/partition_alloc/pointers/raw_ptr_hookable_impl.h:85:9
    #3 0x55887e84faf9 in base::raw_ptr<BubbleContentsWrapper, (partition_alloc::internal::RawPtrTraits)0>::GetForDereference() const base/allocator/partition_allocator/src/partition_alloc/pointers/raw_ptr.h:833:12
    #4 0x55887e84ee54 in base::raw_ptr<BubbleContentsWrapper, (partition_alloc::internal::RawPtrTraits)0>::operator->() const base/allocator/partition_allocator/src/partition_alloc/pointers/raw_ptr.h:577:12
    #5 0x55887e84a9a8 in WebUIBubbleDialogView::ClearContentsWrapper() chrome/browser/ui/views/bubble/webui_bubble_dialog_view.cc:81:3
    #6 0x55887e84b2d8 in WebUIBubbleDialogView::OnWidgetClosing(views::Widget*) chrome/browser/ui/views/bubble/webui_bubble_dialog_view.cc:94:3
    #7 0x7f4d94f67aed in views::Widget::CloseNow() ui/views/widget/widget.cc:796:14
    #8 0x7f4d950c7a4d in views::(anonymous namespace)::CloseWindow(aura::Window*) ui/views/widget/native_widget_aura.cc:1275:15
    #9 0x7f4d95183d52 in views::DesktopWindowTreeHostPlatform::CleanUpWindowList(void (*)(aura::Window*)) ui/views/widget/desktop_aura/desktop_window_tree_host_platform.cc:251:5
    #10 0x7f4d950c79ef in views::Widget::CloseAllSecondaryWidgets() ui/views/widget/native_widget_aura.cc:1297:3
    #11 0x5588701fed1c in chrome::HandleAppExitingForPlatform() chrome/browser/lifetime/application_lifetime_aura.cc:50:3
    #12 0x55886f38d441 in chrome::OnAppExiting() chrome/browser/lifetime/application_lifetime_desktop.cc:270:3
    #13 0x55886f38c450 in chrome::(anonymous namespace)::ShutdownIfNoBrowsers() chrome/browser/lifetime/application_lifetime_desktop.cc:151:3
    #14 0x55886f38d40f in chrome::ShutdownIfNeeded() chrome/browser/lifetime/application_lifetime_desktop.cc:262:3
    #15 0x55886d768c44 in BrowserProcessImpl::Unpin() chrome/browser/browser_process_impl.cc:1470:3
    #16 0x55886d7685fc in BrowserProcessImpl::OnKeepAliveStateChanged(bool) chrome/browser/browser_process_impl.cc:1142:5

想办法来造成这种情况,得看一下ChromeComposeDialogController的析构函数会在什么时候被调用

[702205:702205:0320/163815.220919:ERROR:debug_logger.h(25)] ChromeComposeDialogController destructor called

[702205:702205:0320/163815.221174:INFO:debug_logger.h(26)] chromium(STACK): ~DebugLogger#0 0x7fd53c1c202c base::debug::CollectStackTrace() [../../base/debug/stack_trace_posix.cc:1040:7]
#1 0x7fd53c1725fa base::debug::StackTrace::StackTrace() [../../base/debug/stack_trace.cc:221:12]
#2 0x7fd53c1725b5 base::debug::StackTrace::StackTrace() [../../base/debug/stack_trace.cc:218:28]
#3 0x55aaddbee941 DebugLogger::~DebugLogger() [../../xinali/debug_logger.h:26:9]
#4 0x55aaddec7032 ChromeComposeDialogController::~ChromeComposeDialogController() [../../chrome/browser/ui/views/compose/chrome_compose_dialog_controller.cc:36:63]
#5 0x55aaddec7069 ChromeComposeDialogController::~ChromeComposeDialogController() [../../chrome/browser/ui/views/compose/chrome_compose_dialog_controller.cc:36:63]
#6 0x55aadcc9644c std::__Cr::default_delete<>::operator()() [../../third_party/libc++/src/include/__memory/unique_ptr.h:68:5]
#7 0x55aadcc963da std::__Cr::unique_ptr<>::reset() [../../third_party/libc++/src/include/__memory/unique_ptr.h:297:7]
#8 0x55aadcc94769 std::__Cr::unique_ptr<>::~unique_ptr() [../../third_party/libc++/src/include/__memory/unique_ptr.h:263:75]
#9 0x55aadcc93823 ChromeComposeClient::~ChromeComposeClient() [../../chrome/browser/compose/chrome_compose_client.cc:63:43]
#10 0x55aadcc938c9 ChromeComposeClient::~ChromeComposeClient() [../../chrome/browser/compose/chrome_compose_client.cc:63:43]
#11 0x7fd53bff126c std::__Cr::default_delete<>::operator()() [../../third_party/libc++/src/include/__memory/unique_ptr.h:68:5]
#12 0x7fd53bff11fa std::__Cr::unique_ptr<>::reset() [../../third_party/libc++/src/include/__memory/unique_ptr.h:297:7]
#13 0x7fd53bfeda49 std::__Cr::unique_ptr<>::~unique_ptr() [../../third_party/libc++/src/include/__memory/unique_ptr.h:263:75]
#14 0x7fd53bfeea59 std::__Cr::pair<>::~pair() [../../third_party/libc++/src/include/__utility/pair.h:81:29]
#15 0x7fd53bfeea2d _ZNSt4__Cr12__destroy_atINS_4pairIPKvNS_10unique_ptrIN4base16SupportsUserData4DataENS_14default_deleteIS7_EEEEEETnNS_9enable_ifIXntsr8is_arrayIT_EE5valueEiE4typeELi0EEEvPSD_ [../../third_party/libc++/src/include/__memory/construct_at.h:69:13]
#16 0x7fd53bfee9d5 _ZNSt4__Cr10destroy_atINS_4pairIPKvNS_10unique_ptrIN4base16SupportsUserData4DataENS_14default_deleteIS7_EEEEEETnNS_9enable_ifIXnt10is_array_vIT_EEiE4typeELi0EEEvPSD_ [../../third_party/libc++/src/include/__memory/construct_at.h:104:5]
#17 0x7fd53bfee9b9 std::__Cr::allocator_traits<>::destroy<>() [../../third_party/libc++/src/include/__memory/allocator_traits.h:323:9]
#18 0x7fd53bfee98d absl::container_internal::map_slot_policy<>::destroy<>() [../../third_party/abseil-cpp/absl/container/internal/container_memory.h:419:7]
#19 0x7fd53bfee95d absl::container_internal::FlatHashMapPolicy<>::destroy<>() [../../third_party/abseil-cpp/absl/container/flat_hash_map.h:578:5]
#20 0x7fd53bfee8ed absl::container_internal::common_policy_traits<>::destroy<>() [../../third_party/abseil-cpp/absl/container/internal/common_policy_traits.h:50:5]
#21 0x7fd53bfee703 absl::container_internal::raw_hash_set<>::destroy_slots() [../../third_party/abseil-cpp/absl/container/internal/raw_hash_set.h:2562:9]
#22 0x7fd53bfee5f1 absl::container_internal::raw_hash_set<>::destructor_impl() [../../third_party/abseil-cpp/absl/container/internal/raw_hash_set.h:2579:5]
#23 0x7fd53bfeefd5 absl::container_internal::raw_hash_set<>::~raw_hash_set() [../../third_party/abseil-cpp/absl/container/internal/raw_hash_set.h:1973:21]
#24 0x7fd53bfeefb5 absl::container_internal::raw_hash_map<>::~raw_hash_map() [../../third_party/abseil-cpp/absl/container/internal/raw_hash_map.h:33:7]
#25 0x7fd53bfeddd5 absl::flat_hash_map<>::~flat_hash_map() [../../third_party/abseil-cpp/absl/container/flat_hash_map.h:113:7]
#26 0x7fd53bfed57d base::SupportsUserData::~SupportsUserData() [../../base/supports_user_data.cc:108:1]
========== <-------
#27 0x7fd5349f08cd content::WebContents::~WebContents() [../../content/public/browser/web_contents.h:371:35]
#28 0x7fd534965c6c content::WebContentsImpl::~WebContentsImpl() [../../content/browser/web_contents/web_contents_impl.cc:1237:1]
#29 0x7fd534966109 content::WebContentsImpl::~WebContentsImpl() [../../content/browser/web_contents/web_contents_impl.cc:1126:37]
========== <-------
#30 0x55aad3c145dc std::__Cr::default_delete<>::operator()() [../../third_party/libc++/src/include/__memory/unique_ptr.h:68:5]
#31 0x55aad3c1456a std::__Cr::unique_ptr<>::reset() [../../third_party/libc++/src/include/__memory/unique_ptr.h:297:7]
#32 0x55aadcfe599b TabStripModel::SendDetachWebContentsNotifications() [../../chrome/browser/ui/tabs/tab_strip_model.cc:528:27]
#33 0x55aadcfe8c81 TabStripModel::CloseTabs() [../../chrome/browser/ui/tabs/tab_strip_model.cc:1928:5]

通过上述的箭头所指信息可以发现,想要触发ChromeComposeDialogController::~ChromeComposeDialogController(),需要当前网页的WebContents析构,
可以发现,上述复现过程应该是通过Ctrl+W直接关闭tab页面从而触发当前网页的WebContents析构,其实通过刷新网页同样可以触发WebContents析构,
但是不能通过点击刷新来刷新,因为点击的话会先触发WebUIBubbleDialogView析构,可以通过Ctrl+Shift+R来实现

整体的UAF逻辑类似于

class TestUnique {
private:
    int *a = nullptr;
    int *b = nullptr;

public:
    void display() {
        if (a) {
            std::cout << "a is " << *a << std::endl;
        }
    }
    void set_a(int *c) {
        a = c;
    }
    void set_b(int *c) {
        b = c;
    }
};

// UAF
// test_a会在test_unique_raw结束时被释放
// 当test_unique再次访问a时,导致UAF
void test_unique_raw(TestUnique *test_unique) {
    std::unique_ptr<int> test_a = std::make_unique<int>(3);
    test_unique->set_a(test_a.get());
}

void test_weak_raw(TestUnique *test_unique) {
    std::shared_ptr<int> test_share = std::make_shared<int>(20);
    std::weak_ptr<int> test_b = test_share;
    // 这里就会出错,weak_ptr无法直接赋值给裸指针
    // test_unique->set_b(test_b);
}

// ./run.sh TestUniqueRawPtr
TEST(CCLearningTest, TestUniqueRawPtr) {
    TestUnique *test_unique = new TestUnique();
    test_unique_raw(test_unique);
    test_unique->display();
    delete test_unique;
}

005-issue-40071901

issue-40071901
模块: UI>Browser>Omnibox
漏洞代码

//https://source.chromium.org/chromium/chromium/src/+/main:components/omnibox/browser/omnibox_controller.cc;l=26-34;drc=5ca8523b8c9ff9a7b3e7eb349d8fc7566f660675
OmniboxController::OmniboxController(OmniboxView* view,
                                     std::unique_ptr<OmniboxClient> client)
    : client_(std::move(client)),
      edit_model_(std::make_unique<OmniboxEditModel>(
          /*omnibox_controller=*/this,
          view)),
      autocomplete_controller_(std::make_unique<AutocompleteController>(		<------ [0]
          client_->CreateAutocompleteProviderClient(),
          AutocompleteClassifier::DefaultOmniboxProviders())) {
  // Directly observe omnibox's `AutocompleteController` instance - i.e., when
  // `view` is provided in the constructor. In the case of realbox - i.e., when
  // `view` is not provided in the constructor - `RealboxHandler` directly
  // observes the `AutocompleteController` instance itself.
  if (view) {
    autocomplete_controller_->AddObserver(this);		<------ [1]
  }
          
// https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/webui/omnibox_popup/omnibox_popup_ui.cc;l=70-72;drc=46a961c0e060305fefce1c0a824c52550b3a7440       
void OmniboxPopupUI::BindInterface(
    mojo::PendingReceiver<omnibox::mojom::PageHandler> pending_page_handler) {
  OmniboxController* controller = g_omnibox_controller;
  g_omnibox_controller = nullptr;

  handler_ = std::make_unique<RealboxHandler>(		<------ [2]
      std::move(pending_page_handler), Profile::FromWebUI(web_ui()),
      web_ui()->GetWebContents(), &metrics_reporter_, controller);
}

上述代码中用到了设计模式中的观察者模式,观察者模式是一种设计模式,
允许一个对象(称为“观察者Observer”)自动接收到另一个对象(称为“主题Subject”或“被观察者”)状态变化的通知。
实现这个模式通常涉及创建一个观察者接口,它定义了接收更新的方法。被观察者维护一个观察者列表,
当其状态发生变化时,会遍历这个列表并调用每个观察者的更新方法。
其中通过[1]可以看出OmniboxController作为ObserverAutocompleteController作为Subject

// https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/webui/omnibox_popup/omnibox_popup_ui.h;l=52;drc=f4a00cc248dd2dc8ec8759fb51620d47b5114090
class OmniboxPopupUI : public ui::MojoWebUIController {
 public:
  explicit OmniboxPopupUI(content::WebUI* web_ui);
  // ...
  // Instantiates the implementor of color_change_listener::mojom::PageHandler
  // mojo interface passing the pending receiver that will be internally bound.
  void BindInterface(
      mojo::PendingReceiver<color_change_listener::mojom::PageHandler>
          pending_receiver);

  RealboxHandler* handler() { return handler_.get(); }

 private:
  std::unique_ptr<RealboxHandler> handler_; <----- [3]


// https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/webui/realbox/realbox_handler.h;l=46;drc=f4a00cc248dd2dc8ec8759fb51620d47b5114090
class RealboxHandler : public omnibox::mojom::PageHandler,
                       public AutocompleteController::Observer,
                       public LocationBarModel {
 public:

  // Note: `omnibox_controller` may be null for the Realbox, in which case
  //  an internally owned controller is created and used.
  RealboxHandler(
      mojo::PendingReceiver<omnibox::mojom::PageHandler> pending_page_handler,
      Profile* profile,
      content::WebContents* web_contents,
      MetricsReporter* metrics_reporter,
      OmniboxController* omnibox_controller);

  // Handle observers to be notified of WebUI changes.
  void AddObserver(OmniboxWebUIPopupChangeObserver* observer);
  void RemoveObserver(OmniboxWebUIPopupChangeObserver* observer);
  bool HasObserver(const OmniboxWebUIPopupChangeObserver* observer) const;


 private:
  OmniboxEditModel* edit_model() const;
  AutocompleteController* autocomplete_controller() const; <------ [4]

其中OmniboxPopupUI有成员变量std::unique_ptr<RealboxHandler> handler_;,通过分析代码[3,4],可以发现handler_可以视为Observer
那么简单点考虑OmniboxPopupUI作为Observer
将上述的整体逻辑简化,可以更好的理解其中的观察者模式

// subject
class AutocompleteController {
public:
    // observer_list[] = {OmniboxController, RealboxHandler(OmniboxPopupUI)}; 
    void detach(Observer *observer) {  
        observer_list.remove(observer);
    } 
    void attach(Observer *observer) {   
        observer_list.add(observer);
    }
private:
    std::list<Observer*> observer_list;
}

// observer1
class OmniboxController { 
public:
    OmniboxController(AutocompleteController *autocomplete_controller) {
        autocomplete_controller_ = autocomplete_controller;
        autocomplete_controller_.attach(this);
    }
    void remove_me_from_observer() {
        autocomplete_controller_.detach(this);
    }
private:
    AutocompleteController *autocomplete_controller_;
}

// observer2
class OmniboxPopupUI {
public:
    OmniboxPopupUI(AutocompleteController *autocomplete_controller) {
        autocomplete_controller_ = autocomplete_controller;
        autocomplete_controller_.attach(this);
    }
    void remove_me_from_observer() {
        autocomplete_controller_.detach(this);
    }
private:
    AutocompleteController *autocomplete_controller_;
}

void func() { 
    AutocompleteController autocomple_controller;
    OmniboxController omnibox_controller{autocomple_controller};
    OmniboxPopupUI omnibox_popup_ui{autocomple_controller}; 
    // 正常逻辑
    omnibox_popup_ui.remove_me_from_observer();
    omnibox_controller.remove_me_from_observer();
    delete ominibox_popup_ui;
    delete omnibox_controller;
    delete autocomplete_controller;

    // UAF
    delete autocomplete_controller;
    delete omnibox_controller; 
    delete ominibox_popup_ui;
}

通过简化的代码可以发现,如果SubjectObserver之前释放,那么再释放Observer时就会导致UAF
具体测试观察者模式先释放Subject后释放Observer导致UAF的效果,可以看我的测试代码

@xinali xinali added the Chromium label Mar 7, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant