CVE-2026-10960
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifmedia/gpu/vaapi/vaapi_wrapper.cc |
modified |
Files Changed
media/gpu/vaapi/vaapi_wrapper.cctools/metrics/histograms/metadata/media/enums.xml
Patch
From b6d5873d6546bd8a4fbaf2ac7dc416ecd959f471 Mon Sep 17 00:00:00 2001 From: Ted Meyer <[email protected]> Date: Thu, 30 Apr 2026 18:23:14 -0700 Subject: [PATCH] Initialize VAImage and improve error handling Make sure we return an error when vaCreateImage or vaDeriveImage fails, since it means we can't actually upload anything. Bug: 507258786 Change-Id: Ifff8fc3cdc8e4d29d8330976fea384b8570baf97 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7804995 Reviewed-by: Eugene Zemtsov <[email protected]> Reviewed-by: Evan Liu <[email protected]> Commit-Queue: Ted (Chromium) Meyer <[email protected]> Cr-Commit-Position: refs/heads/main@{#1623631} --- diff --git a/media/gpu/vaapi/vaapi_wrapper.cc b/media/gpu/vaapi/vaapi_wrapper.cc index 26572a5..60d29946 100644 --- a/media/gpu/vaapi/vaapi_wrapper.cc +++ b/media/gpu/vaapi/vaapi_wrapper.cc @@ -132,7 +132,7 @@ // These values are logged to UMA. Entries should not be renumbered and numeric // values should never be reused. Please keep in sync with -// "VaapiFunctions" in src/tools/metrics/histograms/enums.xml. +// "VaapiFunctions" in src/tools/metrics/histograms/metadata/media/enums.xml. enum class VaapiFunctions { kVABeginPicture = 0, kVACreateBuffer = 1, @@ -166,9 +166,11 @@ kVADetachProtectedSession = 28, kVAProtectedSessionHwUpdate_Deprecated = 29, kVAProtectedSessionExecute = 30, - // Anything else is captured in this last entry. + // Anything else is captured in this entry. It used to be last, but more + // library calls have since been added, and we can't change the number. kOtherVAFunction = 31, - kMaxValue = kOtherVAFunction, + kVADeriveImage = 32, + kMaxValue = kVADeriveImage, }; void ReportVaapiErrorToUMA(const std::string& histogram_name, @@ -210,7 +212,8 @@ "vaDetachProtectedSession", "vaProtectedSessionHwUpdate (Deprecated)", "vaProtectedSessionExecute", - "Other VA function"}; + "Other VA function", + "vaDeriveImage"}; // Translates |function| into a human readable string for logging. const char* VaapiFunctionName(VaapiFunctions function) { @@ -2880,7 +2883,7 @@ const gfx::Size visible_size = frame.visible_rect().size(); bool needs_va_put_image = false; - VAImage image; + VAImage image = {}; VAStatus va_res = vaDeriveImage(va_display_, va_surface_id, &image); if (va_res == VA_STATUS_ERROR_OPERATION_FAILED) { DVLOG(4) << "vaDeriveImage failed and fallback to Create_PutImage"; @@ -2893,6 +2896,8 @@ va_surface_size.height(), &image); VA_SUCCESS_OR_RETURN(va_res, VaapiFunctions::kVACreateImage, false); needs_va_put_image = true; + } else { + VA_SUCCESS_OR_RETURN(va_res, VaapiFunctions::kVADeriveImage, false); } absl::Cleanup vaimage_deleter = [this, &image]() EXCLUSIVE_LOCKS_REQUIRED(va_lock_.get()) { diff --git a/tools/metrics/histograms/metadata/media/enums.xml b/tools/metrics/histograms/metadata/media/enums.xml index b891906..cc7d1cbd 100644 --- a/tools/metrics/histograms/metadata/media/enums.xml +++ b/tools/metrics/histograms/metadata/media/enums.xml @@ -2365,6 +2365,7 @@ <int value="29" label="vaProtectedSessionHwUpdate() (deprecated)"/> <int value="30" label="kVAProtectedSessionExecute()"/> <int value="31" label="Other VA functions"/> + <int value="32" label="vaDeriveImage()"/> </enum> <enum name="VideoCaptureApi">
Original Bug Report
Potential cross-context UAF/OOB write in GPU process via uninitialized VAImage
Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports without the Chrome Security team. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.
Overview: In VaapiWrapper::UploadVideoFrameToSurface, a VAImage struct is allocated on the stack without zero-initialization. If vaDeriveImage fails with an unexpected error code, the code proceeds to use the uninitialized struct. This could potentially allow a compromised renderer to achieve a cross-context Use-After-Free or Out-of-Bounds write in the GPU process by grooming the stack.
Affected files:
media/gpu/vaapi/vaapi_wrapper.cc
Estimated timestamp from git blame: 2019-04-03
Root Cause
In media/gpu/vaapi/vaapi_wrapper.cc, the function VaapiWrapper::UploadVideoFrameToSurface allocates a VAImage struct on the stack without initializing it:
// media/gpu/vaapi/vaapi_wrapper.cc:2849
VAImage image;
VAStatus va_res = vaDeriveImage(va_display_, va_surface_id, &image);
if (va_res == VA_STATUS_ERROR_OPERATION_FAILED) {
// Fallback path that initializes the image
...
}
The libva frontend implementation of vaDeriveImage does not initialize the output struct if it returns an error. The error handling in UploadVideoFrameToSurface only checks for VA_STATUS_ERROR_OPERATION_FAILED. If vaDeriveImage returns any other error code (e.g., VA_STATUS_ERROR_ALLOCATION_FAILED), the fallback path is skipped, and the image struct remains filled with stack residue.
Impact
1. Cross-Context Use-After-Free (UAF)
An absl::Cleanup block captures the uninitialized image reference:
// media/gpu/vaapi/vaapi_wrapper.cc:2863
absl::Cleanup vaimage_deleter = [this, &image]() {
DestroyVAImage(va_display_, image);
};
DestroyVAImage checks if image.image_id != VA_INVALID_ID and calls vaDestroyImage. If an attacker can control the stack residue, they can supply a valid image_id that is currently actively used by another context within the shared VADisplay in the GPU process. This prematurely destroys the image, leading to a UAF when the rightful owner accesses it.
2. Cross-Context / Out-of-Bounds (OOB) Write
If the stack residue coincidentally (or intentionally) bypasses subsequent validation checks (e.g., image.format.fourcc == VA_FOURCC_NV12 and dimension checks), the code maps the buffer specified by the uninitialized image.buf:
// media/gpu/vaapi/vaapi_wrapper.cc:2885
auto mapping = ScopedVABufferMapping::Create(..., va_display_, image.buf);
...
ret = libyuv::I420ToNV12(..., UNSAFE_TODO(image_ptr + image.offsets[0]), image.pitches[0], ...);
If image.buf points to a buffer owned by another context, and offsets/pitches are derived from stack garbage, this allows copying attacker-controlled video frame data to an attacker-controlled offset within that buffer, resulting in a cross-context OOB write.
Potential Exploit Steps
Note: These are potential steps; our tooling has not executed a proof-of-concept.
- A compromised Renderer process connects to the GPU process via the
media::mojom::VideoEncodeAcceleratorinterface. - The attacker calls
InitializewithkShmemstorage type. - The attacker issues highly deterministic IPC calls that execute on the GPU process’s dedicated encoder thread to groom the stack memory. The goal is to place specific values (e.g., a target
image_id,VA_FOURCC_NV12, and a targetbufID) at the exact offset where theVAImagestruct will be allocated. - The attacker intentionally exhausts driver resources or provides invalid parameters to ensure
vaDeriveImagefails with an error likeVA_STATUS_ERROR_ALLOCATION_FAILED. - The attacker calls
Encode()with a shared memory video frame, triggeringUploadVideoFrameToSurface. The uninitialized struct causes the OOB write and subsequent cross-context UAF upon function exit.
Proposed Fix
- Zero-initialize the struct upon declaration:
VAImage image = {}; - Safely handle unexpected failure modes from
vaDeriveImage. Ifva_resis neitherVA_STATUS_SUCCESSnorVA_STATUS_ERROR_OPERATION_FAILED, the function should log the error and returnfalseearly, avoiding the use of theimagestruct.
Evaluated with Chrome root at commit: a1e33f5848218e21d4a16ae2c1bc94e815c30c7f
Results so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.