Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactOut of bounds read in Metrics
DescriptionOut of bounds read in Metrics
ComponentMetrics
Bug ClassOOB
Tracker378623799
Fix commit23479ae0d333 (chromium/src) +260/-218
CISA KEVNot listed
CreditedXiantong Hou of Wuheng Lab and Pisanbao
Disclosed2025-01-14

Changed Functions

FunctionChangeNotes
if
base/metrics/field_trial.cc
modified
for
base/metrics/persistent_histogram_allocator.cc
modified
if
base/metrics/persistent_histogram_allocator.cc
modified

Files Changed

  • base/metrics/field_trial.cc
  • base/metrics/persistent_histogram_allocator.cc
From 23479ae0d3332f5525cfd9491137fc6c0ffcb46a Mon Sep 17 00:00:00 2001
From: Roger McFarlane <rogerm@chromium.org>
Date: Tue, 10 Dec 2024 20:31:23 +0000
Subject: [PATCH] Remove PersistentMemoryAllocator::GetAllocSize()

This CL removes PersistentMemoryAllocator::GetAllocSize() in favor
of allowing various other API entry points to return the alloc size.
This mitigates potential TOCTOU errors where the size of an alloc
is validated by one API then separately fetched in another call. The
size could otherwise be manipulated in between initial validation and
the subsequent fetch.

Bug: 378623799
Change-Id: I8021cf4c07f1a96172deb2a252326e9ffa525798
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6025612
Reviewed-by: Alexei Svitkine <asvitkine@chromium.org>
Commit-Queue: Roger McFarlane <rogerm@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1394492}
---

diff --git a/base/metrics/field_trial.cc b/base/metrics/field_trial.cc
index 829a5c4b..bf2ffe6 100644
--- a/base/metrics/field_trial.cc
+++ b/base/metrics/field_trial.cc
@@ -124,7 +124,7 @@
 }
 
 // Returns the boundary value for comparing against the FieldTrial's added
-// groups for a given |divisor| (total probability) and |entropy_value|.
+// groups for a given `divisor` (total probability) and `entropy_value`.
 FieldTrial::Probability GetGroupBoundaryValue(
     FieldTrial::Probability divisor,
     double entropy_value) {
@@ -138,7 +138,7 @@
   const double kEpsilon = 1e-8;
   const FieldTrial::Probability result =
       static_cast<FieldTrial::Probability>(divisor * entropy_value + kEpsilon);
-  // Ensure that adding the epsilon still results in a value < |divisor|.
+  // Ensure that adding the epsilon still results in a value < `divisor`.
   return std::min(result, divisor - 1);
 }
 
@@ -259,7 +259,7 @@
   if (forced_) {
     DCHECK(!group_name_.empty());
     if (name == group_name_) {
-      // Note that while |group_| may be equal to |kDefaultGroupNumber| on the
+      // Note that while `group_` may be equal to `kDefaultGroupNumber` on the
       // forced trial, it will not have the same value as the default group
       // number returned from the non-forced |FactoryGetFieldTrial()| call,
       // which takes care to ensure that this does not happen.
@@ -326,7 +326,7 @@
 void FieldTrial::EnableBenchmarking() {
   // We don't need to see field trials created via CreateFieldTrial() for
   // benchmarking, because such field trials have only a single group and are
-  // not affected by randomization that |enable_benchmarking_| would disable.
+  // not affected by randomization that `enable_benchmarking_` would disable.
   DCHECK_EQ(0u, FieldTrialList::GetRandomizedFieldTrialCount());
   enable_benchmarking_ = true;
 }
@@ -453,7 +453,7 @@
   if (group_ != kNotFinalized)
     return;
   accumulated_group_probability_ = divisor_;
-  // Here it's OK to use |kDefaultGroupNumber| since we can't be forced and not
+  // Here it's OK to use `kDefaultGroupNumber` since we can't be forced and not
   // finalized.
   DCHECK(!forced_);
   SetGroupChoice(default_group_name_, kDefaultGroupNumber);
@@ -807,7 +807,7 @@
   field_trial = new FieldTrial(name, kTotalProbability, group_name, 0,
                                is_low_anonymity, is_overridden);
   // The group choice will be finalized in this method. So
-  // |is_randomized_trial| should be false.
+  // `is_randomized_trial` should be false.
   FieldTrialList::Register(field_trial, /*is_randomized_trial=*/false);
   // Force the trial, which will also finalize the group choice.
   field_trial->SetForced();
@@ -910,12 +910,12 @@
   if (!field_trial->ref_)
     return false;
 
+  size_t allocated_size = 0;
   const FieldTrial::FieldTrialEntry* entry =
       global_->field_trial_allocator_->GetAsObject<FieldTrial::FieldTrialEntry>(
-          field_trial->ref_);
+          field_trial->ref_, &allocated_size);
+  CHECK(entry);
 
-  size_t allocated_size =
-      global_->field_trial_allocator_->GetAllocSize(field_trial->ref_);
   uint64_t actual_size =
       sizeof(FieldTrial::FieldTrialEntry) + entry->pickle_size;
   if (allocated_size < actual_size)
diff --git a/base/metrics/persistent_histogram_allocator.cc b/base/metrics/persistent_histogram_allocator.cc
index 825c117..dcc2b2a 100644
--- a/base/metrics/persistent_histogram_allocator.cc
+++ b/base/metrics/persistent_histogram_allocator.cc
@@ -89,13 +89,13 @@
 }
 
 // Calculate the number of bytes required to store all of a histogram's
-// "counts". This will return zero (0) if |bucket_count| is not valid.
+// "counts". This will return zero (0) if `bucket_count` is not valid.
 size_t CalculateRequiredCountsBytes(size_t bucket_count) {
   // 2 because each "sample count" also requires a backup "logged count"
   // used for calculating the delta during snapshot operations.
   const size_t kBytesPerBucket = 2 * sizeof(HistogramBase::AtomicCount);
 
-  // If the |bucket_count| is such that it would overflow the return type,
+  // If the `bucket_count` is such that it would overflow the return type,
   // perhaps as the result of a malicious actor, then return zero to
   // indicate the problem to the caller.
   if (bucket_count > std::numeric_limits<size_t>::max() / kBytesPerBucket)
@@ -176,7 +176,7 @@
 PersistentSparseHistogramDataManager::LoadRecords(
     PersistentSampleMapRecords* sample_map_records,
     std::optional<HistogramBase::Sample> until_value) {
-  // DataManager must be locked in order to access the |sample_records_|
+  // DataManager must be locked in order to access the `sample_records_`
   // vectors.
   base::AutoLock auto_lock(lock_);
 
@@ -222,7 +222,7 @@
   }
 
   // Return all references found that have not yet been seen by
-  // |sample_map_records|, up until |until_value| (if applicable).
+  // `sample_map_records`, up until `until_value` (if applicable).
   std::vector<PersistentMemoryAllocator::Reference> new_references;
   CHECK_GE(found_records.size(), sample_map_records->seen_);
   auto new_found_records =
@@ -230,9 +230,9 @@
   new_references.reserve(new_found_records.size());
   for (const auto& new_record : new_found_records) {
     new_references.push_back(new_record.reference);
-    // Maybe references after |until_value| were found. Stop here immediately in
+    // Maybe references after `until_value` were found. Stop here immediately in
     // such a case, since the caller will not expect any more samples after
-    // |until_value|.
+    // `until_value`.
     if (until_value.has_value() && new_record.value == until_value.value()) {
       break;
     }
@@ -321,9 +321,9 @@
   // count data (while these must reference the persistent counts) and always
   // add it to the local list of known histograms (while these may be simple
   // references to histograms in other processes).
+  size_t length = 0;
   PersistentHistogramData* data =
-      memory_allocator_->GetAsObject<PersistentHistogramData>(ref);
-  const size_t length = memory_allocator_->GetAllocSize(ref);
+      memory_allocator_->GetAsObject<PersistentHistogramData>(ref, &length);
 
   // Check that metadata is reasonable: name is null-terminated and non-empty,
   // ID fields have been loaded with a hash of the name (0 is considered
@@ -331,7 +331,7 @@
   if (!data || data->name[0] == '\0' ||
       reinterpret_cast<char*>(data)[length - 1] != '\0' ||
       data->samples_metadata.id == 0 || data->logged_metadata.id == 0 ||
-      // Note: Sparse histograms use |id + 1| in |logged_metadata|.
+      // Note: Sparse histograms use `id + 1` in `logged_metadata`.
       (data->logged_metadata.id != data->samples_metadata.id &&
        data->logged_metadata.id != data->samples_metadata.id + 1) ||
       // Most non-matching values happen due to truncated names. Ideally, we
@@ -374,7 +374,7 @@
     histogram_data->histogram_type = histogram_type;
     histogram_data->flags = flags | HistogramBase::kIsPersistent;
 
-    // |counts_ref| relies on being zero'd out initially. Even though this
+    // `counts_ref` relies on being zero'd out initially. Even though this
     // should always be the case, manually zero it out again here in case there
     // was memory corruption (e.g. if the memory was mapped from a corrupted
     // spare file).
@@ -388,7 +388,7 @@
     size_t bucket_count = bucket_ranges->bucket_count();
     size_t counts_bytes = CalculateRequiredCountsBytes(bucket_count);
     if (counts_bytes == 0) {
-      // |bucket_count| was out-of-range.
+      // `bucket_count` was out-of-range.
       return nullptr;
     }
 
@@ -396,8 +396,8 @@
     // objects for re-use, it would be dangerous for one to hold a reference
     // from a persistent allocator that is not the global one (which is
     // permanent once set). If this stops being the case, this check can
-    // become an "if" condition beside "!ranges_ref" below and before
-    // set_persistent_reference() farther down.
+    // become an `if` condition beside `!ranges_ref` below and before
+    // `set_persistent_reference()` farther down.
     DCHECK_EQ(this, GlobalHistogramAllocator::Get());
 
     // Re-use an existing BucketRanges persistent allocation if one is known;
@@ -434,7 +434,7 @@
     if (ranges_ref && histogram_data) {
       histogram_data->minimum = minimum;
       histogram_data->maximum = maximum;
-      // |bucket_count| must fit within 32-bits or the allocation of the counts
+      // `bucket_count` must fit within 32-bits or the allocation of the counts
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/base/metrics/persistent_memory_allocator_unittest.cc b/base/metrics/persistent_memory_allocator_unittest.cc
index 01ac173..a0b96616 100644
--- a/base/metrics/persistent_memory_allocator_unittest.cc
+++ b/base/metrics/persistent_memory_allocator_unittest.cc
@@ -141,11 +141,12 @@
   ASSERT_TRUE(obj1);
   Reference block1 = allocator_->GetAsReference(obj1);
   ASSERT_NE(0U, block1);
-  EXPECT_NE(nullptr, allocator_->GetAsObject<TestObject1>(block1));
   EXPECT_EQ(nullptr, allocator_->GetAsObject<TestObject2>(block1));
-  EXPECT_LE(sizeof(TestObject1), allocator_->GetAllocSize(block1));
-  EXPECT_GT(sizeof(TestObject1) + kAllocAlignment,
-            allocator_->GetAllocSize(block1));
+  size_t alloc_size_1 = 0;
+  EXPECT_NE(nullptr,
+            allocator_->GetAsObject<TestObject1>(block1, &alloc_size_1));
+  EXPECT_LE(sizeof(TestObject1), alloc_size_1);
+  EXPECT_GT(sizeof(TestObject1) + kAllocAlignment, alloc_size_1);
   PersistentMemoryAllocator::MemoryInfo meminfo1;
   allocator_->GetMemoryInfo(&meminfo1);
   EXPECT_EQ(meminfo0.total, meminfo1.total);
@@ -181,11 +182,12 @@
   ASSERT_TRUE(obj2);
   Reference block2 = allocator_->GetAsReference(obj2);
   ASSERT_NE(0U, block2);
-  EXPECT_NE(nullptr, allocator_->GetAsObject<TestObject2>(block2));
   EXPECT_EQ(nullptr, allocator_->GetAsObject<TestObject1>(block2));
-  EXPECT_LE(sizeof(TestObject2), allocator_->GetAllocSize(block2));
-  EXPECT_GT(sizeof(TestObject2) + kAllocAlignment,
-            allocator_->GetAllocSize(block2));
+  size_t alloc_size_2 = 0;
+  EXPECT_NE(nullptr,
+            allocator_->GetAsObject<TestObject2>(block2, &alloc_size_2));
+  EXPECT_LE(sizeof(TestObject2), alloc_size_2);
+  EXPECT_GT(sizeof(TestObject2) + kAllocAlignment, alloc_size_2);
   PersistentMemoryAllocator::MemoryInfo meminfo2;
   allocator_->GetMemoryInfo(&meminfo2);
   EXPECT_EQ(meminfo1.total, meminfo2.total);
@@ -966,10 +968,10 @@
       uint32_t type_id;
       Reference ref;
       while ((ref = iter.GetNext(&type_id)) != 0) {
+        size_t size = 0;
         const char* data = allocator.GetAsArray<char>(
-            ref, 0, PersistentMemoryAllocator::kSizeAny);
+            ref, 0, PersistentMemoryAllocator::kSizeAny, &size);
         uint32_t type = allocator.GetType(ref);
-        size_t size = allocator.GetAllocSize(ref);
         // Ensure compiler can't optimize-out above variables.
         (void)data;
         (void)type;
Loading diff…

Original Bug Report

reported by bl...@gmail.com

TOCTOU in PersistentHistogramAllocator::GetHistogram

Steps to reproduce the problem

  1. Apply the patch_renderer.diff with --ignore-whitespace argument.
  2. Build the release version of Chrome and start it.
  3. Open a new tab and visit any URL.
  4. Close the tab. If the issue does not trigger, repeat the process of opening a new tab, visiting a URL, and closing the tab.
  5. Since ASAN on Windows cannot detect this vulnerability, You can use WinDbg to attach to the broker process and then perform the above operations to capture the exception.

Problem Description

This vulnerability can be triggered through process such as render, GPU, and utility to cause a crash in the broker processes. The current demonstration shows that in the render process, it requires opening a new page to visit a website and then closing the page to have a chance of triggering it, as this is a TOCTOU bug.

base/metrics/persistent_histogram_allocator.cc

std::unique_ptr<HistogramBase> PersistentHistogramAllocator::GetHistogram(
    Reference ref) {
  PersistentHistogramData* data =
      memory_allocator_->GetAsObject<PersistentHistogramData>(ref);[1]
  const size_t length = memory_allocator_->GetAllocSize(ref);[2]

  if (!data || data->name[0] == '\0' ||
      reinterpret_cast<char*>(data)[length - 1] != '\0' ||
      data->samples_metadata.id == 0 || data->logged_metadata.id == 0 ||
      (data->logged_metadata.id != data->samples_metadata.id &&
       data->logged_metadata.id != data->samples_metadata.id + 1) ||
      [3]HashMetricName(data->name) != data->samples_metadata.id) {
    return nullptr;
  }
  return CreateHistogram(data);
}

When the function reaches point [1], the subsequently called function checks whether block->size is greater than or equal to 0x68.If it is, it will return data, which is a pointer to shared memory. The value of data is shared_memory_base + [shared_memory_base + 0x3c] + 0x10. This shared memory is also mapped in other processes, and is categorized based on metrics and types, such as render, GPU, utility, etc.

However, at point [2], when actually obtaining length, it only checks whether block->size is greater than 0x10.

Since this operation involves shared memory, there exists a TOCTOU (Time-of-Check to Time-of-Use) bug here.

If a child process modifies the value of size between points [1] and [2], it can bypass the subsequent check reinterpret_cast<char*>(data)[length - 1] != '\0'. This check is used to ensure that data->name is a null-terminated string.

Before entering function [3], the code calls strlen to get the length of data->name. If data->name is very long and reaches the end of the shared memory, the strlen function will read out-of-bounds into the next heap block to check for a null terminator.

There are three possible scenarios:

  1. The beginning of the next memory region happens to be 0. In this case, the out-of-bounds access does not have much impact.
  2. The next memory region is not yet allocated. An out-of-bounds access by strlen will cause an exception.
  3. The beginning of the next memory region is not 0. In this case, strlen will return a value larger than the actual length of data->name.

In the third scenario, at point [3], the code will call OPENSSL_memcpy(data + n, in, len), which will lead to an out-of-bounds read.

HashMetricName(std::string_view name) –> MD5Sum(as_byte_span(name), &digest) –> MD5(data.data(), data.size(), digest->a.data()) –> MD5_Update –> crypto_md32_update

For this vulnerability patch, I recommend checking if the length is larger 0x68 after obtaining it. The code is as follows:

--- a/base/metrics/persistent_histogram_allocator.cc
+++ b/base/metrics/persistent_histogram_allocator.cc
@@ -328,7 +328,7 @@ std::unique_ptr<HistogramBase> PersistentHistogramAllocator::GetHistogram(
   // Check that metadata is reasonable: name is null-terminated and non-empty,
   // ID fields have been loaded with a hash of the name (0 is considered
   // unset/invalid).
-  if (!data || data->name[0] == '\0' ||
+  if (length < 0x58 || !data || data->name[0] == '\0' ||
       reinterpret_cast<char*>(data)[length - 1] != '\0' ||
       data->samples_metadata.id == 0 || data->logged_metadata.id == 0 ||
       // Note: Sparse histograms use |id + 1| in |logged_metadata|.

Additional Comments

Chrome Version: Version 131.0.6778.20 (Developer Build) (64-bit) commit 5b1aa5623a75aec826e1c812228173345a5065c3 (HEAD -> beta, origin/branch-heads/6778) Operating System: windows10

Summary

TOCTOU in PersistentHistogramAllocator::GetHistogram

Custom Questions

Type of crash:

browser

Crash state:

0:010> p
chrome!std::__Cr::__constexpr_strlen [inlined in chrome!base::PersistentHistogramAllocator::GetHistogram+0xad]:
00007ffc`1e43fc3d e8aebb3b09      call    chrome!strlen (00007ffc`277fb7f0)
0:010> db 000001B1AD9300C0
000001b1`ad9300c0  61 61 61 61 61 61 61 61-61 61 61 61 61 61 61 61  aaaaaaaaaaaaaaaa
000001b1`ad9300d0  61 61 61 61 61 61 61 61-61 61 61 61 61 61 61 61  aaaaaaaaaaaaaaaa
000001b1`ad9300e0  61 61 61 61 61 61 61 61-61 61 61 61 61 61 61 61  aaaaaaaaaaaaaaaa
000001b1`ad9300f0  61 61 61 61 61 61 61 61-61 61 61 61 61 61 61 61  aaaaaaaaaaaaaaaa
000001b1`ad930100  61 61 61 61 61 61 61 61-61 61 61 61 61 61 61 61  aaaaaaaaaaaaaaaa
000001b1`ad930110  61 61 61 61 61 61 61 61-61 61 61 61 61 61 61 61  aaaaaaaaaaaaaaaa
000001b1`ad930120  61 61 61 61 61 61 61 61-61 61 61 61 61 61 61 61  aaaaaaaaaaaaaaaa
000001b1`ad930130  61 61 61 61 61 61 61 61-61 61 61 61 61 61 61 61  aaaaaaaaaaaaaaaa
0:010> p
(3300.167c): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
chrome!strlen+0x31:
00007ffc`277fb821 488b10          mov     rdx,qword ptr [rax] ds:000001b1`adb30000=????????????????
0:010> db 000001B1ADB30000
000001b1`adb30000  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
000001b1`adb30010  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
000001b1`adb30020  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
000001b1`adb30030  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
000001b1`adb30040  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
000001b1`adb30050  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
000001b1`adb30060  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
000001b1`adb30070  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
0:010> k
 # Child-SP          RetAddr               Call Site
00 00000036`d25fd0b8 00007ffc`1e43fc42     chrome!strlen+0x31 [C:\chromium\src\out\release\minkernel\crts\ucrt\src\appcrt\string\amd64\strlen.asm @ 70] 
01 (Inline Function) --------`--------     chrome!std::__Cr::__constexpr_strlen+0x5 [C:\chromium\src\third_party\libc++\src\include\__string\constexpr_c_functions.h @ 66] 
02 (Inline Function) --------`--------     chrome!std::__Cr::char_traits<char>::length+0x5 [C:\chromium\src\third_party\libc++\src\include\__string\char_traits.h @ 130] 
03 (Inline Function) --------`--------     chrome!std::__Cr::__char_traits_length_checked+0x5 [C:\chromium\src\third_party\libc++\src\include\string_view @ 269] 
04 (Inline Function) --------`--------     chrome!std::__Cr::basic_string_view<char,std::__Cr::char_traits<char> >::basic_string_view+0xa [C:\chromium\src\third_party\libc++\src\include\string_view @ 347] 
05 00000036`d25fd0c0 00007ffc`1e43fb3d     chrome!base::PersistentHistogramAllocator::GetHistogram+0xb2 [C:\chromium\src\base\metrics\persistent_histogram_allocator.cc @ 340] 
06 00000036`d25fd120 00007ffc`2052ed7b     chrome!base::PersistentHistogramAllocator::Iterator::GetNextWithIgnore+0xed [C:\chromium\src\base\metrics\persistent_histogram_allocator.cc @ 314] 
07 (Inline Function) --------`--------     chrome!base::PersistentHistogramAllocator::Iterator::GetNext+0xb [C:\chromium\src\base\metrics\persistent_histogram_allocator.h @ 198] 
08 00000036`d25fd2c0 00007ffc`16da3e98     chrome!metrics::SubprocessMetricsProvider::MergeHistogramDeltasFromAllocator+0x7b [C:\chromium\src\components\metrics\content\subprocess_metrics_provider.cc @ 268] 
09 00000036`d25fd4d0 00007ffc`1e3dfe32     chrome!base::OnceCallback<void ()>::Run+0x58 [C:\chromium\src\base\functional\callback.h @ 156] 
0a 00000036`d25fd550 00007ffc`1e3e0006     chrome!base::internal::PostTaskAndReplyRelay::RunTaskAndPostReply+0x32 [C:\chromium\src\base\threading\post_task_and_reply_impl.h @ 49] 
0b (Inline Function) --------`--------     chrome!base::internal::DecayedFunctorTraits<void (*)(base::internal::PostTaskAndReplyRelay),base::internal::PostTaskAndReplyRelay &&>::Invoke+0x21 [C:\chromium\src\base\functional\bind_internal.h @ 671] 
0c (Inline Function) --------`--------     chrome!base::internal::InvokeHelper<0,base::internal::FunctorTraits<void (*&&)(base::internal::PostTaskAndReplyRelay),base::internal::PostTaskAndReplyRelay &&>,void,0>::MakeItSo+0x21 [C:\chromium\src\base\functional\bind_internal.h @ 930] 
0d (Inline Function) --------`--------     chrome!base::internal::Invoker<base::internal::FunctorTraits<void (*&&)(base::internal::PostTaskAndReplyRelay),base::internal::PostTaskAndReplyRelay &&>,base::internal::BindState<0,1,0,void (*)(base::internal::PostTaskAndReplyRelay),base::internal::PostTaskAndReplyRelay>,void ()>::RunImpl+0x21 [C:\chromium\src\base\functional\bind_internal.h @ 1067] 
0e 00000036`d25fd5e0 00007ffc`16da3e98     chrome!base::internal::Invoker<base::internal::FunctorTraits<void (*&&)(base::internal::PostTaskAndReplyRelay),base::internal::PostTaskAndReplyRelay &&>,base::internal::BindState<0,1,0,void (*)(base::internal::PostTaskAndReplyRelay),base::internal::PostTaskAndReplyRelay>,void ()>::RunOnce+0x36 [C:\chromium\src\base\functional\bind_internal.h @ 980] 
0f 00000036`d25fd660 00007ffc`1e3f7194     chrome!base::OnceCallback<void ()>::Run+0x58 [C:\chromium\src\base\functional\callback.h @ 156] 
10 00000036`d25fd6e0 00007ffc`224e2173     chrome!base::TaskAnnotator::RunTaskImpl+0x154 [C:\chromium\src\base\task\common\task_annotator.cc @ 203] 
11 (Inline Function) --------`--------     chrome!base::TaskAnnotator::RunTask+0x54 [C:\chromium\src\base\task\common\task_annotator.h @ 98] 
12 (Inline Function) --------`--------     chrome!base::internal::TaskTracker::RunTaskImpl+0x78 [C:\chromium\src\base\task\thread_pool\task_tracker.cc @ 677] 
13 00000036`d25fd780 00007ffc`224e14b6     chrome!base::internal::TaskTracker::RunBlockShutdown+0xc3 [C:\chromium\src\base\task\thread_pool\task_tracker.cc @ 670] 
14 (Inline Function) --------`--------     chrome!base::internal::TaskTracker::RunTaskWithShutdownBehavior+0x5b [C:\chromium\src\base\task\thread_pool\task_tracker.cc @ 695] 
15 00000036`d25fd840 00007ffc`224e0c91     chrome!base::internal::TaskTracker::RunTask+0x426 [C:\chromium\src\base\task\thread_pool\task_tracker.cc @ 524] 
16 00000036`d25ff1e0 00007ffc`243741cc     chrome!base::internal::TaskTracker::RunAndPopNextTask+0x2d1 [C:\chromium\src\base\task\thread_pool\task_tracker.cc @ 417] 
17 00000036`d25ff490 00007ffc`24373a28     chrome!base::internal::WorkerThread::RunWorker+0x49c [C:\chromium\src\base\task\thread_pool\worker_thread.cc @ 493] 
18 00000036`d25ff730 00007ffc`1e3864a4     chrome!base::internal::WorkerThread::RunBackgroundPooledWorker+0x18 [C:\chromium\src\base\task\thread_pool\worker_thread.cc @ 385] 
19 00000036`d25ff770 00007ffc`8a387374     chrome!base::`anonymous namespace'::ThreadFunc+0x164 [C:\chromium\src\base\threading\platform_thread_win.cc @ 124] 
1a 00000036`d25ff940 00007ffc`8acbcc91     KERNEL32!BaseThreadInitThunk+0x14
1b 00000036`d25ff970 00000000`00000000     ntdll!RtlUserThreadStart+0x21

Reporter credit:

Xiantong Hou and Pisanbao of Wuheng Lab

Additional Data

Category: Security
Chrome Channel: Dev
Regression: N/A

View on issue tracker