diff --git a/pkg/sentry/kernel/task_run.go b/pkg/sentry/kernel/task_run.go index 109149c0c..b39fe1998 100644 --- a/pkg/sentry/kernel/task_run.go +++ b/pkg/sentry/kernel/task_run.go @@ -201,26 +201,20 @@ func (app *runApp) execute(t *Task) taskRunState { if t.rseqPreempted { t.rseqPreempted = false if t.rseqAddr != 0 || t.oldRSeqCPUAddr != 0 { - // Linux writes the CPU on every preemption. We only do - // so if it changed. Thus we may delay delivery of - // SIGSEGV if rseqAddr/oldRSeqCPUAddr is invalid. - cpu := int32(hostcpu.GetCPU()) - if t.rseqCPU != cpu { - t.rseqCPU = cpu - if err := t.rseqCopyOutCPU(); err != nil { - t.Debugf("Failed to copy CPU to %#x for rseq: %v", t.rseqAddr, err) - t.forceSignal(linux.SIGSEGV, false) - t.SendSignal(SignalInfoPriv(linux.SIGSEGV)) - // Re-enter the task run loop for signal delivery. - return (*runApp)(nil) - } - if err := t.oldRSeqCopyOutCPU(); err != nil { - t.Debugf("Failed to copy CPU to %#x for old rseq: %v", t.oldRSeqCPUAddr, err) - t.forceSignal(linux.SIGSEGV, false) - t.SendSignal(SignalInfoPriv(linux.SIGSEGV)) - // Re-enter the task run loop for signal delivery. - return (*runApp)(nil) - } + t.rseqCPU = int32(hostcpu.GetCPU()) + if err := t.rseqCopyOutCPU(); err != nil { + t.Debugf("Failed to copy CPU to %#x for rseq: %v", t.rseqAddr, err) + t.forceSignal(linux.SIGSEGV, false) + t.SendSignal(SignalInfoPriv(linux.SIGSEGV)) + // Re-enter the task run loop for signal delivery. + return (*runApp)(nil) + } + if err := t.oldRSeqCopyOutCPU(); err != nil { + t.Debugf("Failed to copy CPU to %#x for old rseq: %v", t.oldRSeqCPUAddr, err) + t.forceSignal(linux.SIGSEGV, false) + t.SendSignal(SignalInfoPriv(linux.SIGSEGV)) + // Re-enter the task run loop for signal delivery. + return (*runApp)(nil) } } t.rseqInterrupt() diff --git a/test/syscalls/linux/rseq.cc b/test/syscalls/linux/rseq.cc index 94f9154a0..520a083e9 100644 --- a/test/syscalls/linux/rseq.cc +++ b/test/syscalls/linux/rseq.cc @@ -13,6 +13,7 @@ // limitations under the License. #include +#include #include #include #include @@ -196,6 +197,18 @@ TEST(RseqTest, InvalidAbortClearsCS) { RunChildTest(kRseqTestInvalidAbortClearsCS, 0); } +// TODO(b/326665974): Implement clone() in rseq/start_arm64.S. +#if defined(__x86_64__) +TEST(RseqTest, MembarrierResetsCpuIdStart) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(RSeqSupported())); + int cmds = syscall(SYS_membarrier, MEMBARRIER_CMD_QUERY, 0); + SKIP_IF(cmds < 0); + SKIP_IF((cmds & MEMBARRIER_CMD_PRIVATE_EXPEDITED_RSEQ) == 0); + + RunChildTest(kRseqTestMembarrierResetsCpuIdStart, 0); +} +#endif + } // namespace } // namespace testing diff --git a/test/syscalls/linux/rseq/rseq.cc b/test/syscalls/linux/rseq/rseq.cc index 6f5d38bba..1cc0d80f9 100644 --- a/test/syscalls/linux/rseq/rseq.cc +++ b/test/syscalls/linux/rseq/rseq.cc @@ -319,6 +319,78 @@ int TestInvalidAbortClearsCS() { return 0; } +// rseq.cpu_id_start is overwritten by RSEQ fence. +int TestMembarrierResetsCpuIdStart() { + struct rseq r = {}; + int ret = sys_rseq(&r, sizeof(r), 0, kRseqSignature); + if (sys_errno(ret) != 0) { + return 1; + } + ret = sys_membarrier(MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED_RSEQ, 0); + if (sys_errno(ret) != 0) { + return 1; + } + constexpr size_t kStackSize = 2 << 20; // 2 MB + uintptr_t child_stack_start = + sys_mmap(nullptr, kStackSize, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0); + if (sys_errno(child_stack_start) != 0) { + return 1; + } + uintptr_t child_stack_end = child_stack_start + kStackSize; + + // Set cpu_id_start to a negative value. + __atomic_store_n(&r.cpu_id_start, 0xffffffff, __ATOMIC_RELAXED); + + // Start a thread to invoke the RSEQ fence. + uint32_t child_cleartid = 0; + bool fenced = false; + auto tid = clone( + +[](void* arg) { + int ret = sys_membarrier(MEMBARRIER_CMD_PRIVATE_EXPEDITED_RSEQ, 0); + if (sys_errno(ret) != 0) { + sys_exit_group(1); + } + __atomic_store_n(static_cast(arg), true, __ATOMIC_RELAXED); + return 0; + }, + child_stack_end, + CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_THREAD | + CLONE_CHILD_CLEARTID | CLONE_CHILD_SETTID, + &fenced, &child_cleartid); + if (sys_errno(tid) != 0) { + return 1; + } + + // Expect that cpu_id_start will be overwritten with a real CPU number when + // the thread invokes the RSEQ fence. + while (true) { + if (__atomic_load_n(&fenced, __ATOMIC_ACQUIRE)) { + if (static_cast( + __atomic_load_n(&r.cpu_id_start, __ATOMIC_RELAXED)) < 0) { + return 1; + } + break; + } + } + + // Wait for the thread to exit. + while (true) { + uint32_t cur_child_cleartid = + __atomic_load_n(&child_cleartid, __ATOMIC_RELAXED); + if (cur_child_cleartid == 0) { + break; + } + auto ret = + sys_futex(&child_cleartid, FUTEX_WAIT, cur_child_cleartid, nullptr); + if (ret != 0 && sys_errno(ret) != EAGAIN && sys_errno(ret) != EINTR) { + return 1; + } + } + + return 0; +} + // Exit codes: // 0 - Pass // 1 - Fail @@ -369,6 +441,9 @@ extern "C" int main(int argc, char** argv, char** envp) { if (strcmp(argv[1], kRseqTestInvalidAbortClearsCS) == 0) { return TestInvalidAbortClearsCS(); } + if (strcmp(argv[1], kRseqTestMembarrierResetsCpuIdStart) == 0) { + return TestMembarrierResetsCpuIdStart(); + } return 3; } diff --git a/test/syscalls/linux/rseq/start_amd64.S b/test/syscalls/linux/rseq/start_amd64.S index b9611b276..3ea51ddaf 100644 --- a/test/syscalls/linux/rseq/start_amd64.S +++ b/test/syscalls/linux/rseq/start_amd64.S @@ -43,3 +43,43 @@ raw_syscall: .size raw_syscall,.-raw_syscall .section .note.GNU-stack,"",@progbits + + .text + .globl clone + .type clone, @function + +clone: + // Initially: + // %rdi = fn + // %rsi = stack + // %edx = flags + // %rcx = arg + // %r8 = child_tid + push %rbx + push %r12 + mov %rdi,%rbx + mov %rcx,%r12 + mov %edx,%edi // flags + // stack already in %rsi + xor %edx,%edx // parent_tid + mov %r8,%r10 // child_tid + xor %r9d,%r9d // tls + mov $56,%rax // SYS_clone + syscall // clobbers %rcx, %r11 + cmp $0,%rax + je child + pop %r12 + pop %rbx + ret +child: + mov %rsi,%rsp + xor %rbp,%rbp + mov %r12,%rdi + call *%rbx + mov %rax,%rdi + mov $60,%rax // SYS_exit + syscall + hlt + + .size clone,.-clone + .section .note.GNU-stack,"",@progbits diff --git a/test/syscalls/linux/rseq/start_arm64.S b/test/syscalls/linux/rseq/start_arm64.S index 693c1c6eb..8e8c2c8e0 100644 --- a/test/syscalls/linux/rseq/start_arm64.S +++ b/test/syscalls/linux/rseq/start_arm64.S @@ -43,3 +43,15 @@ raw_syscall: .size raw_syscall,.-raw_syscall .section .note.GNU-stack,"",@progbits + + .text + .globl clone + .type clone, @function + +clone: + // TODO(b/326665974) + mov x0,#-38 // ENOSYS + ret + + .size clone,.-clone + .section .note.GNU-stack,"",@progbits diff --git a/test/syscalls/linux/rseq/syscalls.h b/test/syscalls/linux/rseq/syscalls.h index c4118e6c5..0e0354d24 100644 --- a/test/syscalls/linux/rseq/syscalls.h +++ b/test/syscalls/linux/rseq/syscalls.h @@ -19,11 +19,17 @@ // Syscall numbers. #if defined(__x86_64__) -constexpr int kGetpid = 39; constexpr int kExitGroup = 231; +constexpr int kFutex = 202; +constexpr int kGetpid = 39; +constexpr int kMembarrier = 324; +constexpr int kMmap = 9; #elif defined(__aarch64__) -constexpr int kGetpid = 172; constexpr int kExitGroup = 94; +constexpr int kFutex = 98; +constexpr int kGetpid = 172; +constexpr int kMembarrier = 283; +constexpr int kMmap = 222; #else #error "Unknown architecture" #endif @@ -41,6 +47,8 @@ constexpr int kMaxErrno = 4095; // Errno values. #define EPERM 1 +#define EINTR 4 +#define EAGAIN 11 #define EFAULT 14 #define EBUSY 16 #define EINVAL 22 @@ -56,13 +64,54 @@ static inline int sys_errno(uintptr_t rval) { extern "C" uintptr_t raw_syscall(int number, ...); +extern "C" int clone(int (*fn)(void*), uintptr_t stack, int flags, void* arg, + uint32_t* child_tid); + +// clone flags: +#define CLONE_VM 0x00000100 +#define CLONE_FS 0x00000200 +#define CLONE_FILES 0x00000400 +#define CLONE_SIGHAND 0x00000800 +#define CLONE_THREAD 0x00010000 +#define CLONE_CHILD_CLEARTID 0x00200000 +#define CLONE_CHILD_SETTID 0x01000000 + static inline void sys_exit_group(int status) { raw_syscall(kExitGroup, status); } + +static inline uintptr_t sys_futex(uint32_t* uaddr, int futex_op, uint32_t val, + const struct timespec* timeout) { + return raw_syscall(kFutex, uaddr, futex_op, val, timeout); +} + +// futex ops: +#define FUTEX_WAIT 0 + static inline int sys_getpid() { return static_cast(raw_syscall(kGetpid)); } +static inline uintptr_t sys_membarrier(int cmd, unsigned int flags) { + return raw_syscall(kMembarrier, cmd, flags); +} + +// membarrier commands: +#define MEMBARRIER_CMD_PRIVATE_EXPEDITED_RSEQ (1 << 7) +#define MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED_RSEQ (1 << 8) + +static inline uintptr_t sys_mmap(void* addr, size_t length, int prot, int flags, + int fd, int64_t offset) { + return raw_syscall(kMmap, addr, length, prot, flags, fd, offset); +} + +// mmap options: +#define PROT_READ 0x1 +#define PROT_WRITE 0x2 +#define MAP_PRIVATE 0x02 +#define MAP_ANONYMOUS 0x20 +#define MAP_STACK 0x020000 + } // namespace testing } // namespace gvisor diff --git a/test/syscalls/linux/rseq/test.h b/test/syscalls/linux/rseq/test.h index ff0dd6e48..bacdb4dd8 100644 --- a/test/syscalls/linux/rseq/test.h +++ b/test/syscalls/linux/rseq/test.h @@ -34,6 +34,8 @@ constexpr char kRseqTestAbortSignature[] = "abort-signature"; constexpr char kRseqTestAbortPreCommit[] = "abort-precommit"; constexpr char kRseqTestAbortClearsCS[] = "abort-clears-cs"; constexpr char kRseqTestInvalidAbortClearsCS[] = "invalid-abort-clears-cs"; +constexpr char kRseqTestMembarrierResetsCpuIdStart[] = + "membarrier-resets-cpu-id-start"; } // namespace testing } // namespace gvisor