Overview

High
Severity
8.8
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactMemory corruption in Codecs
DescriptionMemory corruption in Codecs
ComponentCodecs
Sub-componentInternals>Images>Codecs
Bug ClassMemory Corruption
Action tierwatch — corruption primitive but gated (uncommon) — not default-reachable
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker489489002
Fix commit5689f16923f5 (libyuv/libyuv) +145/-172
CWECWE-119 (Buffer bounds error)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CISA KEVNot listed
CreditedCasper Woudenberg
Reported2026-03-03 (per vendor advisory)
Fix landed2026-08-25 (public git)
Disclosed2026-09-08
Patch gap14 days — fix public in source before the advisory

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.

Key insight
The core mistake was relying on 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

  1. 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.
  2. Trigger split-block execution The scaler runs the constant-loader asm block and then the pixel-loop asm block as two independent statements.
  3. 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.
  4. Corrupt shuffle/write pshufb with 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

An attacker gains memory corruption in the media/codec processing path where 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 carry
    Flag any function that loads values in one asm volatile block and consumes them in a later block, since register/flag state is not guaranteed to persist between disjoint statements.
  • Missing clobber/operand declarations
    Audit 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 operands
    Prefer 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.
From 5689f16923f516ba6eb5b5a3ee11dc90065a3641 Mon Sep 17 00:00:00 2001
From: Frank Barchard <fbarchard@google.com>
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 <wtc@google.com>
Commit-Queue: Frank Barchard <fbarchard@google.com>
---
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)
Loading diff…

Original Bug Report

reported by ca...@gmail.com

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:

  1. Lower 64-bits: Calculated to equal 0x0000000000000000 to silently bypass intra-thread stability checks (there is a check that gets triggered in runworker when not having the lower bits set to 0).
  2. 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]

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

View on issue tracker