diff options
Diffstat (limited to 'test/syscalls/linux/exceptions.cc')
-rw-r--r-- | test/syscalls/linux/exceptions.cc | 367 |
1 files changed, 367 insertions, 0 deletions
diff --git a/test/syscalls/linux/exceptions.cc b/test/syscalls/linux/exceptions.cc new file mode 100644 index 000000000..420b9543f --- /dev/null +++ b/test/syscalls/linux/exceptions.cc @@ -0,0 +1,367 @@ +// Copyright 2018 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include <signal.h> + +#include "gtest/gtest.h" +#include "test/util/logging.h" +#include "test/util/platform_util.h" +#include "test/util/signal_util.h" +#include "test/util/test_util.h" + +namespace gvisor { +namespace testing { + +// Default value for the x87 FPU control word. See Intel SDM Vol 1, Ch 8.1.5 +// "x87 FPU Control Word". +constexpr uint16_t kX87ControlWordDefault = 0x37f; + +// Mask for the divide-by-zero exception. +constexpr uint16_t kX87ControlWordDiv0Mask = 1 << 2; + +// Default value for the SSE control register (MXCSR). See Intel SDM Vol 1, Ch +// 11.6.4 "Initialization of SSE/SSE3 Extensions". +constexpr uint32_t kMXCSRDefault = 0x1f80; + +// Mask for the divide-by-zero exception. +constexpr uint32_t kMXCSRDiv0Mask = 1 << 9; + +// Flag for a pending divide-by-zero exception. +constexpr uint32_t kMXCSRDiv0Flag = 1 << 2; + +void inline Halt() { asm("hlt\r\n"); } + +void inline SetAlignmentCheck() { + asm("subq $128, %%rsp\r\n" // Avoid potential red zone clobber + "pushf\r\n" + "pop %%rax\r\n" + "or $0x40000, %%rax\r\n" + "push %%rax\r\n" + "popf\r\n" + "addq $128, %%rsp\r\n" + : + : + : "ax"); +} + +void inline ClearAlignmentCheck() { + asm("subq $128, %%rsp\r\n" // Avoid potential red zone clobber + "pushf\r\n" + "pop %%rax\r\n" + "mov $0x40000, %%rbx\r\n" + "not %%rbx\r\n" + "and %%rbx, %%rax\r\n" + "push %%rax\r\n" + "popf\r\n" + "addq $128, %%rsp\r\n" + : + : + : "ax", "bx"); +} + +void inline Int3Normal() { asm(".byte 0xcd, 0x03\r\n"); } + +void inline Int3Compact() { asm(".byte 0xcc\r\n"); } + +void InIOHelper(int width, int value) { + EXPECT_EXIT( + { + switch (width) { + case 1: + asm volatile("inb %%dx, %%al" ::"d"(value) : "%eax"); + break; + case 2: + asm volatile("inw %%dx, %%ax" ::"d"(value) : "%eax"); + break; + case 4: + asm volatile("inl %%dx, %%eax" ::"d"(value) : "%eax"); + break; + default: + FAIL() << "invalid input width, only 1, 2 or 4 is allowed"; + } + }, + ::testing::KilledBySignal(SIGSEGV), ""); +} + +TEST(ExceptionTest, Halt) { + // In order to prevent the regular handler from messing with things (and + // perhaps refaulting until some other signal occurs), we reset the handler to + // the default action here and ensure that it dies correctly. + struct sigaction sa = {}; + sa.sa_handler = SIG_DFL; + auto const cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGSEGV, sa)); + + EXPECT_EXIT(Halt(), ::testing::KilledBySignal(SIGSEGV), ""); +} + +TEST(ExceptionTest, DivideByZero) { + // See above. + struct sigaction sa = {}; + sa.sa_handler = SIG_DFL; + auto const cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGFPE, sa)); + + EXPECT_EXIT( + { + uint32_t remainder; + uint32_t quotient; + uint32_t divisor = 0; + uint64_t value = 1; + asm("divl 0(%2)\r\n" + : "=d"(remainder), "=a"(quotient) + : "r"(&divisor), "d"(value >> 32), "a"(value)); + TEST_CHECK(quotient > 0); // Force dependency. + }, + ::testing::KilledBySignal(SIGFPE), ""); +} + +// By default, x87 exceptions are masked and simply return a default value. +TEST(ExceptionTest, X87DivideByZeroMasked) { + int32_t quotient; + int32_t value = 1; + int32_t divisor = 0; + asm("fildl %[value]\r\n" + "fidivl %[divisor]\r\n" + "fistpl %[quotient]\r\n" + : [ quotient ] "=m"(quotient) + : [ value ] "m"(value), [ divisor ] "m"(divisor)); + + EXPECT_EQ(quotient, INT32_MIN); +} + +// When unmasked, division by zero raises SIGFPE. +TEST(ExceptionTest, X87DivideByZeroUnmasked) { + // See above. + struct sigaction sa = {}; + sa.sa_handler = SIG_DFL; + auto const cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGFPE, sa)); + + EXPECT_EXIT( + { + // Clear the divide by zero exception mask. + constexpr uint16_t kControlWord = + kX87ControlWordDefault & ~kX87ControlWordDiv0Mask; + + int32_t quotient; + int32_t value = 1; + int32_t divisor = 0; + asm volatile( + "fldcw %[cw]\r\n" + "fildl %[value]\r\n" + "fidivl %[divisor]\r\n" + "fistpl %[quotient]\r\n" + : [ quotient ] "=m"(quotient) + : [ cw ] "m"(kControlWord), [ value ] "m"(value), + [ divisor ] "m"(divisor)); + }, + ::testing::KilledBySignal(SIGFPE), ""); +} + +// Pending exceptions in the x87 status register are not clobbered by syscalls. +TEST(ExceptionTest, X87StatusClobber) { + // See above. + struct sigaction sa = {}; + sa.sa_handler = SIG_DFL; + auto const cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGFPE, sa)); + + EXPECT_EXIT( + { + // Clear the divide by zero exception mask. + constexpr uint16_t kControlWord = + kX87ControlWordDefault & ~kX87ControlWordDiv0Mask; + + int32_t quotient; + int32_t value = 1; + int32_t divisor = 0; + asm volatile( + "fildl %[value]\r\n" + "fidivl %[divisor]\r\n" + // Exception is masked, so it does not occur here. + "fistpl %[quotient]\r\n" + + // SYS_getpid placed in rax by constraint. + "syscall\r\n" + + // Unmask exception. The syscall didn't clobber the pending + // exception, so now it can be raised. + // + // N.B. "a floating-point exception will be generated upon execution + // of the *next* floating-point instruction". + "fldcw %[cw]\r\n" + "fwait\r\n" + : [ quotient ] "=m"(quotient) + : [ value ] "m"(value), [ divisor ] "m"(divisor), "a"(SYS_getpid), + [ cw ] "m"(kControlWord) + : "rcx", "r11"); + }, + ::testing::KilledBySignal(SIGFPE), ""); +} + +// By default, SSE exceptions are masked and simply return a default value. +TEST(ExceptionTest, SSEDivideByZeroMasked) { + uint32_t status; + int32_t quotient; + int32_t value = 1; + int32_t divisor = 0; + asm("cvtsi2ssl %[value], %%xmm0\r\n" + "cvtsi2ssl %[divisor], %%xmm1\r\n" + "divss %%xmm1, %%xmm0\r\n" + "cvtss2sil %%xmm0, %[quotient]\r\n" + : [ quotient ] "=r"(quotient), [ status ] "=r"(status) + : [ value ] "r"(value), [ divisor ] "r"(divisor) + : "xmm0", "xmm1"); + + EXPECT_EQ(quotient, INT32_MIN); +} + +// When unmasked, division by zero raises SIGFPE. +TEST(ExceptionTest, SSEDivideByZeroUnmasked) { + // See above. + struct sigaction sa = {}; + sa.sa_handler = SIG_DFL; + auto const cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGFPE, sa)); + + EXPECT_EXIT( + { + // Clear the divide by zero exception mask. + constexpr uint32_t kMXCSR = kMXCSRDefault & ~kMXCSRDiv0Mask; + + int32_t quotient; + int32_t value = 1; + int32_t divisor = 0; + asm volatile( + "ldmxcsr %[mxcsr]\r\n" + "cvtsi2ssl %[value], %%xmm0\r\n" + "cvtsi2ssl %[divisor], %%xmm1\r\n" + "divss %%xmm1, %%xmm0\r\n" + "cvtss2sil %%xmm0, %[quotient]\r\n" + : [ quotient ] "=r"(quotient) + : [ mxcsr ] "m"(kMXCSR), [ value ] "r"(value), + [ divisor ] "r"(divisor) + : "xmm0", "xmm1"); + }, + ::testing::KilledBySignal(SIGFPE), ""); +} + +// Pending exceptions in the SSE status register are not clobbered by syscalls. +TEST(ExceptionTest, SSEStatusClobber) { + uint32_t mxcsr; + int32_t quotient; + int32_t value = 1; + int32_t divisor = 0; + asm("cvtsi2ssl %[value], %%xmm0\r\n" + "cvtsi2ssl %[divisor], %%xmm1\r\n" + "divss %%xmm1, %%xmm0\r\n" + // Exception is masked, so it does not occur here. + "cvtss2sil %%xmm0, %[quotient]\r\n" + + // SYS_getpid placed in rax by constraint. + "syscall\r\n" + + // Intel SDM Vol 1, Ch 10.2.3.1 "SIMD Floating-Point Mask and Flag Bits": + // "If LDMXCSR or FXRSTOR clears a mask bit and sets the corresponding + // exception flag bit, a SIMD floating-point exception will not be + // generated as a result of this change. The unmasked exception will be + // generated only upon the execution of the next SSE/SSE2/SSE3 instruction + // that detects the unmasked exception condition." + // + // Though ambiguous, empirical evidence indicates that this means that + // exception flags set in the status register will never cause an + // exception to be raised; only a new exception condition will do so. + // + // Thus here we just check for the flag itself rather than trying to raise + // the exception. + "stmxcsr %[mxcsr]\r\n" + : [ quotient ] "=r"(quotient), [ mxcsr ] "+m"(mxcsr) + : [ value ] "r"(value), [ divisor ] "r"(divisor), "a"(SYS_getpid) + : "xmm0", "xmm1", "rcx", "r11"); + + EXPECT_TRUE(mxcsr & kMXCSRDiv0Flag); +} + +TEST(ExceptionTest, IOAccessFault) { + // See above. + struct sigaction sa = {}; + sa.sa_handler = SIG_DFL; + auto const cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGSEGV, sa)); + + InIOHelper(1, 0x0); + InIOHelper(2, 0x7); + InIOHelper(4, 0x6); + InIOHelper(1, 0xffff); + InIOHelper(2, 0xffff); + InIOHelper(4, 0xfffd); +} + +TEST(ExceptionTest, Alignment) { + SetAlignmentCheck(); + ClearAlignmentCheck(); +} + +TEST(ExceptionTest, AlignmentHalt) { + // See above. + struct sigaction sa = {}; + sa.sa_handler = SIG_DFL; + auto const cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGSEGV, sa)); + + // Reported upstream. We need to ensure that bad flags are cleared even in + // fault paths. Set the alignment flag and then generate an exception. + EXPECT_EXIT( + { + SetAlignmentCheck(); + Halt(); + }, + ::testing::KilledBySignal(SIGSEGV), ""); +} + +TEST(ExceptionTest, AlignmentCheck) { + SKIP_IF(PlatformSupportAlignmentCheck() != PlatformSupport::Allowed); + + // See above. + struct sigaction sa = {}; + sa.sa_handler = SIG_DFL; + auto const cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGBUS, sa)); + + EXPECT_EXIT( + { + char array[16]; + SetAlignmentCheck(); + for (int i = 0; i < 8; i++) { + // At least 7/8 offsets will be unaligned here. + uint64_t* ptr = reinterpret_cast<uint64_t*>(&array[i]); + asm("mov %0, 0(%0)\r\n" : : "r"(ptr) : "ax"); + } + }, + ::testing::KilledBySignal(SIGBUS), ""); +} + +TEST(ExceptionTest, Int3Normal) { + // See above. + struct sigaction sa = {}; + sa.sa_handler = SIG_DFL; + auto const cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGTRAP, sa)); + + EXPECT_EXIT(Int3Normal(), ::testing::KilledBySignal(SIGTRAP), ""); +} + +TEST(ExceptionTest, Int3Compact) { + // See above. + struct sigaction sa = {}; + sa.sa_handler = SIG_DFL; + auto const cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGTRAP, sa)); + + EXPECT_EXIT(Int3Compact(), ::testing::KilledBySignal(SIGTRAP), ""); +} + +} // namespace testing +} // namespace gvisor |