Firefox · Graphics
CVE-2026-100760
Logic Error in Graphics
Overview
High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifmozglue/misc/WindowsUserHandleValidation.h |
modified | |
CompositorWindowThreadwidget/windows/WinCompositorWindowThread.cpp |
modified |
Files Changed
gfx/layers/ipc/CompositorThread.cppgfx/webrender_bindings/RenderThread.cppmodules/libpref/init/StaticPrefList.yamlmozglue/misc/WindowsUserHandleValidation.hmozglue/misc/moz.buildsecurity/sandbox/win/src/sandboxbroker/sandboxBroker.cppwidget/windows/WinCompositorWindowThread.cpp
Patch
diff --git a/gfx/layers/ipc/CompositorThread.cpp b/gfx/layers/ipc/CompositorThread.cpp
index caa63113121..6501b51b820 100644
--- a/gfx/layers/ipc/CompositorThread.cpp
+++ b/gfx/layers/ipc/CompositorThread.cpp
@@ -16,6 +16,9 @@
#include "mozilla/media/MediaSystemResourceService.h"
#include "nsThread.h"
#include "nsThreadUtils.h"
+#ifdef XP_WIN
+# include "mozilla/WindowsUserHandleValidation.h"
+#endif
namespace mozilla {
namespace layers {
@@ -94,6 +97,9 @@ CompositorThreadHolder::CreateCompositorThread() {
2048);
nsCOMPtr<nsIThread> thread = NS_GetCurrentThread();
static_cast<nsThread*>(thread.get())->SetUseHangMonitor(true);
+#ifdef XP_WIN
+ mozilla::ForceToGuiThreadAndFixTebValidateHandlesFlag();
+#endif
}),
{.stackSize = stackSize});
diff --git a/gfx/webrender_bindings/RenderThread.cpp b/gfx/webrender_bindings/RenderThread.cpp
index f2614ca13da..ffc29f2c327 100644
--- a/gfx/webrender_bindings/RenderThread.cpp
+++ b/gfx/webrender_bindings/RenderThread.cpp
@@ -37,6 +37,7 @@
#ifdef XP_WIN
# include "GLContextEGL.h"
# include "GLLibraryEGL.h"
+# include "mozilla/WindowsUserHandleValidation.h"
# include "mozilla/gfx/DeviceManagerDx.h"
# include "mozilla/webrender/DCLayerTree.h"
# include "mozilla/widget/WinCompositorWindowThread.h"
@@ -197,6 +198,9 @@ void RenderThread::Start(uint32_t aNamespace) {
nsThread* nsthread = static_cast<nsThread*>(thread.get());
nsthread->SetUseHangMonitor(true);
nsthread->SetPriority(nsISupportsPriority::PRIORITY_HIGH);
+#ifdef XP_WIN
+ mozilla::ForceToGuiThreadAndFixTebValidateHandlesFlag();
+#endif
}),
{.stackSize = stackSize});
diff --git a/modules/libpref/init/StaticPrefList.yaml b/modules/libpref/init/StaticPrefList.yaml
index 7dc5cdd1912..17a37337fc3 100644
--- a/modules/libpref/init/StaticPrefList.yaml
+++ b/modules/libpref/init/StaticPrefList.yaml
@@ -18635,6 +18635,12 @@
value: true
mirror: always
+# If enabled, excepts all JOB_OBJECT_UILIMIT_* flags from the GPU process's job.
+- name: security.sandbox.gpu.disable-job-uilimits
+ type: RelaxedAtomicBool
+ value: false
+ mirror: always
+
- name: security.sandbox.gmp.shadow-stack.enabled
type: RelaxedAtomicBool
value: true
diff --git a/mozglue/misc/WindowsUserHandleValidation.h b/mozglue/misc/WindowsUserHandleValidation.h
new file mode 100644
index 00000000000..9ca421b15f5
--- /dev/null
+++ b/mozglue/misc/WindowsUserHandleValidation.h
@@ -0,0 +1,99 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MOZGLUE_MISC_WINDOWSUSERHANDLEVALIDATION_H_
+#define MOZGLUE_MISC_WINDOWSUSERHANDLEVALIDATION_H_
+
+#include <cstdint>
+
+#include <windows.h>
+#include <winternl.h>
+
+#include "mozilla/Assertions.h"
+
+namespace mozilla {
+
+// Bit in Win32ClientInfo user side flags that gates whether user32 APIs like
+// GetClientRect/ValidateHwnd/IsWindow route through the kernel's full
+// handle-validation path (NtUserValidateHandleSecure).
+static const uint32_t kValidateHandlesFlag = 0x20000000;
+
+inline uint32_t& GetWin32UserFlagsFromTeb() {
+ static const size_t kX64AndWowWin32ClientInfoOffsetInBytes = 0x800;
+ static const size_t kX64AndWowUserFlagsOffsetInBytes = 0x1c;
+
+ auto* teb = reinterpret_cast<uint8_t*>(NtCurrentTeb());
+ uint8_t* baseForOffset = teb;
+ size_t win32kClientInfoUserFlagsOffset;
+#if defined(_WIN64)
+ win32kClientInfoUserFlagsOffset =
+ kX64AndWowWin32ClientInfoOffsetInBytes + kX64AndWowUserFlagsOffsetInBytes;
+#else
+ static const size_t kNativeX86Win32ClientInfoOffsetInBytes = 0x6cc;
+ static const size_t kNativeX86UserFlagsOffsetInBytes = 0x14;
+
+ BOOL isWow64Process;
+ if (!::IsWow64Process(::GetCurrentProcess(), &isWow64Process)) {
+ MOZ_CRASH("IsWow64Process failed");
+ }
+
+ if (isWow64Process) {
+ // On win10+, x86 WOW user32 reads a signed delta at TEB+0xfdc and, if
+ // negative, adds it to the base TEB pointer before applying the offset.
+ int32_t delta = *reinterpret_cast<int32_t*>(teb + 0xfdc);
+ if (delta < 0) {
+ baseForOffset += delta;
+ }
+
+ win32kClientInfoUserFlagsOffset = kX64AndWowWin32ClientInfoOffsetInBytes +
+ kX64AndWowUserFlagsOffsetInBytes;
+ } else {
+ win32kClientInfoUserFlagsOffset = kNativeX86Win32ClientInfoOffsetInBytes +
+ kNativeX86UserFlagsOffsetInBytes;
+ }
+#endif
+
+ return *reinterpret_cast<uint32_t*>(baseForOffset +
+ win32kClientInfoUserFlagsOffset);
+}
+
+inline bool JobUILimitsRequireTebFlagClear() {
+ // Store result as job is not changed during process lifetime.
+ static const bool sClearRequired = [] {
+ JOBOBJECT_BASIC_UI_RESTRICTIONS uiRestrictionsInfo{};
+ if (!::QueryInformationJobObject(nullptr, JobObjectBasicUIRestrictions,
+ &uiRestrictionsInfo,
+ sizeof(uiRestrictionsInfo), nullptr)) {
+ // Not in a job or query failed;
+ return false;
+ }
+
+ // On older Windows the flag gets set incorrectly if any UI restrictions are
+ // set and it should only be set if JOB_OBJECT_UILIMIT_HANDLES is set.
+ // So, the flag needs clearing if there are restrictions and
+ // JOB_OBJECT_UILIMIT_HANDLES is not set.
+ return uiRestrictionsInfo.UIRestrictionsClass &&
+ !(uiRestrictionsInfo.UIRestrictionsClass &
+ JOB_OBJECT_UILIMIT_HANDLES);
+ }();
+ return sClearRequired;
+}
+
+inline void ForceToGuiThreadAndFixTebValidateHandlesFlag() {
+ if (!JobUILimitsRequireTebFlagClear()) {
+ return;
+ }
+
+ BOOL isGuiThread = ::IsGUIThread(/* bConvertToGuiThread */ TRUE);
+ if (!isGuiThread) {
+ MOZ_CRASH("Failed to convert to GUI Thread.");
+ }
+
+ auto& userFlags = GetWin32UserFlagsFromTeb();
+ userFlags &= ~kValidateHandlesFlag;
+}
+
+} // namespace mozilla
+
+#endif // MOZGLUE_MISC_WINDOWSUSERHANDLEVALIDATION_H_
diff --git a/mozglue/misc/moz.build b/mozglue/misc/moz.build
index b92ab85f866..fd16921f3eb 100644
--- a/mozglue/misc/moz.build
+++ b/mozglue/misc/moz.build
@@ -84,6 +84,7 @@ if CONFIG["OS_ARCH"] == "WINNT":
"WindowsProcessMitigations.h",
"WindowsStackCookie.h",
"WindowsUnwindInfo.h",
+ "WindowsUserHandleValidation.h",
]
EXPORTS.mozilla.glue += [
"WindowsUnicode.h",
diff --git a/security/sandbox/win/src/sandboxbroker/sandboxBroker.cpp b/security/sandbox/win/src/sandboxbroker/sandboxBroker.cpp
index eff029e9a63..74c0e5486b8 100644
--- a/security/sandbox/win/src/sandboxbroker/sandboxBroker.cpp
+++ b/security/sandbox/win/src/sandboxbroker/sandboxBroker.cpp
@@ -1344,11 +1344,15 @@ void SandboxBroker::SetSecurityLevelForGPUProcess(int32_t aSandboxLevel) {
sandbox::IntegrityLevel initialIntegrityLevel = sandbox::INTEGRITY_LEVEL_LOW;
sandbox::IntegrityLevel delayedIntegrityLevel = sandbox::INTEGRITY_LEVEL_LOW;
- sandbox::JobLevel jobLevel = sandbox::JobLevel::kLimitedUser;
+ sandbox::JobLevel jobLevel = sandbox::JobLevel::kLockdown;
uint32_t uiExceptions =
JOB_OBJECT_UILIMIT_SYSTEMPARAMETERS | JOB_OBJECT_UILIMIT_DESKTOP |
- JOB_OBJECT_UILIMIT_EXITWINDOWS | JOB_OBJECT_UILIMIT_DISPLAYSETTINGS;
+ JOB_OBJECT_UILIMIT_EXITWINDOWS | JOB_OBJECT_UILIMIT_DISPLAYSETTINGS |
+ JOB_OBJECT_UILIMIT_HANDLES | JOB_OBJECT_UILIMIT_GLOBALATOMS;
+ if (StaticPrefs::security_sandbox_gpu_disable_job_uilimits()) {
+ uiExceptions |= JOB_OBJECT_UILIMIT_ALL;
+ }
sandbox::MitigationFlags initialMitigations =
sandbox::MITIGATION_BOTTOM_UP_ASLR | sandbox::MITIGATION_HEAP_TERMINATE |
diff --git a/widget/windows/WinCompositorWindowThread.cpp b/widget/windows/WinCompositorWindowThread.cpp
index 0b89e16d45c..10f5d13b6f1 100644
--- a/widget/windows/WinCompositorWindowThread.cpp
+++ b/widget/windows/WinCompositorWindowThread.cpp
@@ -8,6 +8,7 @@
#include "mozilla/StaticMonitor.h"
#include "mozilla/StaticPrefs_apz.h"
#include "mozilla/StaticPtr.h"
+#include "mozilla/WindowsUserHandleValidation.h"
#include "mozilla/gfx/Logging.h"
#include "mozilla/layers/SynchronousTask.h"
#include "transport/runnable_utils.h"
@@ -20,6 +21,19 @@ static StaticRefPtr<WinCompositorWindowThread> sWinCompositorWindowThread;
static StaticMonitor sShutdownMonitor;
static bool sShutdownComplete MOZ_GUARDED_BY(sShutdownMonitor) = false;
+// Ensures the flag that forces user32 handle validation calls onto the slow
+// kernel path is cleared for this thread, since it creates, destroys and
+// dispatches messages to its own HWNDs. Overriding Init() (rather than
+// posting a task after Start()) guarantees this runs on every OS thread this
+// object drives, including after a Stop()/StartWithOptions() restart.
+class CompositorWindowThread final : public base::Thread {
+ public:
+ CompositorWindowThread() : base::Thread("WinCompositor") {}
+
+ protected:
+ void Init() override { ForceToGuiThreadAndFixTebValidateHandlesFlag(); }
+};
+
/// A window procedure that logs when an input event is received to the gfx
/// error log
///
@@ -90,7 +104,7 @@ void WinCompositorWindowThread::Start() {
sWinCompositorWindowThread = nullptr;
}
- base::Thread* thread = new base::Thread("WinCompositor");
+ base::Thread* thread = new CompositorWindowThread();
if (!thread->StartWithOptions(options)) {
delete thread;
return;
Loading diff…
References
On This Page