Add tests for rseq(2)
Add a decent set of syscall tests for rseq(2). These are a bit awkward because of issues with library integration. libc may register rseq on thread start (including before main on the initial thread), precluding much testing. Thus we run tests in a libc-free subprocess. Support for rseq(2) in gVisor will come in a later commit. PiperOrigin-RevId: 284595994
This commit is contained in:
parent
01eadf51ea
commit
498595d543
|
@ -376,6 +376,8 @@ syscall_test(
|
|||
|
||||
syscall_test(test = "//test/syscalls/linux:rlimits_test")
|
||||
|
||||
syscall_test(test = "//test/syscalls/linux:rseq_test")
|
||||
|
||||
syscall_test(test = "//test/syscalls/linux:rtsignal_test")
|
||||
|
||||
syscall_test(test = "//test/syscalls/linux:sched_test")
|
||||
|
|
|
@ -1852,6 +1852,22 @@ cc_binary(
|
|||
],
|
||||
)
|
||||
|
||||
cc_binary(
|
||||
name = "rseq_test",
|
||||
testonly = 1,
|
||||
srcs = ["rseq.cc"],
|
||||
data = ["//test/syscalls/linux/rseq"],
|
||||
linkstatic = 1,
|
||||
deps = [
|
||||
"//test/syscalls/linux/rseq:lib",
|
||||
"//test/util:logging",
|
||||
"//test/util:multiprocess_util",
|
||||
"//test/util:test_main",
|
||||
"//test/util:test_util",
|
||||
"@com_google_googletest//:gtest",
|
||||
],
|
||||
)
|
||||
|
||||
cc_binary(
|
||||
name = "rtsignal_test",
|
||||
testonly = 1,
|
||||
|
|
|
@ -0,0 +1,198 @@
|
|||
// 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 <errno.h>
|
||||
#include <signal.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "test/syscalls/linux/rseq/test.h"
|
||||
#include "test/syscalls/linux/rseq/uapi.h"
|
||||
#include "test/util/logging.h"
|
||||
#include "test/util/multiprocess_util.h"
|
||||
#include "test/util/test_util.h"
|
||||
|
||||
namespace gvisor {
|
||||
namespace testing {
|
||||
|
||||
namespace {
|
||||
|
||||
// Syscall test for rseq (restartable sequences).
|
||||
//
|
||||
// We must be very careful about how these tests are written. Each thread may
|
||||
// only have one struct rseq registration, which may be done automatically at
|
||||
// thread start (as of 2019-11-13, glibc does *not* support rseq and thus does
|
||||
// not do so).
|
||||
//
|
||||
// Testing of rseq is thus done primarily in a child process with no
|
||||
// registration. This means exec'ing a nostdlib binary, as rseq registration can
|
||||
// only be cleared by execve (or knowing the old rseq address), and glibc (based
|
||||
// on the current unmerged patches) register rseq before calling main()).
|
||||
|
||||
int RSeq(struct rseq* rseq, uint32_t rseq_len, int flags, uint32_t sig) {
|
||||
return syscall(kRseqSyscall, rseq, rseq_len, flags, sig);
|
||||
}
|
||||
|
||||
// Returns true if this kernel supports the rseq syscall.
|
||||
PosixErrorOr<bool> RSeqSupported() {
|
||||
// We have to be careful here, there are three possible cases:
|
||||
//
|
||||
// 1. rseq is not supported -> ENOSYS
|
||||
// 2. rseq is supported and not registered -> success, but we should
|
||||
// unregister.
|
||||
// 3. rseq is supported and registered -> EINVAL (most likely).
|
||||
|
||||
// The only validation done on new registrations is that rseq is aligned and
|
||||
// writable.
|
||||
rseq rseq = {};
|
||||
int ret = RSeq(&rseq, sizeof(rseq), 0, 0);
|
||||
if (ret == 0) {
|
||||
// Successfully registered, rseq is supported. Unregister.
|
||||
ret = RSeq(&rseq, sizeof(rseq), kRseqFlagUnregister, 0);
|
||||
if (ret != 0) {
|
||||
return PosixError(errno);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (errno) {
|
||||
case ENOSYS:
|
||||
// Not supported.
|
||||
return false;
|
||||
case EINVAL:
|
||||
// Supported, but already registered. EINVAL returned because we provided
|
||||
// a different address.
|
||||
return true;
|
||||
default:
|
||||
// Unknown error.
|
||||
return PosixError(errno);
|
||||
}
|
||||
}
|
||||
|
||||
constexpr char kRseqBinary[] = "test/syscalls/linux/rseq/rseq";
|
||||
|
||||
void RunChildTest(std::string test_case, int want_status) {
|
||||
std::string path = RunfilePath(kRseqBinary);
|
||||
|
||||
pid_t child_pid = -1;
|
||||
int execve_errno = 0;
|
||||
auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(
|
||||
ForkAndExec(path, {path, test_case}, {}, &child_pid, &execve_errno));
|
||||
|
||||
ASSERT_GT(child_pid, 0);
|
||||
ASSERT_EQ(execve_errno, 0);
|
||||
|
||||
int status = 0;
|
||||
ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), SyscallSucceeds());
|
||||
ASSERT_EQ(status, want_status);
|
||||
}
|
||||
|
||||
// Test that rseq must be aligned.
|
||||
TEST(RseqTest, Unaligned) {
|
||||
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(RSeqSupported()));
|
||||
|
||||
RunChildTest(kRseqTestUnaligned, 0);
|
||||
}
|
||||
|
||||
// Sanity test that registration works.
|
||||
TEST(RseqTest, Register) {
|
||||
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(RSeqSupported()));
|
||||
|
||||
RunChildTest(kRseqTestRegister, 0);
|
||||
}
|
||||
|
||||
// Registration can't be done twice.
|
||||
TEST(RseqTest, DoubleRegister) {
|
||||
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(RSeqSupported()));
|
||||
|
||||
RunChildTest(kRseqTestDoubleRegister, 0);
|
||||
}
|
||||
|
||||
// Registration can be done again after unregister.
|
||||
TEST(RseqTest, RegisterUnregister) {
|
||||
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(RSeqSupported()));
|
||||
|
||||
RunChildTest(kRseqTestRegisterUnregister, 0);
|
||||
}
|
||||
|
||||
// The pointer to rseq must match on register/unregister.
|
||||
TEST(RseqTest, UnregisterDifferentPtr) {
|
||||
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(RSeqSupported()));
|
||||
|
||||
RunChildTest(kRseqTestUnregisterDifferentPtr, 0);
|
||||
}
|
||||
|
||||
// The signature must match on register/unregister.
|
||||
TEST(RseqTest, UnregisterDifferentSignature) {
|
||||
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(RSeqSupported()));
|
||||
|
||||
RunChildTest(kRseqTestUnregisterDifferentSignature, 0);
|
||||
}
|
||||
|
||||
// The CPU ID is initialized.
|
||||
TEST(RseqTest, CPU) {
|
||||
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(RSeqSupported()));
|
||||
|
||||
RunChildTest(kRseqTestCPU, 0);
|
||||
}
|
||||
|
||||
// Critical section is eventually aborted.
|
||||
TEST(RseqTest, Abort) {
|
||||
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(RSeqSupported()));
|
||||
|
||||
RunChildTest(kRseqTestAbort, 0);
|
||||
}
|
||||
|
||||
// Abort may be before the critical section.
|
||||
TEST(RseqTest, AbortBefore) {
|
||||
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(RSeqSupported()));
|
||||
|
||||
RunChildTest(kRseqTestAbortBefore, 0);
|
||||
}
|
||||
|
||||
// Signature must match.
|
||||
TEST(RseqTest, AbortSignature) {
|
||||
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(RSeqSupported()));
|
||||
|
||||
RunChildTest(kRseqTestAbortSignature, SIGSEGV);
|
||||
}
|
||||
|
||||
// Abort must not be in the critical section.
|
||||
TEST(RseqTest, AbortPreCommit) {
|
||||
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(RSeqSupported()));
|
||||
|
||||
RunChildTest(kRseqTestAbortPreCommit, SIGSEGV);
|
||||
}
|
||||
|
||||
// rseq.rseq_cs is cleared on abort.
|
||||
TEST(RseqTest, AbortClearsCS) {
|
||||
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(RSeqSupported()));
|
||||
|
||||
RunChildTest(kRseqTestAbortClearsCS, 0);
|
||||
}
|
||||
|
||||
// rseq.rseq_cs is cleared on abort outside of critical section.
|
||||
TEST(RseqTest, InvalidAbortClearsCS) {
|
||||
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(RSeqSupported()));
|
||||
|
||||
RunChildTest(kRseqTestInvalidAbortClearsCS, 0);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace testing
|
||||
} // namespace gvisor
|
|
@ -0,0 +1,59 @@
|
|||
# This package contains a standalone rseq test binary. This binary must not
|
||||
# depend on libc, which might use rseq itself.
|
||||
|
||||
load("@bazel_tools//tools/cpp:cc_flags_supplier.bzl", "cc_flags_supplier")
|
||||
load("@rules_cc//cc:defs.bzl", "cc_library")
|
||||
|
||||
package(licenses = ["notice"])
|
||||
|
||||
genrule(
|
||||
name = "rseq_binary",
|
||||
srcs = [
|
||||
"critical.h",
|
||||
"critical.S",
|
||||
"rseq.cc",
|
||||
"syscalls.h",
|
||||
"start.S",
|
||||
"test.h",
|
||||
"types.h",
|
||||
"uapi.h",
|
||||
],
|
||||
outs = ["rseq"],
|
||||
cmd = " ".join([
|
||||
"$(CC)",
|
||||
"$(CC_FLAGS) ",
|
||||
"-I.",
|
||||
"-Wall",
|
||||
"-Werror",
|
||||
"-O2",
|
||||
"-std=c++17",
|
||||
"-static",
|
||||
"-nostdlib",
|
||||
"-ffreestanding",
|
||||
"-o",
|
||||
"$(location rseq)",
|
||||
"$(location critical.S)",
|
||||
"$(location rseq.cc)",
|
||||
"$(location start.S)",
|
||||
]),
|
||||
toolchains = [
|
||||
":no_pie_cc_flags",
|
||||
"@bazel_tools//tools/cpp:current_cc_toolchain",
|
||||
],
|
||||
visibility = ["//:sandbox"],
|
||||
)
|
||||
|
||||
cc_flags_supplier(
|
||||
name = "no_pie_cc_flags",
|
||||
features = ["-pie"],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "lib",
|
||||
testonly = 1,
|
||||
hdrs = [
|
||||
"test.h",
|
||||
"uapi.h",
|
||||
],
|
||||
visibility = ["//:sandbox"],
|
||||
)
|
|
@ -0,0 +1,66 @@
|
|||
// 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.
|
||||
|
||||
// Restartable sequences critical sections.
|
||||
|
||||
// Loops continuously until aborted.
|
||||
//
|
||||
// void rseq_loop(struct rseq* r, struct rseq_cs* cs)
|
||||
|
||||
.text
|
||||
.globl rseq_loop
|
||||
.type rseq_loop, @function
|
||||
|
||||
rseq_loop:
|
||||
jmp begin
|
||||
|
||||
// Abort block before the critical section.
|
||||
// Abort signature is 4 nops for simplicity.
|
||||
.byte 0x90, 0x90, 0x90, 0x90
|
||||
.globl rseq_loop_early_abort
|
||||
rseq_loop_early_abort:
|
||||
ret
|
||||
|
||||
begin:
|
||||
// r->rseq_cs = cs
|
||||
movq %rsi, 8(%rdi)
|
||||
|
||||
// N.B. rseq_cs will be cleared by any preempt, even outside the critical
|
||||
// section. Thus it must be set in or immediately before the critical section
|
||||
// to ensure it is not cleared before the section begins.
|
||||
.globl rseq_loop_start
|
||||
rseq_loop_start:
|
||||
jmp rseq_loop_start
|
||||
|
||||
// "Pre-commit": extra instructions inside the critical section. These are
|
||||
// used as the abort point in TestAbortPreCommit, which is not valid.
|
||||
.globl rseq_loop_pre_commit
|
||||
rseq_loop_pre_commit:
|
||||
// Extra abort signature + nop for TestAbortPostCommit.
|
||||
.byte 0x90, 0x90, 0x90, 0x90
|
||||
nop
|
||||
|
||||
// "Post-commit": never reached in this case.
|
||||
.globl rseq_loop_post_commit
|
||||
rseq_loop_post_commit:
|
||||
|
||||
// Abort signature is 4 nops for simplicity.
|
||||
.byte 0x90, 0x90, 0x90, 0x90
|
||||
|
||||
.globl rseq_loop_abort
|
||||
rseq_loop_abort:
|
||||
ret
|
||||
|
||||
.size rseq_loop,.-rseq_loop
|
||||
.section .note.GNU-stack,"",@progbits
|
|
@ -0,0 +1,39 @@
|
|||
// 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.
|
||||
|
||||
#ifndef GVISOR_TEST_SYSCALLS_LINUX_RSEQ_CRITICAL_H_
|
||||
#define GVISOR_TEST_SYSCALLS_LINUX_RSEQ_CRITICAL_H_
|
||||
|
||||
#include "test/syscalls/linux/rseq/types.h"
|
||||
#include "test/syscalls/linux/rseq/uapi.h"
|
||||
|
||||
constexpr uint32_t kRseqSignature = 0x90909090;
|
||||
|
||||
extern "C" {
|
||||
|
||||
extern void rseq_loop(struct rseq* r, struct rseq_cs* cs);
|
||||
extern void* rseq_loop_early_abort;
|
||||
extern void* rseq_loop_start;
|
||||
extern void* rseq_loop_pre_commit;
|
||||
extern void* rseq_loop_post_commit;
|
||||
extern void* rseq_loop_abort;
|
||||
|
||||
extern int rseq_getpid(struct rseq* r, struct rseq_cs* cs);
|
||||
extern void* rseq_getpid_start;
|
||||
extern void* rseq_getpid_post_commit;
|
||||
extern void* rseq_getpid_abort;
|
||||
|
||||
} // extern "C"
|
||||
|
||||
#endif // GVISOR_TEST_SYSCALLS_LINUX_RSEQ_CRITICAL_H_
|
|
@ -0,0 +1,366 @@
|
|||
// 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<char**>(&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<const unsigned char*>(s1);
|
||||
const unsigned char* p2 = reinterpret_cast<const unsigned char*>(s2);
|
||||
|
||||
while (*p1 == *p2) {
|
||||
if (!*p1) {
|
||||
return 0;
|
||||
}
|
||||
++p1;
|
||||
++p2;
|
||||
}
|
||||
return static_cast<int>(*p1) - static_cast<int>(*p2);
|
||||
}
|
||||
|
||||
int sys_rseq(struct rseq* rseq, uint32_t rseq_len, int flags, uint32_t 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<uintptr_t>(&buf[0]);
|
||||
if ((ptr & (kRequiredAlignment - 1)) == 0) {
|
||||
// buf is already aligned. Misalign it.
|
||||
ptr++;
|
||||
}
|
||||
|
||||
int ret = sys_rseq(reinterpret_cast<rseq*>(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<uint64_t>(&rseq_loop_start);
|
||||
cs.post_commit_offset = reinterpret_cast<uint64_t>(&rseq_loop_post_commit) -
|
||||
reinterpret_cast<uint64_t>(&rseq_loop_start);
|
||||
cs.abort_ip = reinterpret_cast<uint64_t>(&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<uint64_t>(&rseq_loop_start);
|
||||
cs.post_commit_offset = reinterpret_cast<uint64_t>(&rseq_loop_post_commit) -
|
||||
reinterpret_cast<uint64_t>(&rseq_loop_start);
|
||||
cs.abort_ip = reinterpret_cast<uint64_t>(&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<uint64_t>(&rseq_loop_start);
|
||||
cs.post_commit_offset = reinterpret_cast<uint64_t>(&rseq_loop_post_commit) -
|
||||
reinterpret_cast<uint64_t>(&rseq_loop_start);
|
||||
cs.abort_ip = reinterpret_cast<uint64_t>(&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<uint64_t>(&rseq_loop_start);
|
||||
cs.post_commit_offset = reinterpret_cast<uint64_t>(&rseq_loop_post_commit) -
|
||||
reinterpret_cast<uint64_t>(&rseq_loop_start);
|
||||
cs.abort_ip = reinterpret_cast<uint64_t>(&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<uint64_t>(&rseq_loop_start);
|
||||
cs.post_commit_offset = reinterpret_cast<uint64_t>(&rseq_loop_post_commit) -
|
||||
reinterpret_cast<uint64_t>(&rseq_loop_start);
|
||||
cs.abort_ip = reinterpret_cast<uint64_t>(&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<uint64_t>(&rseq_loop_start);
|
||||
cs.post_commit_offset = reinterpret_cast<uint64_t>(&rseq_loop_post_commit) -
|
||||
reinterpret_cast<uint64_t>(&rseq_loop_start);
|
||||
cs.abort_ip = reinterpret_cast<uint64_t>(&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 <test case>
|
||||
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
|
|
@ -0,0 +1,45 @@
|
|||
// 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.
|
||||
|
||||
|
||||
.text
|
||||
.align 4
|
||||
.type _start,@function
|
||||
.globl _start
|
||||
|
||||
_start:
|
||||
movq %rsp,%rdi
|
||||
call __init
|
||||
hlt
|
||||
|
||||
.size _start,.-_start
|
||||
.section .note.GNU-stack,"",@progbits
|
||||
|
||||
.text
|
||||
.globl raw_syscall
|
||||
.type raw_syscall, @function
|
||||
|
||||
raw_syscall:
|
||||
mov %rdi,%rax // syscall #
|
||||
mov %rsi,%rdi // arg0
|
||||
mov %rdx,%rsi // arg1
|
||||
mov %rcx,%rdx // arg2
|
||||
mov %r8,%r10 // arg3 (goes in r10 instead of rcx for system calls)
|
||||
mov %r9,%r8 // arg4
|
||||
mov 0x8(%rsp),%r9 // arg5
|
||||
syscall
|
||||
ret
|
||||
|
||||
.size raw_syscall,.-raw_syscall
|
||||
.section .note.GNU-stack,"",@progbits
|
|
@ -0,0 +1,66 @@
|
|||
// 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.
|
||||
|
||||
#ifndef GVISOR_TEST_SYSCALLS_LINUX_RSEQ_SYSCALLS_H_
|
||||
#define GVISOR_TEST_SYSCALLS_LINUX_RSEQ_SYSCALLS_H_
|
||||
|
||||
#include "test/syscalls/linux/rseq/types.h"
|
||||
|
||||
#ifdef __x86_64__
|
||||
// Syscall numbers.
|
||||
constexpr int kGetpid = 39;
|
||||
constexpr int kExitGroup = 231;
|
||||
#else
|
||||
#error "Unknown architecture"
|
||||
#endif
|
||||
|
||||
namespace gvisor {
|
||||
namespace testing {
|
||||
|
||||
// Standalone system call interfaces.
|
||||
// Note that these are all "raw" system call interfaces which encode
|
||||
// errors by setting the return value to a small negative number.
|
||||
// Use sys_errno() to check system call return values for errors.
|
||||
|
||||
// Maximum Linux error number.
|
||||
constexpr int kMaxErrno = 4095;
|
||||
|
||||
// Errno values.
|
||||
#define EPERM 1
|
||||
#define EFAULT 14
|
||||
#define EBUSY 16
|
||||
#define EINVAL 22
|
||||
|
||||
// Get the error number from a raw system call return value.
|
||||
// Returns a positive error number or 0 if there was no error.
|
||||
static inline int sys_errno(uintptr_t rval) {
|
||||
if (rval >= static_cast<uintptr_t>(-kMaxErrno)) {
|
||||
return -static_cast<int>(rval);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern "C" uintptr_t raw_syscall(int number, ...);
|
||||
|
||||
static inline void sys_exit_group(int status) {
|
||||
raw_syscall(kExitGroup, status);
|
||||
}
|
||||
static inline int sys_getpid() {
|
||||
return static_cast<int>(raw_syscall(kGetpid));
|
||||
}
|
||||
|
||||
} // namespace testing
|
||||
} // namespace gvisor
|
||||
|
||||
#endif // GVISOR_TEST_SYSCALLS_LINUX_RSEQ_SYSCALLS_H_
|
|
@ -0,0 +1,43 @@
|
|||
// 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.
|
||||
|
||||
#ifndef GVISOR_TEST_SYSCALLS_LINUX_RSEQ_TEST_H_
|
||||
#define GVISOR_TEST_SYSCALLS_LINUX_RSEQ_TEST_H_
|
||||
|
||||
namespace gvisor {
|
||||
namespace testing {
|
||||
|
||||
// Test cases supported by rseq binary.
|
||||
|
||||
inline constexpr char kRseqTestUnaligned[] = "unaligned";
|
||||
inline constexpr char kRseqTestRegister[] = "register";
|
||||
inline constexpr char kRseqTestDoubleRegister[] = "double-register";
|
||||
inline constexpr char kRseqTestRegisterUnregister[] = "register-unregister";
|
||||
inline constexpr char kRseqTestUnregisterDifferentPtr[] =
|
||||
"unregister-different-ptr";
|
||||
inline constexpr char kRseqTestUnregisterDifferentSignature[] =
|
||||
"unregister-different-signature";
|
||||
inline constexpr char kRseqTestCPU[] = "cpu";
|
||||
inline constexpr char kRseqTestAbort[] = "abort";
|
||||
inline constexpr char kRseqTestAbortBefore[] = "abort-before";
|
||||
inline constexpr char kRseqTestAbortSignature[] = "abort-signature";
|
||||
inline constexpr char kRseqTestAbortPreCommit[] = "abort-precommit";
|
||||
inline constexpr char kRseqTestAbortClearsCS[] = "abort-clears-cs";
|
||||
inline constexpr char kRseqTestInvalidAbortClearsCS[] =
|
||||
"invalid-abort-clears-cs";
|
||||
|
||||
} // namespace testing
|
||||
} // namespace gvisor
|
||||
|
||||
#endif // GVISOR_TEST_SYSCALLS_LINUX_RSEQ_TEST_H_
|
|
@ -0,0 +1,31 @@
|
|||
// 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.
|
||||
|
||||
#ifndef GVISOR_TEST_SYSCALLS_LINUX_RSEQ_TYPES_H_
|
||||
#define GVISOR_TEST_SYSCALLS_LINUX_RSEQ_TYPES_H_
|
||||
|
||||
using size_t = __SIZE_TYPE__;
|
||||
using uintptr_t = __UINTPTR_TYPE__;
|
||||
|
||||
using uint8_t = __UINT8_TYPE__;
|
||||
using uint16_t = __UINT16_TYPE__;
|
||||
using uint32_t = __UINT32_TYPE__;
|
||||
using uint64_t = __UINT64_TYPE__;
|
||||
|
||||
using int8_t = __INT8_TYPE__;
|
||||
using int16_t = __INT16_TYPE__;
|
||||
using int32_t = __INT32_TYPE__;
|
||||
using int64_t = __INT64_TYPE__;
|
||||
|
||||
#endif // GVISOR_TEST_SYSCALLS_LINUX_RSEQ_TYPES_H_
|
|
@ -0,0 +1,54 @@
|
|||
// 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.
|
||||
|
||||
#ifndef GVISOR_TEST_SYSCALLS_LINUX_RSEQ_UAPI_H_
|
||||
#define GVISOR_TEST_SYSCALLS_LINUX_RSEQ_UAPI_H_
|
||||
|
||||
// User-kernel ABI for restartable sequences.
|
||||
|
||||
// Standard types.
|
||||
//
|
||||
// N.B. This header will be included in targets that do have the standard
|
||||
// library, so we can't shadow the standard type names.
|
||||
using __u32 = __UINT32_TYPE__;
|
||||
using __u64 = __UINT64_TYPE__;
|
||||
|
||||
#ifdef __x86_64__
|
||||
// Syscall numbers.
|
||||
constexpr int kRseqSyscall = 334;
|
||||
#else
|
||||
#error "Unknown architecture"
|
||||
#endif // __x86_64__
|
||||
|
||||
struct rseq_cs {
|
||||
__u32 version;
|
||||
__u32 flags;
|
||||
__u64 start_ip;
|
||||
__u64 post_commit_offset;
|
||||
__u64 abort_ip;
|
||||
} __attribute__((aligned(4 * sizeof(__u64))));
|
||||
|
||||
// N.B. alignment is enforced by the kernel.
|
||||
struct rseq {
|
||||
__u32 cpu_id_start;
|
||||
__u32 cpu_id;
|
||||
struct rseq_cs* rseq_cs;
|
||||
__u32 flags;
|
||||
} __attribute__((aligned(4 * sizeof(__u64))));
|
||||
|
||||
constexpr int kRseqFlagUnregister = 1 << 0;
|
||||
|
||||
constexpr int kRseqCPUIDUninitialized = -1;
|
||||
|
||||
#endif // GVISOR_TEST_SYSCALLS_LINUX_RSEQ_UAPI_H_
|
Loading…
Reference in New Issue