CVE-2026-17744
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifui/gtk/select_file_dialog_linux_gtk.cc |
modified |
Files Changed
ui/gtk/BUILD.gnui/gtk/DEPSui/gtk/gdk_pixbuf.sigsui/gtk/select_file_dialog_linux_gtk.cc
Patch
From ff0ccedb7abd3abf4af2f7bd16b87dabec120b82 Mon Sep 17 00:00:00 2001
From: Tom Anderson <thomasanderson@chromium.org>
Date: Tue, 23 Jun 2026 16:08:38 -0700
Subject: [PATCH] [GTK] Migrate file chooser image preview loading to async Data Decoder
SelectFileDialogLinuxGtk currently calls
gdk_pixbuf_new_from_file_at_size() which parses untrusted files
in the unsandboxed browser process. Instead of removing the GTK3 image
preview entirely, this CL migrates image previews to be fully
asynchronous and sandboxed.
This implementation:
1. Performs raw file I/O on background threads using ThreadPool.
2. Safely parses and downscales untrusted image data out-of-process via
the sandboxed data_decoder service (DecodeImageIsolated).
Offloading the downscaling to the sandboxed process completely
avoids UI thread CPU jank (e.g. from Lanczos scaling large images)
and dramatically reduces Mojo/IPC transfer size.
3. Automatically supports all web-compatible formats (WebP, GIF, AVIF,
BMP, PNG, JPEG, etc.) via Blink's image decoders.
4. Prevents UI thread jank by decoupling file reading and decoding
from the GTK main loop.
5. Tracks preview widget and target file paths on the DialogState map,
resolving concurrency race conditions when multiple file chooser
dialogs are open.
6. Employs WeakPtr and dialog existence validation to safely discard
out-of-date preview loads.
Fixed: 500137309
Change-Id: Ibd3a9cae65c42b482cd435443baf11998a663cf0
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7980558
Reviewed-by: Lei Zhang <thestig@chromium.org>
Commit-Queue: Thomas Anderson <thomasanderson@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1651353}
---
diff --git a/ui/gtk/BUILD.gn b/ui/gtk/BUILD.gn
index 6d2693a6..e7ff4e43 100644
--- a/ui/gtk/BUILD.gn
+++ b/ui/gtk/BUILD.gn
@@ -123,6 +123,7 @@
"//base",
"//cc/paint",
"//printing/buildflags",
+ "//services/data_decoder/public/cpp",
"//skia",
# GTK pulls pangoft2, which requires HarfBuzz symbols. When linking
diff --git a/ui/gtk/DEPS b/ui/gtk/DEPS
index 33cfaf3..be9ceea 100644
--- a/ui/gtk/DEPS
+++ b/ui/gtk/DEPS
@@ -2,6 +2,8 @@
"+cc/paint",
"+chrome/browser/themes/theme_properties.h",
"+printing",
+ "+services/data_decoder/public",
+ "+skia/ext",
"+third_party/skia",
"+ui/aura",
"+ui/base",
diff --git a/ui/gtk/gdk_pixbuf.sigs b/ui/gtk/gdk_pixbuf.sigs
index 35caa0f..89e28a1 100644
--- a/ui/gtk/gdk_pixbuf.sigs
+++ b/ui/gtk/gdk_pixbuf.sigs
@@ -1,3 +1,5 @@
-GdkPixbuf* gdk_pixbuf_new_from_file_at_size(const char* filename, int width, int height, GError** error);
+GdkPixbuf* gdk_pixbuf_new(GdkColorspace colorspace, gboolean has_alpha, int bits_per_sample, int width, int height);
+guchar* gdk_pixbuf_get_pixels(const GdkPixbuf* pixbuf);
+int gdk_pixbuf_get_rowstride(const GdkPixbuf* pixbuf);
int gdk_pixbuf_get_width(const GdkPixbuf* pixbuf);
int gdk_pixbuf_get_height(const GdkPixbuf* pixbuf);
diff --git a/ui/gtk/select_file_dialog_linux_gtk.cc b/ui/gtk/select_file_dialog_linux_gtk.cc
index 9e3390f..c354a04 100644
--- a/ui/gtk/select_file_dialog_linux_gtk.cc
+++ b/ui/gtk/select_file_dialog_linux_gtk.cc
@@ -10,6 +10,7 @@
#include <unistd.h>
#include <algorithm>
+#include <cmath>
#include <cstddef>
#include <memory>
#include <set>
@@ -17,6 +18,8 @@
#include <vector>
#include "base/byte_size.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/no_destructor.h"
@@ -24,8 +27,12 @@
#include "base/strings/string_util.h"
#include "base/strings/sys_string_conversions.h"
#include "base/strings/utf_string_conversions.h"
+#include "base/task/thread_pool.h"
#include "base/threading/thread.h"
#include "base/threading/thread_restrictions.h"
+#include "services/data_decoder/public/cpp/decode_image.h"
+#include "skia/ext/image_operations.h"
+#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/aura/window_observer.h"
#include "ui/base/glib/scoped_gobject.h"
#include "ui/base/l10n/l10n_util.h"
@@ -160,6 +167,34 @@
return filenames_fp;
}
+ScopedGObject<GdkPixbuf> ConvertSkBitmapToGdkPixbuf(const SkBitmap& bitmap) {
+ if (bitmap.isNull() || bitmap.empty()) {
+ return {};
+ }
+
+ int width = bitmap.width();
+ int height = bitmap.height();
+
+ // Create a new GdkPixbuf. GDK_COLORSPACE_RGB is standard and only colorspace.
+ // Use has_alpha = TRUE and bits_per_sample = 8.
+ auto pixbuf =
+ TakeGObject(gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, width, height));
+ if (!pixbuf) {
+ return {};
+ }
+
+ guchar* gdk_pixels = gdk_pixbuf_get_pixels(pixbuf.get());
+ int gdk_rowstride = gdk_pixbuf_get_rowstride(pixbuf.get());
+
+ SkImageInfo dst_info = SkImageInfo::Make(
+ width, height, kRGBA_8888_SkColorType, kUnpremul_SkAlphaType);
+ if (!bitmap.readPixels(dst_info, gdk_pixels, gdk_rowstride, 0, 0)) {
+ return {};
+ }
+
+ return pixbuf;
+}
+
} // namespace
// The size of the preview we display for selected image files. We set height
@@ -310,17 +345,20 @@
connect("destroy", &SelectFileDialogLinuxGtk::OnFileChooserDestroy);
+ GtkWidget* preview = nullptr;
if (!GtkCheckVersion(4)) {
- preview_ = gtk_image_new();
+ preview = gtk_image_new();
connect("update-preview", &SelectFileDialogLinuxGtk::OnUpdatePreview);
- gtk_file_chooser_set_preview_widget(GTK_FILE_CHOOSER(dialog), preview_);
+ gtk_file_chooser_set_preview_widget(GTK_FILE_CHOOSER(dialog), preview);
}
base::OnceClosure reenable_input_events =
DisableHostInputHandling(dialog, owning_window);
- dialogs_[dialog] = DialogState(std::move(signals), owning_window,
- std::move(reenable_input_events));
+ DialogState state(std::move(signals), owning_window,
+ std::move(reenable_input_events));
+ state.preview_widget = preview;
+ dialogs_[dialog] = std::move(state);
if (!GtkCheckVersion(4))
gtk_widget_show_all(dialog);
@@ -645,9 +683,16 @@
void SelectFileDialogLinuxGtk::OnUpdatePreview(GtkWidget* chooser) {
DCHECK(!GtkCheckVersion(4));
+ auto it = dialogs_.find(chooser);
+ if (it == dialogs_.end()) {
+ return;
+ }
+ auto& state = it->second;
+
gchar* filename =
gtk_file_chooser_get_preview_filename(GTK_FILE_CHOOSER(chooser));
if (!filename) {
+ state.preview_file_path.clear();
gtk_file_chooser_set_preview_widget_active(GTK_FILE_CHOOSER(chooser),
FALSE);
return;
@@ -662,20 +707,113 @@
if (stat(filename, &stat_buf) != 0 || !S_ISREG(stat_buf.st_mode) ||
static_cast<uint64_t>(stat_buf.st_size) > kMaxPreviewFileSize.InBytes()) {
g_free(filename);
+ state.preview_file_path.clear();
gtk_file_chooser_set_preview_widget_active(GTK_FILE_CHOOSER(chooser),
FALSE);
return;
}
- // This will preserve the image's aspect ratio.
- GdkPixbuf* pixbuf = gdk_pixbuf_new_from_file_at_size(filename, kPreviewWidth,
- kPreviewHeight, nullptr);
+ base::FilePath file_path(filename);
g_free(filename);
- if (pixbuf) {
- gtk_image_set_from_pixbuf(GTK_IMAGE(preview_.get()), pixbuf);
Original Bug Report
Sandbox escape via gdk-pixbuf image parsing in GTK3 file chooser
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 security team.
Overview: Chromium’s Linux GTK3 file chooser explicitly enables image previews, parsing selected files directly in the unsandboxed browser process using the gdk-pixbuf C library. This violates the Rule of 2 by handling untrusted input with a memory-unsafe library in a privileged process. A malicious webpage could exploit this by downloading a crafted image and prompting the user to select it, potentially leading to a sandbox escape and Remote Code Execution.
Affected files:
ui/gtk/select_file_dialog_linux_gtk.ccui/gtk/gtk_compat.cc
Estimated timestamp from git blame: 2023-09-29
Description
There is a potential sandbox escape vulnerability in Chromium’s GTK3 file chooser implementation on Linux. In ui/gtk/select_file_dialog_linux_gtk.cc, the SelectFileDialogLinuxGtk::OnUpdatePreview callback explicitly generates image previews by calling the gdk-pixbuf C library directly within the unsandboxed browser process.
Specifically, when a user highlights a file, the code calls gdk_pixbuf_new_from_file_at_size(). The gdk-pixbuf library is a complex, memory-unsafe image parser with a history of heap corruption vulnerabilities. Processing untrusted, potentially attacker-controlled files with it in the browser process is a direct violation of Chromium’s “Rule of 2” security architecture.
Potential Attack Scenario
Although we do not currently have a working proof of concept that has been successfully run, an attacker could potentially trigger this vulnerability through the following steps:
- Payload Delivery: A malicious webpage triggers a drive-by download of a crafted image payload (e.g., a malformed
.anior.giffile designed to exploit agdk-pixbufparser bug) into the user’s default~/Downloadsdirectory. - Trigger Dialog: The webpage calls a file selection API such as
window.showOpenFilePicker({startIn: 'downloads'})following a user gesture. - User Interaction: On Linux systems using the GTK3 fallback dialog, the file chooser opens directly in the downloads folder. The user highlights the malicious file (e.g., by single-clicking it or using arrow keys).
- Unsandboxed Parsing: The GTK
update-previewsignal fires, causing the browser process to executegdk_pixbuf_new_from_file_at_size()on the payload. - Exploitation: The parsing triggers a memory corruption vulnerability within
gdk-pixbufor itsgliballocator, leading to Remote Code Execution (RCE) and a full sandbox escape. MiraclePtr provides no mitigation here, as the allocations occur entirely outside of Chromium’s PartitionAlloc.
Suggested Fix
To comply with the Rule of 2, remove the custom preview widget implementation from the GTK3 fallback dialog entirely (this is already the default behavior for GTK4).
If image previews are strictly required for product reasons, the file path should be sent to the sandboxed DataDecoder utility process. The utility process can safely parse the untrusted image and return a decoded bitmap to the browser process over IPC.
Evaluated with Chrome root at commit: f200f57a19490707ff8bc7aa5de3cbc443a3afad
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.