[zircon][debugger] Don't write reserved part of mxcsr register.
This CL fixes a kernel panic in zircon, in which the
`zx_thread_write_state` syscall could overwrite the entire `mxcsr`,
including the reserved bits.
According to section "10.2.3 MXCSR Control and Status Register" in
Intel's Software Developer Manual Volume 1: Basic Architecture,
writing a non-zero value to the reserved bits causes a general
protection fault.
The fix reads the mxcsr_mask from the `x86_xsave_legacy_area` and checks
whether the function is trying to write into a non-set bit. Note that
it's possible for the mxcsr_mask to be zero, and in that case, the mask
is hard-coded to the value 0x0000ffbf, as specified by the intel and amd
manuals[0][1].
If the mxcsr has invalid bits, the function returns `ZX_ERR_INVALID_ARGS`,
not copying anything to the fxsave area.
I added a unit test to test this behavior: writing all ones to the
register would trigger a general protection fault if this fix is not included.
To run the test:
```
$ fx set bringup.x64 \
--with-base //garnet/packages/tests:zircon \
--with-base //bundles/bringup:tests
```
And run it with `runtests -t threads-test`
BUG=50632
This vulnerability was reported by: Quarkslab
[0]: Intel 64 and IA-32 Architectures Software Developer’s Manual
Volume 1: Basic Architecture
Section: 11.6.6 Guidelines for Writing to the MXCSR Register
[1]: AMD64 Architecture Programmer’s Manual
Volume 2: System Programming
Section: 11.5.9 MXCSR State Management
Change-Id: If1794258a086e20beb24591e63d8da87b74b828a
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/383146
Commit-Queue: Marco Vanotti <mvanotti@google.com>
Reviewed-by: Gianfranco Valentino <gevalentino@google.com>
Reviewed-by: Venkatesh Srinivas <venkateshs@google.com>
Reviewed-by: Brett Wilson <brettw@google.com>
Testability-Review: Gianfranco Valentino <gevalentino@google.com>
Testability-Review: Brett Wilson <brettw@google.com>
diff --git a/zircon/kernel/arch/x86/debugger.cc b/zircon/kernel/arch/x86/debugger.cc
index 6d20d23..ca42445 100644
--- a/zircon/kernel/arch/x86/debugger.cc
+++ b/zircon/kernel/arch/x86/debugger.cc
@@ -87,6 +87,28 @@
// Whether an operation gets thread state or sets it.
enum class RegAccess { kGet, kSet };
+// Checks whether the mxcsr register has unsupported bits.
+// The processor specifies which flags of the mxcsr are supported via the
+// mxcsr_mask obtained with the fxsave instruction.
+//
+// The manuals mention that it is possible for the mask to be 0, and specify
+// 0x000ffbf as the default value.
+//
+// For details see:
+// Intel 64 and IA-32 Architectures Software Developer’s Manual
+// Volume 1: Basic Architecture
+// Section: 11.6.6 Guidelines for Writing to the MXCSR Register
+// AMD64 Architecture Programmer’s Manual
+// Volume 2: System Programming
+// Section: 11.5.9 MXCSR State Management
+static inline bool mxcsr_is_valid(uint32_t mxcsr, uint32_t mxcsr_mask) {
+ if (mxcsr_mask == 0x0) {
+ mxcsr_mask = 0x0000ffbf;
+ }
+
+ return mxcsr & ~mxcsr_mask;
+}
+
// Backend for arch_get_vector_regs and arch_set_vector_regs. This does a read or write of the
// thread to or from the regs structure.
zx_status_t x86_get_set_vector_regs(Thread* thread, zx_thread_state_vector_regs* regs,
@@ -121,6 +143,14 @@
thread->arch_.extended_register_state, X86_XSAVE_STATE_INDEX_SSE, mark_present,
&comp_size));
DEBUG_ASSERT(save); // Legacy getter should always succeed.
+
+ // fxbug.dev/50632: Overwriting the reserved bits of the mxcsr register
+ // causes a #GP Fault. We need to check against the mxcsr_mask to see if the
+ // proposed mxcsr is valid.
+ if (access == RegAccess::kSet && mxcsr_is_valid(regs->mxcsr, save->mxcsr_mask)) {
+ return ZX_ERR_INVALID_ARGS;
+ }
+
for (int i = 0; i < kNumSSERegs; i++) {
get_set_memcpy(®s->zmm[i].v[0], &save->xmm[i], kXmmRegSize);
}
@@ -244,8 +274,8 @@
x86_fill_in_syscall_from_gregs(thread->arch_.suspended_general_regs.syscall, in);
break;
}
- default:
- ASSERT(false);
+ default:
+ ASSERT(false);
}
thread->arch_.fs_base = in->fs_base;
diff --git a/zircon/system/utest/core/threads/threads.cc b/zircon/system/utest/core/threads/threads.cc
index 40d6593..db3ecb7 100644
--- a/zircon/system/utest/core/threads/threads.cc
+++ b/zircon/system/utest/core/threads/threads.cc
@@ -152,9 +152,7 @@
}
}
- void GrowStackVmo() {
- ASSERT_EQ(stack_handle_.set_size(kStackSize), ZX_OK);
- }
+ void GrowStackVmo() { ASSERT_EQ(stack_handle_.set_size(kStackSize), ZX_OK); }
bool StartThread(zxr_thread_entry_t entry, void* arg) {
return zxr_thread_start(thread_, stack_, kStackSize, entry, arg) == ZX_OK;
@@ -917,9 +915,7 @@
}
// Test signal delivery of suspended threads via single async wait.
-TEST(Threads, SuspendSingleWaitAsyncSignalDelivery) {
- TestSuspendWaitAsyncSignalDeliveryWorker();
-}
+TEST(Threads, SuspendSingleWaitAsyncSignalDelivery) { TestSuspendWaitAsyncSignalDeliveryWorker(); }
// Test signal delivery of suspended threads via repeating async wait.
TEST(Threads, SuspendRepeatingWaitAsyncSignalDelivery) {
@@ -1199,6 +1195,31 @@
EXPECT_TRUE(vector_regs_expect_eq(regs_to_set, regs));
}
+// Test for fxbug.dev/50632: Make sure zx_thread_write_state doesn't overwrite
+// reserved bits in mxcsr (x64 only).
+TEST(Threads, WriteThreadStateWithInvalidMxcsrIsInvalidArgs) {
+#if defined(__x86_64__)
+ RegisterWriteSetup<zx_thread_state_vector_regs_t> setup;
+ setup.Init();
+
+ zx_thread_state_vector_regs_t start_values;
+ ASSERT_OK(zx_thread_read_state(setup.thread_handle(), ZX_THREAD_STATE_VECTOR_REGS, &start_values,
+ sizeof(start_values)));
+
+ zx_thread_state_vector_regs_t regs_to_set;
+ vector_regs_fill_test_values(®s_to_set);
+ regs_to_set.mxcsr = 0xffffffff;
+
+ EXPECT_EQ(ZX_ERR_INVALID_ARGS,
+ zx_thread_write_state(setup.thread_handle(), ZX_THREAD_STATE_VECTOR_REGS, ®s_to_set,
+ sizeof(regs_to_set)));
+
+ zx_thread_state_vector_regs_t end_values;
+ setup.DoSave(&save_vector_regs_and_exit_thread, &end_values);
+ EXPECT_TRUE(vector_regs_expect_eq(start_values, end_values));
+#endif // defined(__x86_64__)
+}
+
// This test starts a thread which reads and writes from TLS.
TEST(Threads, ThreadLocalRegisterState) {
RegisterWriteSetup<struct thread_local_regs> setup;