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 <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
- );
-