// Copyright 2019 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 "test/syscalls/linux/rseq/critical.h" #include "test/syscalls/linux/rseq/syscalls.h" #include "test/syscalls/linux/rseq/test.h" #include "test/syscalls/linux/rseq/types.h" #include "test/syscalls/linux/rseq/uapi.h" namespace gvisor { namespace testing { extern "C" int main(int argc, char** argv, char** envp); // Standalone initialization before calling main(). extern "C" void __init(uintptr_t* sp) { int argc = sp[0]; char** argv = reinterpret_cast(&sp[1]); char** envp = &argv[argc + 1]; // Call main() and exit. sys_exit_group(main(argc, argv, envp)); // sys_exit_group does not return } int strcmp(const char* s1, const char* s2) { const unsigned char* p1 = reinterpret_cast(s1); const unsigned char* p2 = reinterpret_cast(s2); while (*p1 == *p2) { if (!*p1) { return 0; } ++p1; ++p2; } return static_cast(*p1) - static_cast(*p2); } int sys_rseq(struct rseq* rseq, uint32 rseq_len, int flags, uint32 sig) { return raw_syscall(kRseqSyscall, rseq, rseq_len, flags, sig); } // Test that rseq must be aligned. int TestUnaligned() { constexpr uintptr_t kRequiredAlignment = alignof(rseq); char buf[2 * kRequiredAlignment] = {}; uintptr_t ptr = reinterpret_cast(&buf[0]); if ((ptr & (kRequiredAlignment - 1)) == 0) { // buf is already aligned. Misalign it. ptr++; } int ret = sys_rseq(reinterpret_cast(ptr), sizeof(rseq), 0, 0); if (sys_errno(ret) != EINVAL) { return 1; } return 0; } // Sanity test that registration works. int TestRegister() { struct rseq r = {}; if (int ret = sys_rseq(&r, sizeof(r), 0, 0); sys_errno(ret) != 0) { return 1; } return 0; }; // Registration can't be done twice. int TestDoubleRegister() { struct rseq r = {}; if (int ret = sys_rseq(&r, sizeof(r), 0, 0); sys_errno(ret) != 0) { return 1; } if (int ret = sys_rseq(&r, sizeof(r), 0, 0); sys_errno(ret) != EBUSY) { return 1; } return 0; }; // Registration can be done again after unregister. int TestRegisterUnregister() { struct rseq r = {}; if (int ret = sys_rseq(&r, sizeof(r), 0, 0); sys_errno(ret) != 0) { return 1; } if (int ret = sys_rseq(&r, sizeof(r), kRseqFlagUnregister, 0); sys_errno(ret) != 0) { return 1; } if (int ret = sys_rseq(&r, sizeof(r), 0, 0); sys_errno(ret) != 0) { return 1; } return 0; }; // The pointer to rseq must match on register/unregister. int TestUnregisterDifferentPtr() { struct rseq r = {}; if (int ret = sys_rseq(&r, sizeof(r), 0, 0); sys_errno(ret) != 0) { return 1; } struct rseq r2 = {}; if (int ret = sys_rseq(&r2, sizeof(r2), kRseqFlagUnregister, 0); sys_errno(ret) != EINVAL) { return 1; } return 0; }; // The signature must match on register/unregister. int TestUnregisterDifferentSignature() { constexpr int kSignature = 0; struct rseq r = {}; if (int ret = sys_rseq(&r, sizeof(r), 0, kSignature); sys_errno(ret) != 0) { return 1; } if (int ret = sys_rseq(&r, sizeof(r), kRseqFlagUnregister, kSignature + 1); sys_errno(ret) != EPERM) { return 1; } return 0; }; // The CPU ID is initialized. int TestCPU() { struct rseq r = {}; r.cpu_id = kRseqCPUIDUninitialized; if (int ret = sys_rseq(&r, sizeof(r), 0, 0); sys_errno(ret) != 0) { return 1; } if (__atomic_load_n(&r.cpu_id, __ATOMIC_RELAXED) < 0) { return 1; } if (__atomic_load_n(&r.cpu_id_start, __ATOMIC_RELAXED) < 0) { return 1; } return 0; }; // Critical section is eventually aborted. int TestAbort() { struct rseq r = {}; if (int ret = sys_rseq(&r, sizeof(r), 0, kRseqSignature); sys_errno(ret) != 0) { return 1; } struct rseq_cs cs = {}; cs.version = 0; cs.flags = 0; cs.start_ip = reinterpret_cast(&rseq_loop_start); cs.post_commit_offset = reinterpret_cast(&rseq_loop_post_commit) - reinterpret_cast(&rseq_loop_start); cs.abort_ip = reinterpret_cast(&rseq_loop_abort); // Loops until abort. If this returns then abort occurred. rseq_loop(&r, &cs); return 0; }; // Abort may be before the critical section. int TestAbortBefore() { struct rseq r = {}; if (int ret = sys_rseq(&r, sizeof(r), 0, kRseqSignature); sys_errno(ret) != 0) { return 1; } struct rseq_cs cs = {}; cs.version = 0; cs.flags = 0; cs.start_ip = reinterpret_cast(&rseq_loop_start); cs.post_commit_offset = reinterpret_cast(&rseq_loop_post_commit) - reinterpret_cast(&rseq_loop_start); cs.abort_ip = reinterpret_cast(&rseq_loop_early_abort); // Loops until abort. If this returns then abort occurred. rseq_loop(&r, &cs); return 0; }; // Signature must match. int TestAbortSignature() { struct rseq r = {}; if (int ret = sys_rseq(&r, sizeof(r), 0, kRseqSignature + 1); sys_errno(ret) != 0) { return 1; } struct rseq_cs cs = {}; cs.version = 0; cs.flags = 0; cs.start_ip = reinterpret_cast(&rseq_loop_start); cs.post_commit_offset = reinterpret_cast(&rseq_loop_post_commit) - reinterpret_cast(&rseq_loop_start); cs.abort_ip = reinterpret_cast(&rseq_loop_abort); // Loops until abort. This should SIGSEGV on abort. rseq_loop(&r, &cs); return 1; }; // Abort must not be in the critical section. int TestAbortPreCommit() { struct rseq r = {}; if (int ret = sys_rseq(&r, sizeof(r), 0, kRseqSignature + 1); sys_errno(ret) != 0) { return 1; } struct rseq_cs cs = {}; cs.version = 0; cs.flags = 0; cs.start_ip = reinterpret_cast(&rseq_loop_start); cs.post_commit_offset = reinterpret_cast(&rseq_loop_post_commit) - reinterpret_cast(&rseq_loop_start); cs.abort_ip = reinterpret_cast(&rseq_loop_pre_commit); // Loops until abort. This should SIGSEGV on abort. rseq_loop(&r, &cs); return 1; }; // rseq.rseq_cs is cleared on abort. int TestAbortClearsCS() { struct rseq r = {}; if (int ret = sys_rseq(&r, sizeof(r), 0, kRseqSignature); sys_errno(ret) != 0) { return 1; } struct rseq_cs cs = {}; cs.version = 0; cs.flags = 0; cs.start_ip = reinterpret_cast(&rseq_loop_start); cs.post_commit_offset = reinterpret_cast(&rseq_loop_post_commit) - reinterpret_cast(&rseq_loop_start); cs.abort_ip = reinterpret_cast(&rseq_loop_abort); // Loops until abort. If this returns then abort occurred. rseq_loop(&r, &cs); if (__atomic_load_n(&r.rseq_cs, __ATOMIC_RELAXED)) { return 1; } return 0; }; // rseq.rseq_cs is cleared on abort outside of critical section. int TestInvalidAbortClearsCS() { struct rseq r = {}; if (int ret = sys_rseq(&r, sizeof(r), 0, kRseqSignature); sys_errno(ret) != 0) { return 1; } struct rseq_cs cs = {}; cs.version = 0; cs.flags = 0; cs.start_ip = reinterpret_cast(&rseq_loop_start); cs.post_commit_offset = reinterpret_cast(&rseq_loop_post_commit) - reinterpret_cast(&rseq_loop_start); cs.abort_ip = reinterpret_cast(&rseq_loop_abort); __atomic_store_n(&r.rseq_cs, &cs, __ATOMIC_RELAXED); // When the next abort condition occurs, the kernel will clear cs once it // determines we aren't in the critical section. while (1) { if (!__atomic_load_n(&r.rseq_cs, __ATOMIC_RELAXED)) { break; } } return 0; }; // Exit codes: // 0 - Pass // 1 - Fail // 2 - Missing argument // 3 - Unknown test case extern "C" int main(int argc, char** argv, char** envp) { if (argc != 2) { // Usage: rseq return 2; } if (strcmp(argv[1], kRseqTestUnaligned) == 0) { return TestUnaligned(); } if (strcmp(argv[1], kRseqTestRegister) == 0) { return TestRegister(); } if (strcmp(argv[1], kRseqTestDoubleRegister) == 0) { return TestDoubleRegister(); } if (strcmp(argv[1], kRseqTestRegisterUnregister) == 0) { return TestRegisterUnregister(); } if (strcmp(argv[1], kRseqTestUnregisterDifferentPtr) == 0) { return TestUnregisterDifferentPtr(); } if (strcmp(argv[1], kRseqTestUnregisterDifferentSignature) == 0) { return TestUnregisterDifferentSignature(); } if (strcmp(argv[1], kRseqTestCPU) == 0) { return TestCPU(); } if (strcmp(argv[1], kRseqTestAbort) == 0) { return TestAbort(); } if (strcmp(argv[1], kRseqTestAbortBefore) == 0) { return TestAbortBefore(); } if (strcmp(argv[1], kRseqTestAbortSignature) == 0) { return TestAbortSignature(); } if (strcmp(argv[1], kRseqTestAbortPreCommit) == 0) { return TestAbortPreCommit(); } if (strcmp(argv[1], kRseqTestAbortClearsCS) == 0) { return TestAbortClearsCS(); } if (strcmp(argv[1], kRseqTestInvalidAbortClearsCS) == 0) { return TestInvalidAbortClearsCS(); } return 3; } } // namespace testing } // namespace gvisor