CVE-2026-87444
Overview
Background
- `libyuv`
- Chrome’s YUV/ARGB image conversion and scaling library, used across the media and codec pipelines.
- `asm volatile`
- a GCC/Clang inline-assembly statement whose input, output, and clobber lists are the only contract the compiler honors about which registers it reads or destroys.
- `pshufb`
- an SSSE3 byte-shuffle instruction that selects destination bytes from a source register according to an index mask (here loaded from constants like
kShuf0). - `ScaleRowDown34_SSSE3`
- an SSSE3 down-scaler that reduces a row to 3/4 width using precomputed shuffle masks held in
xmm3/xmm4/xmm5.
Root Cause Analysis
The affected SSSE3 scalers in scale_gcc.cc split their inline assembly into two or three separate asm volatile blocks: an initial block loaded constant masks (kShuf0, kShuf1, kShuf2, kMadd01, etc.) into xmm registers, and a later block executed the pixel loop assuming those xmm registers still held the loaded values. The invariant violated is that register state does not carry across disjoint asm volatile statements: the loader block never declared xmm3/xmm4/xmm5 as outputs or clobbers, so the compiler was free to reuse or overwrite those registers between blocks, leaving the loop’s pshufb/pmaddubsw operands undefined. With a corrupted shuffle-index mask, pshufb selects wrong source bytes and the routine writes malformed pixel data, and depending on register allocation and optimization level the scaling loop can operate on inconsistent state.
The fix consolidates each function’s stages into a single asm volatile block with named operand constraints and a complete clobber list, so the constants are loaded and consumed within one region the compiler treats atomically. Because there is now exactly one contiguous assembly statement, no compiler-inserted code or reallocation can clobber the mask registers between load and use.
xmm register contents to persist across separate asm volatile blocks without declaring them as outputs, inputs, or clobbers, which the compiler is never obligated to preserve. Merging the stages into one asm volatile block with named operands and a full clobber list makes the load-then-use dependency explicit and inseparable.Attack Path
- Deliver crafted media A remote page or file feeds image/video content whose dimensions route decoding or resizing through one of the affected SSSE3 scaling paths.
- Trigger split-block execution
The scaler runs the constant-loader
asmblock and then the pixel-loopasmblock as two independent statements. - Compiler clobbers mask registers
Under the right build/optimization, the compiler reuses
xmm3/xmm4/xmm5(or another mask register) between the blocks because they were never declared, so the loop reads a stale or arbitrary shuffle mask. - Corrupt shuffle/write
pshufbwith the wrong index mask, combined with the fixed pointer arithmetic, produces malformed output written to the destination buffer, corrupting memory contents processed downstream.
Impact Assessment
libyuv scaling runs, typically a renderer or GPU/media utility process. Exploitation depends on the specific compiler and optimization decisions that actually clobber the undeclared mask registers, so the condition is build-dependent rather than universally reachable. The metadata classifies this as high-severity memory corruption in Codecs, without a published CVSS or exploit primitive to assert beyond the diff.Files Changed
source/scale_gcc.cc
Audit Directions
- Split `asm volatile` state carryFlag any function that loads values in one
asm volatileblock and consumes them in a later block, since register/flag state is not guaranteed to persist between disjoint statements. - Missing clobber/operand declarationsAudit inline assembly whose used
xmm/general registers are absent from the output, input, or clobber lists, as the compiler may reuse them freely. - Numbered vs. named operandsPrefer converting positional (
%0,%1) operands to named constraints across the codebase to reduce mis-wiring of constants and loop registers in multi-stage SIMD kernels.
Patch
From 5689f16923f516ba6eb5b5a3ee11dc90065a3641 Mon Sep 17 00:00:00 2001 From: Frank Barchard <[email protected]> Date: Mon, 24 Aug 2026 16:10:25 -0700 Subject: [PATCH] Refactor multi-stage SSSE3 scalers into single asm blocks Consolidate split asm volatile blocks into single blocks across SSSE3 scaling functions in scale_gcc.cc. This prevents potential register corruption or clobbering by compiler optimizations between disjoint asm blocks, and uses named operand constraints for clarity and safety. List of affected functions in scale_gcc.cc: - ScaleRowDown34_SSSE3: merge 2 asm blocks into 1 - ScaleRowDown34_1_Box_SSSE3: merge 3 asm blocks into 1 - ScaleRowDown34_0_Box_SSSE3: merge 3 asm blocks into 1 - ScaleRowDown38_2_Box_SSSE3: merge 2 asm blocks into 1 - ScaleRowDown38_3_Box_SSSE3: merge 2 asm blocks into 1 - ScaleARGBFilterCols_SSSE3: merge 2 asm blocks into 1 Test: ninja -C out/Release libyuv_unittest && ./out/Release/libyuv_unittest Test: ninja -C out/Release_x86 libyuv_unittest && ./out/Release_x86/libyuv_unittest Bug: 489489002 Change-Id: I900a590db2ef21b9f019a115699d3a270877ed28 TAG=agy Reviewed-on: https://chromium-review.googlesource.com/c/libyuv/libyuv/+/8280666 Reviewed-by: Wan-Teh Chang <[email protected]> Commit-Queue: Frank Barchard <[email protected]> --- diff --git a/source/scale_gcc.cc b/source/scale_gcc.cc index e9100f6..b2a289e 100644 --- a/source/scale_gcc.cc +++ b/source/scale_gcc.cc @@ -471,34 +471,30 @@ int dst_width) { (void)src_stride; asm volatile( - "movdqa %0,%%xmm3 \n" - "movdqa %1,%%xmm4 \n" - "movdqa %2,%%xmm5 \n" - : - : "m"(kShuf0), // %0 - "m"(kShuf1), // %1 - "m"(kShuf2) // %2 - ); - asm volatile( - "1: \n" - "movdqu (%0),%%xmm0 \n" - "movdqu 0x10(%0),%%xmm2 \n" - "lea 0x20(%0),%0 \n" + "movdqa %[kShuf0],%%xmm3 \n" + "movdqa %[kShuf1],%%xmm4 \n" + "movdqa %[kShuf2],%%xmm5 \n" + "1: \n" + "movdqu (%[src_ptr]),%%xmm0 \n" + "movdqu 0x10(%[src_ptr]),%%xmm2 \n" + "lea 0x20(%[src_ptr]),%[src_ptr] \n" "movdqa %%xmm2,%%xmm1 \n" "palignr $0x8,%%xmm0,%%xmm1 \n" "pshufb %%xmm3,%%xmm0 \n" "pshufb %%xmm4,%%xmm1 \n" "pshufb %%xmm5,%%xmm2 \n" - "movq %%xmm0,(%1) \n" - "movq %%xmm1,0x8(%1) \n" - "movq %%xmm2,0x10(%1) \n" - "lea 0x18(%1),%1 \n" - "sub $0x18,%2 \n" + "movq %%xmm0,(%[dst_ptr]) \n" + "movq %%xmm1,0x8(%[dst_ptr]) \n" + "movq %%xmm2,0x10(%[dst_ptr]) \n" + "lea 0x18(%[dst_ptr]),%[dst_ptr] \n" + "sub $0x18,%[dst_width] \n" "jg 1b \n" - : "+r"(src_ptr), // %0 - "+r"(dst_ptr), // %1 - "+r"(dst_width) // %2 - : + : [src_ptr] "+r"(src_ptr), + [dst_ptr] "+r"(dst_ptr), + [dst_width] "+r"(dst_width) + : [kShuf0] "m"(kShuf0), + [kShuf1] "m"(kShuf1), + [kShuf2] "m"(kShuf2) : "memory", "cc", "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5"); } @@ -507,61 +503,55 @@ uint8_t* dst_ptr, int dst_width) { asm volatile( - "movdqa %0,%%xmm2 \n" // kShuf01 - "movdqa %1,%%xmm3 \n" // kShuf11 - "movdqa %2,%%xmm4 \n" // kShuf21 - : - : "m"(kShuf01), // %0 - "m"(kShuf11), // %1 - "m"(kShuf21) // %2 - ); - asm volatile( - "movdqa %0,%%xmm5 \n" // kMadd01 - "movdqa %1,%%xmm0 \n" // kMadd11 - "movdqa %2,%%xmm1 \n" // kRound34 - : - : "m"(kMadd01), // %0 - "m"(kMadd11), // %1 - "m"(kRound34) // %2 - ); - asm volatile( - "1: \n" - "movdqu (%0),%%xmm6 \n" - "movdqu 0x00(%0,%3,1),%%xmm7 \n" + "movdqa %[kShuf01],%%xmm2 \n" + "movdqa %[kShuf11],%%xmm3 \n" + "movdqa %[kShuf21],%%xmm4 \n" + "movdqa %[kMadd01],%%xmm5 \n" + "movdqa %[kMadd11],%%xmm0 \n" + "movdqa %[kRound34],%%xmm1 \n" + "1: \n" + "movdqu (%[src_ptr]),%%xmm6 \n" + "movdqu 0x00(%[src_ptr],%[src_stride],1),%%xmm7 \n" "pavgb %%xmm7,%%xmm6 \n" "pshufb %%xmm2,%%xmm6 \n" "pmaddubsw %%xmm5,%%xmm6 \n" "paddsw %%xmm1,%%xmm6 \n" "psrlw $0x2,%%xmm6 \n" "packuswb %%xmm6,%%xmm6 \n" - "movq %%xmm6,(%1) \n" - "movdqu 0x8(%0),%%xmm6 \n" - "movdqu 0x8(%0,%3,1),%%xmm7 \n" + "movq %%xmm6,(%[dst_ptr]) \n" + "movdqu 0x8(%[src_ptr]),%%xmm6 \n" + "movdqu 0x8(%[src_ptr],%[src_stride],1),%%xmm7 \n" "pavgb %%xmm7,%%xmm6 \n" "pshufb %%xmm3,%%xmm6 \n" "pmaddubsw %%xmm0,%%xmm6 \n" "paddsw %%xmm1,%%xmm6 \n" "psrlw $0x2,%%xmm6 \n" "packuswb %%xmm6,%%xmm6 \n" - "movq %%xmm6,0x8(%1) \n" - "movdqu 0x10(%0),%%xmm6 \n" - "movdqu 0x10(%0,%3,1),%%xmm7 \n" - "lea 0x20(%0),%0 \n" + "movq %%xmm6,0x8(%[dst_ptr]) \n" + "movdqu 0x10(%[src_ptr]),%%xmm6 \n" + "movdqu 0x10(%[src_ptr],%[src_stride],1),%%xmm7 \n" + "lea 0x20(%[src_ptr]),%[src_ptr] \n" "pavgb %%xmm7,%%xmm6 \n" "pshufb %%xmm4,%%xmm6 \n" - "pmaddubsw %4,%%xmm6 \n" + "pmaddubsw %[kMadd21],%%xmm6 \n" "paddsw %%xmm1,%%xmm6 \n" "psrlw $0x2,%%xmm6 \n" "packuswb %%xmm6,%%xmm6 \n" - "movq %%xmm6,0x10(%1) \n" - "lea 0x18(%1),%1 \n" - "sub $0x18,%2 \n" + "movq %%xmm6,0x10(%[dst_ptr]) \n" + "lea 0x18(%[dst_ptr]),%[dst_ptr] \n" + "sub $0x18,%[dst_width] \n" "jg 1b \n" - : "+r"(src_ptr), // %0 - "+r"(dst_ptr), // %1 - "+r"(dst_width) // %2 - : "r"(src_stride), // %3 - "m"(kMadd21) // %4 + : [src_ptr] "+r"(src_ptr), + [dst_ptr] "+r"(dst_ptr), + [dst_width] "+r"(dst_width) + : [src_stride] "r"(src_stride), + [kShuf01] "m"(kShuf01), + [kShuf11] "m"(kShuf11), + [kShuf21] "m"(kShuf21), + [kMadd01] "m"(kMadd01), + [kMadd11] "m"(kMadd11), + [kRound34] "m"(kRound34), + [kMadd21] "m"(kMadd21) : "memory", "cc", "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7"); } @@ -571,28 +561,15 @@ uint8_t* dst_ptr, int dst_width) { asm volatile( - "movdqa %0,%%xmm2 \n" // kShuf01 - "movdqa %1,%%xmm3 \n" // kShuf11 - "movdqa %2,%%xmm4 \n" // kShuf21 - : - : "m"(kShuf01), // %0 - "m"(kShuf11), // %1 - "m"(kShuf21) // %2 - ); - asm volatile( - "movdqa %0,%%xmm5 \n" // kMadd01 - "movdqa %1,%%xmm0 \n" // kMadd11 - "movdqa %2,%%xmm1 \n" // kRound34 - : - : "m"(kMadd01), // %0 - "m"(kMadd11), // %1 - "m"(kRound34) // %2 - ); - - asm volatile( - "1: \n" - "movdqu (%0),%%xmm6 \n" - "movdqu 0x00(%0,%3,1),%%xmm7 \n" + "movdqa %[kShuf01],%%xmm2 \n" // kShuf01 + "movdqa %[kShuf11],%%xmm3 \n" // kShuf11 + "movdqa %[kShuf21],%%xmm4 \n" // kShuf21 + "movdqa %[kMadd01],%%xmm5 \n" // kMadd01 + "movdqa %[kMadd11],%%xmm0 \n" // kMadd11 + "movdqa %[kRound34],%%xmm1 \n" // kRound34 + "1: \n" + "movdqu (%[src_ptr]),%%xmm6 \n" + "movdqu 0x00(%[src_ptr],%[src_stride],1),%%xmm7 \n" "pavgb %%xmm6,%%xmm7 \n" "pavgb %%xmm7,%%xmm6 \n" "pshufb %%xmm2,%%xmm6 \n" @@ -600,9 +577,9 @@ "paddsw %%xmm1,%%xmm6 \n" "psrlw $0x2,%%xmm6 \n" "packuswb %%xmm6,%%xmm6 \n" - "movq %%xmm6,(%1) \n" - "movdqu 0x8(%0),%%xmm6 \n" - "movdqu 0x8(%0,%3,1),%%xmm7 \n" + "movq %%xmm6,(%[dst_ptr]) \n" + "movdqu 0x8(%[src_ptr]),%%xmm6 \n" + "movdqu 0x8(%[src_ptr],%[src_stride],1),%%xmm7 \n" "pavgb %%xmm6,%%xmm7 \n" "pavgb %%xmm7,%%xmm6 \n" "pshufb %%xmm3,%%xmm6 \n" @@ -610,26 +587,32 @@ "paddsw %%xmm1,%%xmm6 \n" "psrlw $0x2,%%xmm6 \n" "packuswb %%xmm6,%%xmm6 \n" - "movq %%xmm6,0x8(%1) \n" - "movdqu 0x10(%0),%%xmm6 \n" - "movdqu 0x10(%0,%3,1),%%xmm7 \n" - "lea 0x20(%0),%0 \n" + "movq %%xmm6,0x8(%[dst_ptr]) \n" + "movdqu 0x10(%[src_ptr]),%%xmm6 \n" + "movdqu 0x10(%[src_ptr],%[src_stride],1),%%xmm7 \n" + "lea 0x20(%[src_ptr]),%[src_ptr] \n" "pavgb %%xmm6,%%xmm7 \n" "pavgb %%xmm7,%%xmm6 \n" "pshufb %%xmm4,%%xmm6 \n" - "pmaddubsw %4,%%xmm6 \n" + "pmaddubsw %[kMadd21],%%xmm6 \n" "paddsw %%xmm1,%%xmm6 \n" "psrlw $0x2,%%xmm6 \n" "packuswb %%xmm6,%%xmm6 \n" - "movq %%xmm6,0x10(%1) \n" - "lea 0x18(%1),%1 \n" - "sub $0x18,%2 \n" + "movq %%xmm6,0x10(%[dst_ptr]) \n" + "lea 0x18(%[dst_ptr]),%[dst_ptr] \n" + "sub $0x18,%[dst_width] \n" "jg 1b \n" - : "+r"(src_ptr), // %0 - "+r"(dst_ptr), // %1 - "+r"(dst_width) // %2 - : "r"(src_stride), // %3 - "m"(kMadd21) // %4 + : [src_ptr] "+r"(src_ptr), + [dst_ptr] "+r"(dst_ptr), + [dst_width] "+r"(dst_width) + : [src_stride] "r"(src_stride), + [kShuf01] "m"(kShuf01), + [kShuf11] "m"(kShuf11), + [kShuf21] "m"(kShuf21), + [kMadd01] "m"(kMadd01), + [kMadd11] "m"(kMadd11), + [kRound34] "m"(kRound34), + [kMadd21] "m"(kMadd21) : "memory", "cc", "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7"); } @@ -670,21 +653,14 @@ uint8_t* dst_ptr, int dst_width) { asm volatile( - "movdqa %0,%%xmm2 \n" - "movdqa %1,%%xmm3 \n" - "movdqa %2,%%xmm4 \n" - "movdqa %3,%%xmm5 \n" - : - : "m"(kShufAb0), // %0 - "m"(kShufAb1), // %1 - "m"(kShufAb2), // %2 - "m"(kScaleAb2) // %3 - ); - asm volatile( - "1: \n" - "movdqu (%0),%%xmm0 \n" - "movdqu 0x00(%0,%3,1),%%xmm1 \n" - "lea 0x10(%0),%0 \n" + "movdqa %[kShufAb0],%%xmm2 \n" + "movdqa %[kShufAb1],%%xmm3 \n" + "movdqa %[kShufAb2],%%xmm4 \n" + "movdqa %[kScaleAb2],%%xmm5 \n" + "1: \n" + "movdqu (%[src_ptr]),%%xmm0 \n" + "movdqu 0x00(%[src_ptr],%[src_stride],1),%%xmm1 \n" + "lea 0x10(%[src_ptr]),%[src_ptr] \n" "pavgb %%xmm1,%%xmm0 \n" "movdqa %%xmm0,%%xmm1 \n" "pshufb %%xmm2,%%xmm1 \n" @@ -695,16 +671,20 @@ "paddusw %%xmm0,%%xmm1 \n" "pmulhuw %%xmm5,%%xmm1 \n" "packuswb %%xmm1,%%xmm1 \n" - "movd %%xmm1,(%1) \n" + "movd %%xmm1,(%[dst_ptr]) \n" "psrlq $0x10,%%xmm1 \n" - "movd %%xmm1,0x2(%1) \n" - "lea 0x6(%1),%1 \n" - "sub $0x6,%2 \n" + "movd %%xmm1,0x2(%[dst_ptr]) \n" + "lea 0x6(%[dst_ptr]),%[dst_ptr] \n" + "sub $0x6,%[dst_width] \n" "jg 1b \n" - : "+r"(src_ptr), // %0 - "+r"(dst_ptr), // %1 - "+r"(dst_width) // %2 - : "r"(src_stride) // %3 + : [src_ptr] "+r"(src_ptr), + [dst_ptr] "+r"(dst_ptr), + [dst_width] "+r"(dst_width) + : [src_stride] "r"(src_stride), + [kShufAb0] "m"(kShufAb0), + [kShufAb1] "m"(kShufAb1), + [kShufAb2] "m"(kShufAb2), + [kScaleAb2] "m"(kScaleAb2) : "memory", "cc", "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6"); } @@ -713,19 +693,13 @@ uint8_t* dst_ptr, int dst_width) { asm volatile( - "movdqa %0,%%xmm2 \n" - "movdqa %1,%%xmm3 \n" - "movdqa %2,%%xmm4 \n" + "movdqa %[kShufAc],%%xmm2 \n" + "movdqa %[kShufAc3],%%xmm3 \n" + "movdqa %[kScaleAc33],%%xmm4 \n" "pxor %%xmm5,%%xmm5 \n" - : - : "m"(kShufAc), // %0 - "m"(kShufAc3), // %1 - "m"(kScaleAc33) // %2 - ); - asm volatile( - "1: \n" - "movdqu (%0),%%xmm0 \n" - "movdqu 0x00(%0,%3,1),%%xmm6 \n" + "1: \n" + "movdqu (%[src_ptr]),%%xmm0 \n" + "movdqu 0x00(%[src_ptr],%[src_stride],1),%%xmm6 \n" "movhlps %%xmm0,%%xmm1 \n" "movhlps %%xmm6,%%xmm7 \n" "punpcklbw %%xmm5,%%xmm0 \n" @@ -734,8 +708,8 @@ "punpcklbw %%xmm5,%%xmm7 \n" "paddusw %%xmm6,%%xmm0 \n" "paddusw %%xmm7,%%xmm1 \n" - "movdqu 0x00(%0,%3,2),%%xmm6 \n" - "lea 0x10(%0),%0 \n" + "movdqu 0x00(%[src_ptr],%[src_stride],2),%%xmm6 \n" + "lea 0x10(%[src_ptr]),%[src_ptr] \n" "movhlps %%xmm6,%%xmm7 \n" "punpcklbw %%xmm5,%%xmm6 \n" "punpcklbw %%xmm5,%%xmm7 \n" @@ -756,16 +730,19 @@ "paddusw %%xmm7,%%xmm6 \n" "pmulhuw %%xmm4,%%xmm6 \n" "packuswb %%xmm6,%%xmm6 \n" - "movd %%xmm6,(%1) \n" + "movd %%xmm6,(%[dst_ptr]) \n" "psrlq $0x10,%%xmm6 \n" - "movd %%xmm6,0x2(%1) \n" - "lea 0x6(%1),%1 \n" - "sub $0x6,%2 \n" + "movd %%xmm6,0x2(%[dst_ptr]) \n" + "lea 0x6(%[dst_ptr]),%[dst_ptr] \n" + "sub $0x6,%[dst_width] \n" "jg 1b \n" - : "+r"(src_ptr), // %0 - "+r"(dst_ptr), // %1 - "+r"(dst_width) // %2 - : "r"(src_stride) // %3 + : [src_ptr] "+r"(src_ptr), + [dst_ptr] "+r"(dst_ptr), + [dst_width] "+r"(dst_width) + : [src_stride] "r"(src_stride), + [kShufAc] "m"(kShufAc), + [kShufAc3] "m"(kShufAc3), + [kScaleAc33] "m"(kScaleAc33) : "memory", "cc", "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7"); } @@ -2217,71 +2194,67 @@ int dx) { ... (file diff truncated)
Original Bug Report
Systemic missing clobbers in libyuv inline assembly lead to non-volatile register poisoning with controled value and memory corruption
Report description
Systemic missing clobbers in libyuv inline assembly lead to non-volatile register poisoning with controled value and memory corruption
Bug location
Where do you want to report your vulnerability?
Chrome VRP – Report security issues affecting the Chrome browser. See program rules
Which URL (or repository) have you found the vulnerability in?
https://chromium.googlesource.com/libyuv/libyuv
The problem
Please describe the technical details of the vulnerability
Root Cause
The libyuv library (vendored at //third_party/libyuv/source/row_gcc.cc) contains manually written GCC inline assembly (asm volatile) for SIMD color conversions. Multiple functions fail to declare modified SIMD registers in their clobber lists.
Mechanism
In functions such as ARGBToUV444MatrixRow_AVX2 and MergeXR30Row_AVX2, the ymm6 register is actively used for intermediate mathematical operations (e.g., vpmaddubsw and vpcmpeqb). However, the clobber lists omit ymm6/xmm6, explicitly listing only xmm0 through xmm5 and xmm7.
Because the compiler is unaware that ymm6 is modified, it does not generate the necessary prologue and epilogue instructions to preserve the register’s previous state.
ABI Violation and Memory Transition
Under the Windows x64 ABI, xmm6 through xmm15 are non-volatile (callee-saved). When JavaScript triggers these color conversions via the VideoEncoder API, the attacker’s mathematically transformed pixel data is left resident in xmm6. During thread suspension or teardown (specifically in base::internal::WorkerThread::RunWorker), the thread’s context is saved. The CPU executes movaps xmmword ptr [rsp+70h], xmm6, spilling the unmitigated, attacker-controlled pixel data directly into the active stack frame.
Systemic Issue and Exploitability Scope
An audit of row_gcc.cc reveals that missing clobbers are a systemic issue across the library, likely resulting from copy-pasting inline assembly macros without updating the clobber lists for newly added registers. However, while many functions omit clobbers, only the functions omitting non-volatile registers (xmm6) have been deemed exploitable for cross-boundary state poisoning. The omission of volatile registers (xmm0-xmm5) generally only results in localized, unexploitable intra-function compiler misoptimizations.
| Function | Omitted Clobber | Register Type (Win x64) | Exploitability |
|---|---|---|---|
ARGBToUV444MatrixRow_AVX2 |
ymm6 |
Non-Volatile | Exploitable (State Poisoning) |
MergeXR30Row_AVX2 |
ymm6 |
Non-Volatile | Unexploitable (State Poisoning, but haven’t found an angle to get the function triggered through js) |
ARGBToAB64Row_AVX2 |
ymm3 |
Volatile | Unexploitable (Internal logic bug) |
InterpolateRow_AVX2 |
ymm3 |
Volatile | Unexploitable (Internal logic bug) |
ARGB4444ToARGBRow_SSE2 |
xmm5 |
Volatile | Unexploitable (Internal logic bug) |
SobelToPlaneRow_SSE2 |
xmm5 |
Volatile | Unexploitable (Internal logic bug) |
Impact analysis
Impact Assessment
Vulnerability Class: Memory Corruption (Restricted Stack Write) via State Poisoning.
Prerequisites: Execution of standard, unprivileged JavaScript using the WebCodecs VideoEncoder API and a 2D Canvas.
Exploitation Primitive
By supplying a precisely crafted, tiled image payload to the encoder, an attacker gains a deterministic, controlled 128-bit stack write. The hardware SIMD math (vpmaddubsw) dictates a direct relationship between the input RGB pixel values and the written bytes. Due to mathematical constraints in the pixel conversion, the payload is limited to approximately 3.6 million unique 32-bit values that repeat across the 64-bit boundary (e.g., 0xYYYYXXXXYYYYXXXX).
Security Consequence The attacker can surgically partition the 128-bit write:
- Lower 64-bits: Calculated to equal
0x0000000000000000to silently bypass intra-thread stability checks (there is a check that gets triggered in runworker when not having the lower bits set to 0). - Upper 64-bits: Calculated to represent a controlled, restricted 64-bit payload.
When subsequent functions on the WorkerThread reuse the poisoned stack memory ([rsp+70h]), the attacker’s restricted payload is consumed as legitimate program state. While the constrained value prevents forging direct pointers to existing module bases, due to the wide codebase that can touch this poisoned state, the chances of further corruption or impact are deemed likely.
The cause
What version of Chrome have you found the security issue in?
[145.0.7632.119] + [stable]
Is the security issue related to a crash?
Yes, it is related to a crash.
Choose the type of vulnerability
Memory Corruption (in a sandboxed process)
How would you like to be publicly acknowledged for your report?
Casper Woudenberg