Talos Vulnerability Report

TALOS-2023-1751

Google Chrome VideoEncoder av1_svc_check_reset_layer_rc_flag use-after-free vulnerability

September 25, 2023
CVE Number

CVE-2023-3421

SUMMARY

A use-after-free vulnerability exists in the VideoEncoder av1_svc_check_reset_layer_rc_flag functionality of Google Chrome 113.0.5672.127 (64-bit) and Chromium 115.0.5779.0 (Build) (64-bit). A specially crafted web page can lead to a use-after-free. An attacker can serve a malicious HTML page to trigger this vulnerability.

CONFIRMED VULNERABLE VERSIONS

The versions below were either tested or verified to be vulnerable by Talos or confirmed to be vulnerable by the vendor.

Google Chrome 113.0.5672.127 (64-bit)
Google Chrome Chromium 115.0.5779.0 (Build) (64-bit)

PRODUCT URLS

Chrome - https://www.google.com/chrome/

CVSSv3 SCORE

8.3 - CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:L

CWE

CWE-416 - Use After Free

DETAILS

Google Chrome is a cross-platform web browser, developed by Google.

By continuously switching the encoder configuration (reconfiguring the encoder) through javascript code, it is possible to force Chrome to call the media::Av1VideoEncoder::ChangeOptions function (multiple times). This function executes the SetUpAomConfig function, which prepares the configuration for the video encoder.

// src\media\video\av1_video_encoder.cc
EncoderStatus SetUpAomConfig(const VideoEncoder::Options& opts,
                             aom_codec_enc_cfg_t& config,
                             aom_svc_params_t& svc_params) {
                             
...
  // Setting up SVC parameters
  svc_params = {};
  svc_params.framerate_factor[0] = 1;
  svc_params.number_spatial_layers = 1;
  if (opts.scalability_mode.has_value()) { // here
    switch (opts.scalability_mode.value()) {
      case SVCScalabilityMode::kL1T1:
        // Nothing to do
        break;
      case SVCScalabilityMode::kL1T2:
        svc_params.framerate_factor[0] = 2;
        svc_params.framerate_factor[1] = 1;
        svc_params.number_temporal_layers = 2;
        // Bitrate allocation L0: 60% L1: 40%
        svc_params.layer_target_bitrate[0] =
            60 * config.rc_target_bitrate / 100;
        svc_params.layer_target_bitrate[1] = config.rc_target_bitrate;
        break;
      case SVCScalabilityMode::kL1T3:
        svc_params.framerate_factor[0] = 4;
        svc_params.framerate_factor[1] = 2;
        svc_params.framerate_factor[2] = 1;
        svc_params.number_temporal_layers = 3;

        // Bitrate allocation L0: 50% L1: 20% L2: 30%
        svc_params.layer_target_bitrate[0] =
            50 * config.rc_target_bitrate / 100;
        svc_params.layer_target_bitrate[1] =
            70 * config.rc_target_bitrate / 100;
        svc_params.layer_target_bitrate[2] = config.rc_target_bitrate;

        break;
      default:
        return EncoderStatus(
            EncoderStatus::Codes::kEncoderUnsupportedConfig,
            "Unsupported configuration of scalability layers.");
    }
  }
...

Through the javascript code, we have created first a video encoder with the scalabilityMode set. However, during the loop we are also reconfiguring the encoder again, this time without the scalabilityMode option set. In this case SetUpAomConfig will “skip” the svc_params.number_temporal_layers initialization, which will be set to zero. This parameter will be not validated properly afterwards.

At this point, the future call list will be vpx_codec_enc_config_set -> encoder_set_config -> av1_change_config -> check_reset_rc_flag. The check_reset_rc_flag function is interesting to us because it will lead to the av1_svc_check_reset_layer_rc (explained below).

    // src\third_party\libaom\source\libaom\av1\encoder\rc_utils.h
    static AOM_INLINE void check_reset_rc_flag(AV1_COMP *cpi) {
      RATE_CONTROL *rc = &cpi->rc;
      PRIMARY_RATE_CONTROL *const p_rc = &cpi->ppi->p_rc;
      if (cpi->common.current_frame.frame_number >
          (unsigned int)cpi->svc.number_spatial_layers) {
        if (cpi->ppi->use_svc) {
          av1_svc_check_reset_layer_rc_flag(cpi);   --> we need to reach this one 
        } else {
          if (rc->avg_frame_bandwidth > (3 * rc->prev_avg_frame_bandwidth >> 1) ||
              rc->avg_frame_bandwidth < (rc->prev_avg_frame_bandwidth >> 1)) {
            rc->rc_1_frame = 0;
            rc->rc_2_frame = 0;
            p_rc->bits_off_target = p_rc->optimal_buffer_level;
            p_rc->buffer_level = p_rc->optimal_buffer_level;
          }
        }
      }
    }

In order to reach the av1_svc_check_reset_layer_rc_flag function, the cpi->common.current_frame.frame_number > (unsigned int)cpi->svc.number_spatial_layers and cpi->ppi->use_svc conditions need to be passed. Those conditions can be easily passed by specific javascript code (i.e. multiple videoframes with encode/configure calls), like the PoC code shows.

Now the av1_svc_check_reset_layer_rc function will be reached with the svc->number_temporal_layers set to zero:

    // src\third_party\libaom\source\libaom\av1\encoder\svc_layercontext.c
    void av1_svc_check_reset_layer_rc_flag(AV1_COMP *const cpi) {
      SVC *const svc = &cpi->svc;
      for (int sl = 0; sl < svc->number_spatial_layers; ++sl) {
        // Check for reset based on avg_frame_bandwidth for spatial layer sl.
        int layer = LAYER_IDS_TO_IDX(sl, svc->number_temporal_layers - 1,
                                     svc->number_temporal_layers);          // layer = -1
        LAYER_CONTEXT *lc = &svc->layer_context[layer];                     // ups, negative index!!!
        RATE_CONTROL *lrc = &lc->rc;
        if (lrc->avg_frame_bandwidth > (3 * lrc->prev_avg_frame_bandwidth >> 1) ||
            lrc->avg_frame_bandwidth < (lrc->prev_avg_frame_bandwidth >> 1)) {
          // Reset for all temporal layers with spatial layer sl.
          for (int tl = 0; tl < svc->number_temporal_layers; ++tl) {
            int layer2 = LAYER_IDS_TO_IDX(sl, tl, svc->number_temporal_layers);
            LAYER_CONTEXT *lc2 = &svc->layer_context[layer2];
            RATE_CONTROL *lrc2 = &lc2->rc;
            PRIMARY_RATE_CONTROL *lp_rc2 = &lc2->p_rc;
            PRIMARY_RATE_CONTROL *const lp_rc = &lc2->p_rc;
            lrc2->rc_1_frame = 0;
            lrc2->rc_2_frame = 0;
            lp_rc2->bits_off_target = lp_rc->optimal_buffer_level;
            lp_rc2->buffer_level = lp_rc->optimal_buffer_level;
          }
        }
      }
    }

The important part here is that the svc->number_temporal_layers param ( see LAYER_IDS_TO_IDX macro) will be used to calculate the layer index (int layer). Because we were able to force svc->number_temporal_layers to zero (through SetUpAomConfig function) this will give the negative (-1) index. Now this negative index will be used to get the svc->layer_context pointer (lc). So in this particular case:

    LAYER_CONTEXT *lc = &svc->layer_context[layer]; -> LAYER_CONTEXT *lc = &svc->layer_context[-1];
    // layer_context is a memory pointer, due to negative index we can reference to heap memory 0x3340 (size of LAYER_CONTEXT) bytes before "base" region 

    // src\third_party\libaom\source\libaom\av1\encoder\svc_layercontext.h
      /*!
       * Layer context used for rate control in CBR mode.
       * An array. The index for spatial layer `sl` and temporal layer `tl` is
       * sl * number_temporal_layers + tl.
       */
      LAYER_CONTEXT *layer_context;

This leads to out-of-bounds heap memory access (see the negative array index), which can turn both to a heap use-after-free or heap overflow afterwards.

Crash Information

	POC command line: chrome.exe --no-sandbox C:\poc\poc.html
( TESTED ON WINDOWS X64 )

	=================================================================
	==13184==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x1221d7f5bf30 at pc 0x7ff833b6bb98 bp 0x008e8a5ff000 sp 0x008e8a5ff048
	READ of size 4 at 0x1221d7f5bf30 thread T4
	==13184==WARNING: Failed to use and restart external symbolizer!
	==13184==*** WARNING: Failed to initialize DbgHelp!              ***
	==13184==*** Most likely this means that the app is already      ***
	==13184==*** using DbgHelp, possibly with incompatible flags.    ***
	==13184==*** Due to technical reasons, symbolization might crash ***
	==13184==*** or produce wrong results.                           ***
		#0 0x7ff833b6bb97 in av1_svc_check_reset_layer_rc_flag C:\b\s\w\ir\cache\builder\src\third_party\libaom\source\libaom\av1\encoder\svc_layercontext.c:567
		#1 0x7ff833b3b5ed in av1_change_config C:\b\s\w\ir\cache\builder\src\third_party\libaom\source\libaom\av1\encoder\encoder.c:932
		#2 0x7ff830955655 in encoder_set_config C:\b\s\w\ir\cache\builder\src\third_party\libaom\source\libaom\av1\av1_cx_iface.c:1536
		#3 0x7ff8308c5b87 in vpx_codec_enc_config_set C:\b\s\w\ir\cache\builder\src\third_party\libvpx\source\libvpx\vpx\src\vpx_encoder.c:347
		#4 0x7ff8250b83c5 in media::Av1VideoEncoder::ChangeOptions C:\b\s\w\ir\cache\builder\src\media\video\av1_video_encoder.cc:454
		#5 0x7ff825084901 in base::internal::Invoker<base::internal::BindState<void (media::VideoEncoder::*)(const media::VideoEncoder::Options &, base::RepeatingCallback<void (media::VideoEncoderOutput, absl::optional<std::__Cr::vector<unsigned char,std::__Cr::allocator<unsigned char> > >)>, base::OnceCallback<void (media::TypedStatus<media::EncoderStatusTraits>)>),base::internal::UnretainedWrapper<media::VideoEncoder,base::unretained_traits::MayNotDangle,0>,media::VideoEncoder::Options,base::RepeatingCallback<void (media::VideoEncoderOutput, absl::optional<std::__Cr::vector<unsigned char,std::__Cr::allocator<unsigned char> > >)>,base::OnceCallback<void (media::TypedStatus<media::EncoderStatusTraits>)> >,void ()>::RunOnce C:\b\s\w\ir\cache\builder\src\base\functional\bind_internal.h:976
		#6 0x7ff82f90dd96 in base::TaskAnnotator::RunTaskImpl C:\b\s\w\ir\cache\builder\src\base\task\common\task_annotator.cc:186
		#7 0x7ff8371463bf in base::internal::TaskTracker::RunTaskImpl C:\b\s\w\ir\cache\builder\src\base\task\thread_pool\task_tracker.cc:643
		#8 0x7ff8371475c9 in base::internal::TaskTracker::RunSkipOnShutdown C:\b\s\w\ir\cache\builder\src\base\task\thread_pool\task_tracker.cc:628
		#9 0x7ff83714577a in base::internal::TaskTracker::RunTask C:\b\s\w\ir\cache\builder\src\base\task\thread_pool\task_tracker.cc:485
		#10 0x7ff83714481f in base::internal::TaskTracker::RunAndPopNextTask C:\b\s\w\ir\cache\builder\src\base\task\thread_pool\task_tracker.cc:400
		#11 0x7ff83c61f01e in base::internal::WorkerThread::RunWorker C:\b\s\w\ir\cache\builder\src\base\task\thread_pool\worker_thread.cc:480
		#12 0x7ff83c61e1bf in base::internal::WorkerThread::RunPooledWorker C:\b\s\w\ir\cache\builder\src\base\task\thread_pool\worker_thread.cc:356
		#13 0x7ff82f82ba51 in base::`anonymous namespace'::ThreadFunc C:\b\s\w\ir\cache\builder\src\base\threading\platform_thread_win.cc:133
		#14 0x7ff7c21e58b3 in __asan::AsanThread::ThreadStart C:\b\s\w\ir\cache\builder\src\third_party\llvm\compiler-rt\lib\asan\asan_thread.cpp:278
		#15 0x7ff914027613 in BaseThreadInitThunk+0x13 (C:\Windows\System32\KERNEL32.DLL+0x180017613)
		#16 0x7ff9145426a0 in RtlUserThreadStart+0x20 (C:\Windows\SYSTEM32\ntdll.dll+0x1800526a0)

	Address 0x1221d7f5bf30 is a wild pointer inside of access range of size 0x000000000004.
	SUMMARY: AddressSanitizer: heap-buffer-overflow C:\b\s\w\ir\cache\builder\src\third_party\libaom\source\libaom\av1\encoder\svc_layercontext.c:567 in av1_svc_check_reset_layer_rc_flag
	Shadow bytes around the buggy address:
	  0x1221d7f5bc80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
	  0x1221d7f5bd00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
	  0x1221d7f5bd80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
	  0x1221d7f5be00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
	  0x1221d7f5be80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
	=>0x1221d7f5bf00: fa fa fa fa fa fa[fa]fa fa fa fa fa fa fa fa fa
	  0x1221d7f5bf80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
	  0x1221d7f5c000: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
	  0x1221d7f5c080: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
	  0x1221d7f5c100: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
	  0x1221d7f5c180: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
	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
	Thread T4 created by T0 here:
		#0 0x7ff7c21e4392 in __asan_wrap_CreateThread C:\b\s\w\ir\cache\builder\src\third_party\llvm\compiler-rt\lib\asan\asan_win.cpp:146
		#1 0x7ff82f82a87f in base::`anonymous namespace'::CreateThreadInternal C:\b\s\w\ir\cache\builder\src\base\threading\platform_thread_win.cc:198
		#2 0x7ff83c61ca09 in base::internal::WorkerThread::Start C:\b\s\w\ir\cache\builder\src\base\task\thread_pool\worker_thread.cc:193
		#3 0x7ff837164c95 in base::internal::ThreadGroupImpl::ScopedCommandsExecutor::FlushImpl::<lambda_2>::operator() C:\b\s\w\ir\cache\builder\src\base\task\thread_pool\thread_group_impl.cc:179
		#4 0x7ff8371647b7 in base::internal::ThreadGroupImpl::ScopedCommandsExecutor::WorkerContainer::ForEachWorker<`lambda at ../../base/task/thread_pool/thread_group_impl.cc:178:37'> C:\b\s\w\ir\cache\builder\src\base\task\thread_pool\thread_group_impl.cc:146
		#5 0x7ff837164246 in base::internal::ThreadGroupImpl::ScopedCommandsExecutor::FlushImpl C:\b\s\w\ir\cache\builder\src\base\task\thread_pool\thread_group_impl.cc:178
		#6 0x7ff83715ad8e in base::internal::ThreadGroupImpl::ScopedCommandsExecutor::~ScopedCommandsExecutor C:\b\s\w\ir\cache\builder\src\base\task\thread_pool\thread_group_impl.cc:102
		#7 0x7ff83715aa3b in base::internal::ThreadGroupImpl::Start C:\b\s\w\ir\cache\builder\src\base\task\thread_pool\thread_group_impl.cc:408
		#8 0x7ff832e81f79 in base::internal::ThreadPoolImpl::Start C:\b\s\w\ir\cache\builder\src\base\task\thread_pool\thread_pool_impl.cc:213
		#9 0x7ff832358cbf in content::ChildProcess::ChildProcess C:\b\s\w\ir\cache\builder\src\content\child\child_process.cc:115
		#10 0x7ff83b20df19 in content::RenderProcess::RenderProcess C:\b\s\w\ir\cache\builder\src\content\renderer\render_process.cc:18
		#11 0x7ff83603a650 in content::RenderProcessImpl::RenderProcessImpl C:\b\s\w\ir\cache\builder\src\content\renderer\render_process_impl.cc:106
		#12 0x7ff83603ac97 in content::RenderProcessImpl::Create C:\b\s\w\ir\cache\builder\src\content\renderer\render_process_impl.cc:281
		#13 0x7ff8326a1fb0 in content::RendererMain C:\b\s\w\ir\cache\builder\src\content\renderer\renderer_main.cc:273
		#14 0x7ff82e0a5a5e in content::RunOtherNamedProcessTypeMain C:\b\s\w\ir\cache\builder\src\content\app\content_main_runner_impl.cc:767
		#15 0x7ff82e0a8ac1 in content::ContentMainRunnerImpl::Run C:\b\s\w\ir\cache\builder\src\content\app\content_main_runner_impl.cc:1140
		#16 0x7ff82e0a33e7 in content::RunContentProcess C:\b\s\w\ir\cache\builder\src\content\app\content_main.cc:326
		#17 0x7ff82e0a40f1 in content::ContentMain C:\b\s\w\ir\cache\builder\src\content\app\content_main.cc:343
		#18 0x7ff8220b171d in ChromeMain C:\b\s\w\ir\cache\builder\src\chrome\app\chrome_main.cc:187
		#19 0x7ff7c21363e4 in MainDllLoader::Launch C:\b\s\w\ir\cache\builder\src\chrome\app\main_dll_loader_win.cc:166
		#20 0x7ff7c2132bd8 in main C:\b\s\w\ir\cache\builder\src\chrome\app\chrome_exe_main_win.cc:390
		#21 0x7ff7c2561aab in __scrt_common_main_seh D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:288
		#22 0x7ff914027613 in BaseThreadInitThunk+0x13 (C:\Windows\System32\KERNEL32.DLL+0x180017613)
		#23 0x7ff9145426a0 in RtlUserThreadStart+0x20 (C:\Windows\SYSTEM32\ntdll.dll+0x1800526a0)


	==13184==ADDITIONAL INFO

	==13184==Note: Please include this section with the ASan report.
	Task trace:
		#0 0x7ff82507fe4f in media::OffloadingVideoEncoder::ChangeOptions C:\b\s\w\ir\cache\builder\src\media\video\offloading_video_encoder.cc:74
		#1 0x7ff82507ed0b in media::OffloadingVideoEncoder::WrapCallback<base::OnceCallback<void (media::TypedStatus<media::EncoderStatusTraits>)> > C:\b\s\w\ir\cache\builder\src\media\video\offloading_video_encoder.cc:96
		#2 0x7ff825080329 in media::OffloadingVideoEncoder::Flush C:\b\s\w\ir\cache\builder\src\media\video\offloading_video_encoder.cc:83
		#3 0x7ff8306645a8 in IPC::`anonymous namespace'::ChannelAssociatedGroupController::Accept C:\b\s\w\ir\cache\builder\src\ipc\ipc_mojo_bootstrap.cc:1011


==13184==END OF ADDITIONAL INFO
==13184==ABORTING
VENDOR RESPONSE

https://crbug.com/1447568

TIMELINE

2023-05-22 - Vendor Disclosure
2023-06-26 - Vendor Patch Release
2023-09-25 - Public Release

Credit

Discovered by Piotr Bania of Cisco Talos.