mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Open source system call tests.
PiperOrigin-RevId: 224886231 Change-Id: I0fccb4d994601739d8b16b1d4e6b31f40297fb22
This commit is contained in:
+6
-3
@@ -28,8 +28,8 @@ bazel version
|
||||
|
||||
cd git/repo
|
||||
|
||||
# Build everything.
|
||||
bazel build //...
|
||||
# Build everything except //test.
|
||||
bazel build //pkg/... //runsc/... //tools/...
|
||||
|
||||
# Test use this variable to determine what runtime to use.
|
||||
runtime=runsc_test_$((RANDOM))
|
||||
@@ -45,7 +45,10 @@ uninstallRuntime() {
|
||||
# We turn off "-e" flag because we must move the log files even if the test
|
||||
# fails.
|
||||
set +e
|
||||
bazel test --test_output=errors //...
|
||||
|
||||
# Note: We do not run the tests in the //test folder as these would take
|
||||
# too long.
|
||||
bazel test --test_output=errors //pkg/... //runsc/... //tools/...
|
||||
exit_code=${?}
|
||||
|
||||
# This function spawns a subshell to install crictl and containerd.
|
||||
|
||||
@@ -21,6 +21,7 @@ go_library(
|
||||
importpath = "gvisor.googlesource.com/gvisor/runsc/boot",
|
||||
visibility = [
|
||||
"//runsc:__subpackages__",
|
||||
"//test:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
"//pkg/abi",
|
||||
|
||||
@@ -13,6 +13,7 @@ go_library(
|
||||
importpath = "gvisor.googlesource.com/gvisor/runsc/container",
|
||||
visibility = [
|
||||
"//runsc:__subpackages__",
|
||||
"//test:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
"//pkg/log",
|
||||
|
||||
@@ -11,6 +11,7 @@ go_library(
|
||||
importpath = "gvisor.googlesource.com/gvisor/runsc/specutils",
|
||||
visibility = [
|
||||
"//runsc:__subpackages__",
|
||||
"//test:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
"//pkg/abi/linux",
|
||||
|
||||
@@ -13,6 +13,7 @@ go_library(
|
||||
importpath = "gvisor.googlesource.com/gvisor/runsc/test/testutil",
|
||||
visibility = [
|
||||
"//runsc:__subpackages__",
|
||||
"//test:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
"//runsc/boot",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,110 @@
|
||||
# gVisor system call test suite
|
||||
|
||||
This is a test suite for Linux system calls. It runs under both gVisor and
|
||||
Linux, and ensures compatability between the two.
|
||||
|
||||
When adding support for a new syscall (or syscall argument) to gVisor, a
|
||||
corresponding syscall test should be added. It's usually recommended to write
|
||||
the test first and make sure that it passes on Linux before making changes to
|
||||
gVisor.
|
||||
|
||||
This document outlines the general guidelines for tests and specific rules that
|
||||
must be followed for new tests.
|
||||
|
||||
## Running the tests
|
||||
|
||||
Each test file generates three different test targets that run in different
|
||||
environments:
|
||||
|
||||
* a `native` target that runs directly on the host machine,
|
||||
* a `runsc_ptrace` target that runs inside runsc using the ptrace platform, and
|
||||
* a `runsc_kvm` target that runs inside runsc using the KVM platform.
|
||||
|
||||
For example, the test in `access_test.cc` generates the following targets:
|
||||
|
||||
* `//test/syscalls:access_test_native`
|
||||
* `//test/syscalls:access_test_runsc_ptrace`
|
||||
* `//test/syscalls:access_test_runsc_kvm`
|
||||
|
||||
Any of these targets can be run directly via `bazel test`.
|
||||
|
||||
```bash
|
||||
$ bazel test //test/syscalls:access_test_native
|
||||
$ bazel test //test/syscalls:access_test_runsc_ptrace
|
||||
$ bazel test //test/syscalls:access_test_runsc_kvm
|
||||
```
|
||||
|
||||
To run all the tests on a particular platform, you can filter by the platform
|
||||
tag:
|
||||
|
||||
```bash
|
||||
# Run all tests in native environment:
|
||||
$ bazel test --test_tag_filter=native //test/syscalls:*
|
||||
|
||||
# Run all tests in runsc with ptrace:
|
||||
$ bazel test --test_tag_filter=runsc_ptrace //test/syscalls:*
|
||||
|
||||
# Run all tests in runsc with kvm:
|
||||
$ bazel test --test_tag_filter=runsc_kvm //test/syscalls:*
|
||||
```
|
||||
|
||||
You can also run all the tests on every platform. (Warning, this may take a
|
||||
while to run.)
|
||||
|
||||
```bash
|
||||
# Run all tests on every platform:
|
||||
$ bazel test //test/syscalls:*
|
||||
```
|
||||
|
||||
## Writing new tests
|
||||
|
||||
Whenever we add support for a new syscall, or add support for a new argument or
|
||||
option for a syscall, we should always add a new test (perhaps many new tests).
|
||||
|
||||
In general, it is best to write the test first and make sure it passes on Linux
|
||||
by running the test on the `native` platform on a Linux machine. This ensures
|
||||
that the gVisor implementation matches actual Linux behavior. Sometimes man
|
||||
pages contain errors, so always check the actual Linux behavior.
|
||||
|
||||
gVisor uses the [Google Test][googletest] test framework, with a few custom
|
||||
matchers and guidelines, described below.
|
||||
|
||||
### Syscall matchers
|
||||
|
||||
When testing an individual system call, use the following syscall matchers,
|
||||
which will match the value returned by the syscall and the errno.
|
||||
|
||||
```cc
|
||||
SyscallSucceeds()
|
||||
SyscallSucceedsWithValue(...)
|
||||
SyscallFails()
|
||||
SyscallFailsWithErrno(...)
|
||||
```
|
||||
|
||||
### Use test utilities (RAII classes)
|
||||
|
||||
The test utilties are written as RAII classes. These utilities should be
|
||||
preferred over custom test harnesses.
|
||||
|
||||
Local class instances should be preferred, whereever possible, over full test
|
||||
fixtures.
|
||||
|
||||
A test utility should be created when there is more than one test that requires
|
||||
that same functionality, otherwise the class should be test local.
|
||||
|
||||
|
||||
## Save/Restore support in tests
|
||||
gVisor supports save/restore, and our syscall tests are written in a way to
|
||||
enable saving/restoring at certain points. Hence, there are calls to
|
||||
`MaybeSave`, and certain tests that should not trigger saves are named with
|
||||
`NoSave`.
|
||||
|
||||
However, the current open-source test runner does not yet support triggering
|
||||
save/restore, so these functions and annotations have no effect on the
|
||||
open-source tests.
|
||||
|
||||
We plan on extending our open-source test runner to trigger save/restore. Until
|
||||
then, these functions and annotations should be ignored.
|
||||
|
||||
|
||||
[googletest]: https://github.com/abseil/googletest
|
||||
@@ -0,0 +1,54 @@
|
||||
"""Defines a rule for syscall test targets."""
|
||||
|
||||
# syscall_test is a macro that will create targets to run the given test target
|
||||
# on the host (native) and runsc.
|
||||
def syscall_test(test, size = "small"):
|
||||
_syscall_test(test, size, "native")
|
||||
_syscall_test(test, size, "kvm")
|
||||
_syscall_test(test, size, "ptrace")
|
||||
|
||||
def _syscall_test(test, size, platform):
|
||||
test_name = test.split(":")[1]
|
||||
|
||||
# Prepend "runsc" to non-native platform names.
|
||||
full_platform = platform if platform == "native" else "runsc_" + platform
|
||||
|
||||
# Add the full_platform in a tag to make it easier to run all the tests on
|
||||
# a specific platform.
|
||||
tags = [full_platform]
|
||||
|
||||
# Add tag to prevent the tests from running in a Bazel sandbox.
|
||||
# TODO: Make the tests run without this tag.
|
||||
tags.append("no-sandbox")
|
||||
|
||||
# TODO: KVM tests are tagged "manual" to until the platform is
|
||||
# more stable.
|
||||
if platform == "kvm":
|
||||
tags += ["manual"]
|
||||
|
||||
sh_test(
|
||||
srcs = ["syscall_test_runner.sh"],
|
||||
name = test_name + "_" + full_platform,
|
||||
data = [
|
||||
":syscall_test",
|
||||
test,
|
||||
],
|
||||
args = [
|
||||
# First argument is location to syscall_test binary.
|
||||
"$(location :syscall_test)",
|
||||
# Rest of arguments are passed directly to syscall_test binary.
|
||||
"--test-name=" + test_name,
|
||||
"--platform=" + platform,
|
||||
"--debug=false",
|
||||
"--strace=false",
|
||||
"--parallel=true",
|
||||
],
|
||||
size = size,
|
||||
tags = tags,
|
||||
)
|
||||
|
||||
def sh_test(**kwargs):
|
||||
"""Wraps the standard sh_test."""
|
||||
native.sh_test(
|
||||
**kwargs
|
||||
)
|
||||
@@ -0,0 +1,12 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||
|
||||
package(licenses = ["notice"]) # Apache 2.0
|
||||
|
||||
go_library(
|
||||
name = "gtest",
|
||||
srcs = ["gtest.go"],
|
||||
importpath = "gvisor.googlesource.com/gvisor/test/syscalls/gtest",
|
||||
visibility = [
|
||||
"//test:__subpackages__",
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,93 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// 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.
|
||||
|
||||
// Package gtest contains helpers for running google-test tests from Go.
|
||||
package gtest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
// ListTestFlag is the flag that will list tests in gtest binaries.
|
||||
ListTestFlag = "--gtest_list_tests"
|
||||
|
||||
// FilterTestFlag is the flag that will filter tests in gtest binaries.
|
||||
FilterTestFlag = "--gtest_filter"
|
||||
)
|
||||
|
||||
// TestCase is a single gtest test case.
|
||||
type TestCase struct {
|
||||
// Suite is the suite for this test.
|
||||
Suite string
|
||||
|
||||
// Name is the name of this individual test.
|
||||
Name string
|
||||
}
|
||||
|
||||
// FullName returns the name of the test including the suite. It is suitable to
|
||||
// pass to "-gtest_filter".
|
||||
func (tc TestCase) FullName() string {
|
||||
return fmt.Sprintf("%s.%s", tc.Suite, tc.Name)
|
||||
}
|
||||
|
||||
// ParseTestCases calls a gtest test binary to list its test and returns a
|
||||
// slice with the name and suite of each test.
|
||||
func ParseTestCases(testBin string, extraArgs ...string) ([]TestCase, error) {
|
||||
args := append([]string{ListTestFlag}, extraArgs...)
|
||||
cmd := exec.Command(testBin, args...)
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
exitErr, ok := err.(*exec.ExitError)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("could not enumerate gtest tests: %v", err)
|
||||
}
|
||||
return nil, fmt.Errorf("could not enumerate gtest tests: %v\nstderr:\n%s", err, exitErr.Stderr)
|
||||
}
|
||||
|
||||
var t []TestCase
|
||||
var suite string
|
||||
for _, line := range strings.Split(string(out), "\n") {
|
||||
// Strip comments.
|
||||
line = strings.Split(line, "#")[0]
|
||||
|
||||
// New suite?
|
||||
if !strings.HasPrefix(line, " ") {
|
||||
suite = strings.TrimSuffix(strings.TrimSpace(line), ".")
|
||||
continue
|
||||
}
|
||||
|
||||
// Individual test.
|
||||
name := strings.TrimSpace(line)
|
||||
|
||||
// Do we have a suite yet?
|
||||
if suite == "" {
|
||||
return nil, fmt.Errorf("test without a suite: %v", name)
|
||||
}
|
||||
|
||||
// Add this individual test.
|
||||
t = append(t, TestCase{
|
||||
Suite: suite,
|
||||
Name: name,
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
if len(t) == 0 {
|
||||
return nil, fmt.Errorf("no tests parsed from %v", testBin)
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// 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 <string.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
#include "test/util/memory_util.h"
|
||||
#include "test/util/posix_error.h"
|
||||
#include "test/util/test_util.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#ifndef __x86_64__
|
||||
#error "This test is x86-64 specific."
|
||||
#endif
|
||||
|
||||
namespace gvisor {
|
||||
namespace testing {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr char kInt3 = '\xcc';
|
||||
|
||||
constexpr char kInt80[2] = {'\xcd', '\x80'};
|
||||
constexpr char kSyscall[2] = {'\x0f', '\x05'};
|
||||
constexpr char kSysenter[2] = {'\x0f', '\x34'};
|
||||
|
||||
void ExitGroup32(const char instruction[2], int code) {
|
||||
const Mapping m = ASSERT_NO_ERRNO_AND_VALUE(
|
||||
Mmap(nullptr, kPageSize, PROT_READ | PROT_WRITE | PROT_EXEC,
|
||||
MAP_PRIVATE | MAP_ANONYMOUS | MAP_32BIT, -1, 0));
|
||||
|
||||
// Fill with INT 3 in case we execute too far.
|
||||
memset(m.ptr(), kInt3, m.len());
|
||||
|
||||
memcpy(m.ptr(), instruction, 2);
|
||||
|
||||
// We're playing *extremely* fast-and-loose with the various syscall ABIs
|
||||
// here, which we can more-or-less get away with since exit_group doesn't
|
||||
// return.
|
||||
//
|
||||
// SYSENTER expects the user stack in (%ebp) and arg6 in 0(%ebp). The kernel
|
||||
// will unconditionally dereference %ebp for arg6, so we must pass a valid
|
||||
// address or it will return EFAULT.
|
||||
//
|
||||
// SYSENTER also unconditionally returns to thread_info->sysenter_return which
|
||||
// is ostensibly a stub in the 32-bit VDSO. But a 64-bit binary doesn't have
|
||||
// the 32-bit VDSO mapped, so sysenter_return will simply be the value
|
||||
// inherited from the most recent 32-bit ancestor, or NULL if there is none.
|
||||
// As a result, return would not return from SYSENTER.
|
||||
asm volatile(
|
||||
"movl $252, %%eax\n" // exit_group
|
||||
"movl %[code], %%ebx\n" // code
|
||||
"movl %%edx, %%ebp\n" // SYSENTER: user stack (use IP as a valid addr)
|
||||
"leaq -20(%%rsp), %%rsp\n"
|
||||
"movl $0x2b, 16(%%rsp)\n" // SS = CPL3 data segment
|
||||
"movl $0,12(%%rsp)\n" // ESP = nullptr (unused)
|
||||
"movl $0, 8(%%rsp)\n" // EFLAGS
|
||||
"movl $0x23, 4(%%rsp)\n" // CS = CPL3 32-bit code segment
|
||||
"movl %%edx, 0(%%rsp)\n" // EIP
|
||||
"iretl\n"
|
||||
"int $3\n"
|
||||
:
|
||||
: [code] "m"(code), [ip] "d"(m.ptr())
|
||||
: "rax", "rbx", "rsp");
|
||||
}
|
||||
|
||||
constexpr int kExitCode = 42;
|
||||
|
||||
TEST(Syscall32Bit, Int80) {
|
||||
switch (GvisorPlatform()) {
|
||||
case Platform::kKVM:
|
||||
// TODO: 32-bit segments are broken (but not explictly
|
||||
// disabled).
|
||||
return;
|
||||
case Platform::kPtrace:
|
||||
// TODO: The ptrace platform does not have a consistent story
|
||||
// here.
|
||||
return;
|
||||
case Platform::kNative:
|
||||
break;
|
||||
}
|
||||
|
||||
// Upstream Linux. 32-bit syscalls allowed.
|
||||
EXPECT_EXIT(ExitGroup32(kInt80, kExitCode), ::testing::ExitedWithCode(42),
|
||||
"");
|
||||
}
|
||||
|
||||
TEST(Syscall32Bit, Sysenter) {
|
||||
switch (GvisorPlatform()) {
|
||||
case Platform::kKVM:
|
||||
// TODO: See above.
|
||||
return;
|
||||
case Platform::kPtrace:
|
||||
// TODO: See above.
|
||||
return;
|
||||
case Platform::kNative:
|
||||
break;
|
||||
}
|
||||
|
||||
if (GetCPUVendor() == CPUVendor::kAMD) {
|
||||
// SYSENTER is an illegal instruction in compatibility mode on AMD.
|
||||
EXPECT_EXIT(ExitGroup32(kSysenter, kExitCode),
|
||||
::testing::KilledBySignal(SIGILL), "");
|
||||
return;
|
||||
}
|
||||
|
||||
// Upstream Linux on !AMD, 32-bit syscalls allowed.
|
||||
EXPECT_EXIT(ExitGroup32(kSysenter, kExitCode), ::testing::ExitedWithCode(42),
|
||||
"");
|
||||
}
|
||||
|
||||
TEST(Syscall32Bit, Syscall) {
|
||||
switch (GvisorPlatform()) {
|
||||
case Platform::kKVM:
|
||||
// TODO: See above.
|
||||
return;
|
||||
case Platform::kPtrace:
|
||||
// TODO: See above.
|
||||
return;
|
||||
case Platform::kNative:
|
||||
break;
|
||||
}
|
||||
|
||||
if (GetCPUVendor() == CPUVendor::kIntel) {
|
||||
// SYSCALL is an illegal instruction in compatibility mode on Intel.
|
||||
EXPECT_EXIT(ExitGroup32(kSyscall, kExitCode),
|
||||
::testing::KilledBySignal(SIGILL), "");
|
||||
return;
|
||||
}
|
||||
|
||||
// Upstream Linux on !Intel, 32-bit syscalls allowed.
|
||||
EXPECT_EXIT(ExitGroup32(kSyscall, kExitCode), ::testing::ExitedWithCode(42),
|
||||
"");
|
||||
}
|
||||
|
||||
// Far call code called below.
|
||||
//
|
||||
// Input stack layout:
|
||||
//
|
||||
// %esp+12 lcall segment
|
||||
// %esp+8 lcall address offset
|
||||
// %esp+0 return address
|
||||
//
|
||||
// The lcall will enter compatibility mode and jump to the call address (the
|
||||
// address of the lret). The lret will return to 64-bit mode at the retq, which
|
||||
// will return to the external caller of this function.
|
||||
//
|
||||
// Since this enters compatibility mode, it must be mapped in a 32-bit region of
|
||||
// address space and have a 32-bit stack pointer.
|
||||
constexpr char kFarCall[] = {
|
||||
'\x67', '\xff', '\x5c', '\x24', '\x08', // lcall *8(%esp)
|
||||
'\xc3', // retq
|
||||
'\xcb', // lret
|
||||
};
|
||||
|
||||
void FarCall32() {
|
||||
const Mapping m = ASSERT_NO_ERRNO_AND_VALUE(
|
||||
Mmap(nullptr, kPageSize, PROT_READ | PROT_WRITE | PROT_EXEC,
|
||||
MAP_PRIVATE | MAP_ANONYMOUS | MAP_32BIT, -1, 0));
|
||||
|
||||
// Fill with INT 3 in case we execute too far.
|
||||
memset(m.ptr(), kInt3, m.len());
|
||||
|
||||
// 32-bit code.
|
||||
memcpy(m.ptr(), kFarCall, sizeof(kFarCall));
|
||||
|
||||
// Use the end of the code page as its stack.
|
||||
uintptr_t stack = m.endaddr();
|
||||
|
||||
uintptr_t lcall = m.addr();
|
||||
uintptr_t lret = m.addr() + sizeof(kFarCall) - 1;
|
||||
|
||||
// N.B. We must save and restore RSP manually. GCC can do so automatically
|
||||
// with an "rsp" clobber, but clang cannot.
|
||||
asm volatile(
|
||||
// Place the address of lret (%edx) and the 32-bit code segment (0x23) on
|
||||
// the 32-bit stack for lcall.
|
||||
"subl $0x8, %%ecx\n"
|
||||
"movl $0x23, 4(%%ecx)\n"
|
||||
"movl %%edx, 0(%%ecx)\n"
|
||||
|
||||
// Save the current stack and switch to 32-bit stack.
|
||||
"pushq %%rbp\n"
|
||||
"movq %%rsp, %%rbp\n"
|
||||
"movq %%rcx, %%rsp\n"
|
||||
|
||||
// Run the lcall code.
|
||||
"callq *%%rbx\n"
|
||||
|
||||
// Restore the old stack.
|
||||
"leaveq\n"
|
||||
: "+c"(stack)
|
||||
: "b"(lcall), "d"(lret));
|
||||
}
|
||||
|
||||
TEST(Call32Bit, Disallowed) {
|
||||
switch (GvisorPlatform()) {
|
||||
case Platform::kKVM:
|
||||
// TODO: See above.
|
||||
return;
|
||||
case Platform::kPtrace:
|
||||
// The ptrace platform cannot prevent switching to compatibility mode.
|
||||
ABSL_FALLTHROUGH_INTENDED;
|
||||
case Platform::kNative:
|
||||
break;
|
||||
}
|
||||
|
||||
// Shouldn't crash.
|
||||
FarCall32();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace testing
|
||||
} // namespace gvisor
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,93 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// 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 <stdio.h>
|
||||
#include <sys/un.h>
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
#include "gtest/gtest.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "test/syscalls/linux/socket_test_util.h"
|
||||
#include "test/syscalls/linux/unix_domain_socket_test_util.h"
|
||||
#include "test/util/file_descriptor.h"
|
||||
#include "test/util/test_util.h"
|
||||
|
||||
namespace gvisor {
|
||||
namespace testing {
|
||||
|
||||
namespace {
|
||||
|
||||
TEST_P(AllSocketPairTest, BoundSenderAddrCoalesced) {
|
||||
auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
|
||||
|
||||
ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(),
|
||||
sockets->first_addr_size()),
|
||||
SyscallSucceeds());
|
||||
|
||||
ASSERT_THAT(listen(sockets->first_fd(), 5), SyscallSucceeds());
|
||||
|
||||
ASSERT_THAT(connect(sockets->second_fd(), sockets->first_addr(),
|
||||
sockets->first_addr_size()),
|
||||
SyscallSucceeds());
|
||||
|
||||
int accepted = -1;
|
||||
ASSERT_THAT(accepted = accept(sockets->first_fd(), nullptr, nullptr),
|
||||
SyscallSucceeds());
|
||||
FileDescriptor closer(accepted);
|
||||
|
||||
int i = 0;
|
||||
ASSERT_THAT(RetryEINTR(send)(sockets->second_fd(), &i, sizeof(i), 0),
|
||||
SyscallSucceedsWithValue(sizeof(i)));
|
||||
|
||||
ASSERT_THAT(bind(sockets->second_fd(), sockets->second_addr(),
|
||||
sockets->second_addr_size()),
|
||||
SyscallSucceeds());
|
||||
|
||||
i = 0;
|
||||
ASSERT_THAT(RetryEINTR(send)(sockets->second_fd(), &i, sizeof(i), 0),
|
||||
SyscallSucceedsWithValue(sizeof(i)));
|
||||
|
||||
int ri[2] = {0, 0};
|
||||
struct sockaddr_storage addr;
|
||||
socklen_t addr_len = sizeof(addr);
|
||||
ASSERT_THAT(
|
||||
RetryEINTR(recvfrom)(accepted, ri, sizeof(ri), 0,
|
||||
reinterpret_cast<sockaddr*>(&addr), &addr_len),
|
||||
SyscallSucceedsWithValue(sizeof(ri)));
|
||||
EXPECT_EQ(addr_len, sockets->second_addr_len());
|
||||
|
||||
EXPECT_EQ(
|
||||
memcmp(&addr, sockets->second_addr(),
|
||||
std::min((size_t)addr_len, (size_t)sockets->second_addr_len())),
|
||||
0);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
AllUnixDomainSockets, AllSocketPairTest,
|
||||
::testing::ValuesIn(VecCat<SocketPairKind>(
|
||||
ApplyVec<SocketPairKind>(
|
||||
FilesystemUnboundUnixDomainSocketPair,
|
||||
AllBitwiseCombinations(List<int>{SOCK_STREAM},
|
||||
List<int>{0, SOCK_NONBLOCK},
|
||||
List<int>{0, SOCK_CLOEXEC})),
|
||||
ApplyVec<SocketPairKind>(
|
||||
AbstractUnboundUnixDomainSocketPair,
|
||||
AllBitwiseCombinations(List<int>{SOCK_STREAM},
|
||||
List<int>{0, SOCK_NONBLOCK},
|
||||
List<int>{0, SOCK_CLOEXEC})))));
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace testing
|
||||
} // namespace gvisor
|
||||
@@ -0,0 +1,170 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// 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 <fcntl.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "test/util/capability_util.h"
|
||||
#include "test/util/fs_util.h"
|
||||
#include "test/util/temp_path.h"
|
||||
#include "test/util/test_util.h"
|
||||
|
||||
using ::testing::Ge;
|
||||
|
||||
namespace gvisor {
|
||||
namespace testing {
|
||||
|
||||
namespace {
|
||||
|
||||
class AccessTest : public ::testing::Test {
|
||||
public:
|
||||
std::string CreateTempFile(int perm) {
|
||||
const std::string path = NewTempAbsPath();
|
||||
const int fd = open(path.c_str(), O_CREAT | O_RDONLY, perm);
|
||||
TEST_PCHECK(fd > 0);
|
||||
TEST_PCHECK(close(fd) == 0);
|
||||
return path;
|
||||
}
|
||||
|
||||
protected:
|
||||
// SetUp creates various configurations of files.
|
||||
void SetUp() override {
|
||||
// Move to the temporary directory. This allows us to reason more easily
|
||||
// about absolute and relative paths.
|
||||
ASSERT_THAT(chdir(GetAbsoluteTestTmpdir().c_str()), SyscallSucceeds());
|
||||
|
||||
// Create an empty file, standard permissions.
|
||||
relfile_ = NewTempRelPath();
|
||||
int fd;
|
||||
ASSERT_THAT(fd = open(relfile_.c_str(), O_CREAT | O_TRUNC, 0644),
|
||||
SyscallSucceedsWithValue(Ge(0)));
|
||||
ASSERT_THAT(close(fd), SyscallSucceeds());
|
||||
absfile_ = GetAbsoluteTestTmpdir() + "/" + relfile_;
|
||||
|
||||
// Create an empty directory, no writable permissions.
|
||||
absdir_ = NewTempAbsPath();
|
||||
reldir_ = JoinPath(Basename(absdir_), "");
|
||||
ASSERT_THAT(mkdir(reldir_.c_str(), 0555), SyscallSucceeds());
|
||||
|
||||
// This file doesn't exist.
|
||||
relnone_ = NewTempRelPath();
|
||||
absnone_ = GetAbsoluteTestTmpdir() + "/" + relnone_;
|
||||
}
|
||||
|
||||
// TearDown unlinks created files.
|
||||
void TearDown() override {
|
||||
ASSERT_THAT(unlink(absfile_.c_str()), SyscallSucceeds());
|
||||
ASSERT_THAT(rmdir(absdir_.c_str()), SyscallSucceeds());
|
||||
}
|
||||
|
||||
std::string relfile_;
|
||||
std::string reldir_;
|
||||
|
||||
std::string absfile_;
|
||||
std::string absdir_;
|
||||
|
||||
std::string relnone_;
|
||||
std::string absnone_;
|
||||
};
|
||||
|
||||
TEST_F(AccessTest, RelativeFile) {
|
||||
EXPECT_THAT(access(relfile_.c_str(), R_OK), SyscallSucceeds());
|
||||
}
|
||||
|
||||
TEST_F(AccessTest, RelativeDir) {
|
||||
EXPECT_THAT(access(reldir_.c_str(), R_OK | X_OK), SyscallSucceeds());
|
||||
}
|
||||
|
||||
TEST_F(AccessTest, AbsFile) {
|
||||
EXPECT_THAT(access(absfile_.c_str(), R_OK), SyscallSucceeds());
|
||||
}
|
||||
|
||||
TEST_F(AccessTest, AbsDir) {
|
||||
EXPECT_THAT(access(absdir_.c_str(), R_OK | X_OK), SyscallSucceeds());
|
||||
}
|
||||
|
||||
TEST_F(AccessTest, RelDoesNotExist) {
|
||||
EXPECT_THAT(access(relnone_.c_str(), R_OK), SyscallFailsWithErrno(ENOENT));
|
||||
}
|
||||
|
||||
TEST_F(AccessTest, AbsDoesNotExist) {
|
||||
EXPECT_THAT(access(absnone_.c_str(), R_OK), SyscallFailsWithErrno(ENOENT));
|
||||
}
|
||||
|
||||
TEST_F(AccessTest, InvalidMode) {
|
||||
EXPECT_THAT(access(relfile_.c_str(), 0xffffffff),
|
||||
SyscallFailsWithErrno(EINVAL));
|
||||
}
|
||||
|
||||
TEST_F(AccessTest, NoPerms) {
|
||||
// Drop capabilities that allow us to override permissions. We must drop
|
||||
// PERMITTED because access() checks those instead of EFFECTIVE.
|
||||
ASSERT_NO_ERRNO(DropPermittedCapability(CAP_DAC_OVERRIDE));
|
||||
ASSERT_NO_ERRNO(DropPermittedCapability(CAP_DAC_READ_SEARCH));
|
||||
|
||||
EXPECT_THAT(access(absdir_.c_str(), W_OK), SyscallFailsWithErrno(EACCES));
|
||||
}
|
||||
|
||||
TEST_F(AccessTest, InvalidName) {
|
||||
EXPECT_THAT(access(reinterpret_cast<char*>(0x1234), W_OK),
|
||||
SyscallFailsWithErrno(EFAULT));
|
||||
}
|
||||
|
||||
TEST_F(AccessTest, UsrReadOnly) {
|
||||
// Drop capabilities that allow us to override permissions. We must drop
|
||||
// PERMITTED because access() checks those instead of EFFECTIVE.
|
||||
ASSERT_NO_ERRNO(DropPermittedCapability(CAP_DAC_OVERRIDE));
|
||||
ASSERT_NO_ERRNO(DropPermittedCapability(CAP_DAC_READ_SEARCH));
|
||||
|
||||
const std::string filename = CreateTempFile(0400);
|
||||
EXPECT_THAT(access(filename.c_str(), R_OK), SyscallSucceeds());
|
||||
EXPECT_THAT(access(filename.c_str(), W_OK), SyscallFailsWithErrno(EACCES));
|
||||
EXPECT_THAT(access(filename.c_str(), X_OK), SyscallFailsWithErrno(EACCES));
|
||||
EXPECT_THAT(unlink(filename.c_str()), SyscallSucceeds());
|
||||
}
|
||||
|
||||
TEST_F(AccessTest, UsrReadExec) {
|
||||
// Drop capabilities that allow us to override permissions. We must drop
|
||||
// PERMITTED because access() checks those instead of EFFECTIVE.
|
||||
ASSERT_NO_ERRNO(DropPermittedCapability(CAP_DAC_OVERRIDE));
|
||||
ASSERT_NO_ERRNO(DropPermittedCapability(CAP_DAC_READ_SEARCH));
|
||||
|
||||
const std::string filename = CreateTempFile(0500);
|
||||
EXPECT_THAT(access(filename.c_str(), R_OK | X_OK), SyscallSucceeds());
|
||||
EXPECT_THAT(access(filename.c_str(), W_OK), SyscallFailsWithErrno(EACCES));
|
||||
EXPECT_THAT(unlink(filename.c_str()), SyscallSucceeds());
|
||||
}
|
||||
|
||||
TEST_F(AccessTest, UsrReadWrite) {
|
||||
const std::string filename = CreateTempFile(0600);
|
||||
EXPECT_THAT(access(filename.c_str(), R_OK | W_OK), SyscallSucceeds());
|
||||
EXPECT_THAT(access(filename.c_str(), X_OK), SyscallFailsWithErrno(EACCES));
|
||||
EXPECT_THAT(unlink(filename.c_str()), SyscallSucceeds());
|
||||
}
|
||||
|
||||
TEST_F(AccessTest, UsrReadWriteExec) {
|
||||
const std::string filename = CreateTempFile(0700);
|
||||
EXPECT_THAT(access(filename.c_str(), R_OK | W_OK | X_OK), SyscallSucceeds());
|
||||
EXPECT_THAT(unlink(filename.c_str()), SyscallSucceeds());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace testing
|
||||
} // namespace gvisor
|
||||
@@ -0,0 +1,241 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// 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 <sched.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "absl/strings/str_split.h"
|
||||
#include "test/util/cleanup.h"
|
||||
#include "test/util/fs_util.h"
|
||||
#include "test/util/posix_error.h"
|
||||
#include "test/util/test_util.h"
|
||||
#include "test/util/thread_util.h"
|
||||
|
||||
namespace gvisor {
|
||||
namespace testing {
|
||||
namespace {
|
||||
|
||||
// These tests are for both the sched_getaffinity(2) and sched_setaffinity(2)
|
||||
// syscalls.
|
||||
class AffinityTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
EXPECT_THAT(
|
||||
// Needs use the raw syscall to get the actual size.
|
||||
cpuset_size_ = syscall(SYS_sched_getaffinity, /*pid=*/0,
|
||||
sizeof(cpu_set_t), &mask_),
|
||||
SyscallSucceeds());
|
||||
// Lots of tests rely on having more than 1 logical processor available.
|
||||
EXPECT_GT(CPU_COUNT(&mask_), 1);
|
||||
}
|
||||
|
||||
static PosixError ClearLowestBit(cpu_set_t* mask, size_t cpus) {
|
||||
const size_t mask_size = CPU_ALLOC_SIZE(cpus);
|
||||
for (size_t n = 0; n < cpus; ++n) {
|
||||
if (CPU_ISSET_S(n, mask_size, mask)) {
|
||||
CPU_CLR_S(n, mask_size, mask);
|
||||
return NoError();
|
||||
}
|
||||
}
|
||||
return PosixError(EINVAL, "No bit to clear, mask is empty");
|
||||
}
|
||||
|
||||
PosixError ClearLowestBit() { return ClearLowestBit(&mask_, CPU_SETSIZE); }
|
||||
|
||||
// Stores the initial cpu mask for this process.
|
||||
cpu_set_t mask_ = {};
|
||||
int cpuset_size_ = 0;
|
||||
};
|
||||
|
||||
// sched_getaffinity(2) is implemented.
|
||||
TEST_F(AffinityTest, SchedGetAffinityImplemented) {
|
||||
EXPECT_THAT(sched_getaffinity(/*pid=*/0, sizeof(cpu_set_t), &mask_),
|
||||
SyscallSucceeds());
|
||||
}
|
||||
|
||||
// PID is not found.
|
||||
TEST_F(AffinityTest, SchedGetAffinityInvalidPID) {
|
||||
// Flaky, but it's tough to avoid a race condition when finding an unused pid
|
||||
EXPECT_THAT(sched_getaffinity(/*pid=*/INT_MAX - 1, sizeof(cpu_set_t), &mask_),
|
||||
SyscallFailsWithErrno(ESRCH));
|
||||
}
|
||||
|
||||
// PID is not found.
|
||||
TEST_F(AffinityTest, SchedSetAffinityInvalidPID) {
|
||||
// Flaky, but it's tough to avoid a race condition when finding an unused pid
|
||||
EXPECT_THAT(sched_setaffinity(/*pid=*/INT_MAX - 1, sizeof(cpu_set_t), &mask_),
|
||||
SyscallFailsWithErrno(ESRCH));
|
||||
}
|
||||
|
||||
TEST_F(AffinityTest, SchedSetAffinityZeroMask) {
|
||||
CPU_ZERO(&mask_);
|
||||
EXPECT_THAT(sched_setaffinity(/*pid=*/0, sizeof(cpu_set_t), &mask_),
|
||||
SyscallFailsWithErrno(EINVAL));
|
||||
}
|
||||
|
||||
// N.B. This test case relies on cpuset_size_ larger than the actual number of
|
||||
// of all existing CPUs. Check your machine if the test fails.
|
||||
TEST_F(AffinityTest, SchedSetAffinityNonexistentCPUDropped) {
|
||||
cpu_set_t mask = mask_;
|
||||
// Add a nonexistent CPU.
|
||||
//
|
||||
// The number needs to be larger than the possible number of CPU available,
|
||||
// but smaller than the number of the CPU that the kernel claims to support --
|
||||
// it's implicitly returned by raw sched_getaffinity syscall.
|
||||
CPU_SET(cpuset_size_ * 8 - 1, &mask);
|
||||
EXPECT_THAT(
|
||||
// Use raw syscall because it will be rejected by the libc wrapper
|
||||
// otherwise.
|
||||
syscall(SYS_sched_setaffinity, /*pid=*/0, sizeof(cpu_set_t), &mask),
|
||||
SyscallSucceeds())
|
||||
<< "failed with cpumask : " << CPUSetToString(mask)
|
||||
<< ", cpuset_size_ : " << cpuset_size_;
|
||||
cpu_set_t newmask;
|
||||
EXPECT_THAT(sched_getaffinity(/*pid=*/0, sizeof(cpu_set_t), &newmask),
|
||||
SyscallSucceeds());
|
||||
EXPECT_TRUE(CPU_EQUAL(&mask_, &newmask))
|
||||
<< "got: " << CPUSetToString(newmask)
|
||||
<< " != expected: " << CPUSetToString(mask_);
|
||||
}
|
||||
|
||||
TEST_F(AffinityTest, SchedSetAffinityOnlyNonexistentCPUFails) {
|
||||
// Make an empty cpu set.
|
||||
CPU_ZERO(&mask_);
|
||||
// Add a nonexistent CPU.
|
||||
//
|
||||
// The number needs to be larger than the possible number of CPU available,
|
||||
// but smaller than the number of the CPU that the kernel claims to support --
|
||||
// it's implicitly returned by raw sched_getaffinity syscall.
|
||||
int cpu = cpuset_size_ * 8 - 1;
|
||||
if (cpu <= NumCPUs()) {
|
||||
LOG(INFO) << "Skipping test: cpu " << cpu << " exists";
|
||||
return;
|
||||
}
|
||||
CPU_SET(cpu, &mask_);
|
||||
EXPECT_THAT(
|
||||
// Use raw syscall because it will be rejected by the libc wrapper
|
||||
// otherwise.
|
||||
syscall(SYS_sched_setaffinity, /*pid=*/0, sizeof(cpu_set_t), &mask_),
|
||||
SyscallFailsWithErrno(EINVAL));
|
||||
}
|
||||
|
||||
TEST_F(AffinityTest, SchedSetAffinityInvalidSize) {
|
||||
EXPECT_GT(cpuset_size_, 0);
|
||||
// Not big enough.
|
||||
EXPECT_THAT(sched_getaffinity(/*pid=*/0, cpuset_size_ - 1, &mask_),
|
||||
SyscallFailsWithErrno(EINVAL));
|
||||
// Not a multiple of word size.
|
||||
EXPECT_THAT(sched_getaffinity(/*pid=*/0, cpuset_size_ + 1, &mask_),
|
||||
SyscallFailsWithErrno(EINVAL));
|
||||
}
|
||||
|
||||
TEST_F(AffinityTest, Sanity) {
|
||||
ASSERT_NO_ERRNO(ClearLowestBit());
|
||||
EXPECT_THAT(sched_setaffinity(/*pid=*/0, sizeof(cpu_set_t), &mask_),
|
||||
SyscallSucceeds());
|
||||
cpu_set_t newmask;
|
||||
EXPECT_THAT(sched_getaffinity(/*pid=*/0, sizeof(cpu_set_t), &newmask),
|
||||
SyscallSucceeds());
|
||||
EXPECT_TRUE(CPU_EQUAL(&mask_, &newmask))
|
||||
<< "got: " << CPUSetToString(newmask)
|
||||
<< " != expected: " << CPUSetToString(mask_);
|
||||
}
|
||||
|
||||
TEST_F(AffinityTest, NewThread) {
|
||||
ASSERT_NO_ERRNO(ClearLowestBit());
|
||||
ASSERT_NO_ERRNO(ClearLowestBit());
|
||||
EXPECT_THAT(sched_setaffinity(/*pid=*/0, sizeof(cpu_set_t), &mask_),
|
||||
SyscallSucceeds());
|
||||
ScopedThread([this]() {
|
||||
cpu_set_t child_mask;
|
||||
ASSERT_THAT(sched_getaffinity(/*pid=*/0, sizeof(cpu_set_t), &child_mask),
|
||||
SyscallSucceeds());
|
||||
ASSERT_TRUE(CPU_EQUAL(&child_mask, &mask_))
|
||||
<< "child cpu mask: " << CPUSetToString(child_mask)
|
||||
<< " != parent cpu mask: " << CPUSetToString(mask_);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(AffinityTest, ConsistentWithProcCpuInfo) {
|
||||
// Count how many cpus are shown in /proc/cpuinfo.
|
||||
std::string cpuinfo = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/cpuinfo"));
|
||||
int count = 0;
|
||||
for (auto const& line : absl::StrSplit(cpuinfo, '\n')) {
|
||||
if (absl::StartsWith(line, "processor")) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
EXPECT_GE(count, CPU_COUNT(&mask_));
|
||||
}
|
||||
|
||||
TEST_F(AffinityTest, ConsistentWithProcStat) {
|
||||
// Count how many cpus are shown in /proc/stat.
|
||||
std::string stat = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/stat"));
|
||||
int count = 0;
|
||||
for (auto const& line : absl::StrSplit(stat, '\n')) {
|
||||
if (absl::StartsWith(line, "cpu") && !absl::StartsWith(line, "cpu ")) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
EXPECT_GE(count, CPU_COUNT(&mask_));
|
||||
}
|
||||
|
||||
TEST_F(AffinityTest, SmallCpuMask) {
|
||||
const int num_cpus = NumCPUs();
|
||||
const size_t mask_size = CPU_ALLOC_SIZE(num_cpus);
|
||||
cpu_set_t* mask = CPU_ALLOC(num_cpus);
|
||||
ASSERT_NE(mask, nullptr);
|
||||
const auto free_mask = Cleanup([&] { CPU_FREE(mask); });
|
||||
|
||||
CPU_ZERO_S(mask_size, mask);
|
||||
ASSERT_THAT(sched_getaffinity(0, mask_size, mask), SyscallSucceeds());
|
||||
}
|
||||
|
||||
TEST_F(AffinityTest, LargeCpuMask) {
|
||||
// Allocate mask bigger than cpu_set_t normally allocates.
|
||||
const size_t cpus = CPU_SETSIZE * 8;
|
||||
const size_t mask_size = CPU_ALLOC_SIZE(cpus);
|
||||
|
||||
cpu_set_t* large_mask = CPU_ALLOC(cpus);
|
||||
auto free_mask = Cleanup([large_mask] { CPU_FREE(large_mask); });
|
||||
CPU_ZERO_S(mask_size, large_mask);
|
||||
|
||||
// Check that get affinity with large mask works as expected.
|
||||
ASSERT_THAT(sched_getaffinity(/*pid=*/0, mask_size, large_mask),
|
||||
SyscallSucceeds());
|
||||
EXPECT_TRUE(CPU_EQUAL(&mask_, large_mask))
|
||||
<< "got: " << CPUSetToString(*large_mask, cpus)
|
||||
<< " != expected: " << CPUSetToString(mask_);
|
||||
|
||||
// Check that set affinity with large mask works as expected.
|
||||
ASSERT_NO_ERRNO(ClearLowestBit(large_mask, cpus));
|
||||
EXPECT_THAT(sched_setaffinity(/*pid=*/0, mask_size, large_mask),
|
||||
SyscallSucceeds());
|
||||
|
||||
cpu_set_t* new_mask = CPU_ALLOC(cpus);
|
||||
auto free_new_mask = Cleanup([new_mask] { CPU_FREE(new_mask); });
|
||||
CPU_ZERO_S(mask_size, new_mask);
|
||||
EXPECT_THAT(sched_getaffinity(/*pid=*/0, mask_size, new_mask),
|
||||
SyscallSucceeds());
|
||||
|
||||
EXPECT_TRUE(CPU_EQUAL_S(mask_size, large_mask, new_mask))
|
||||
<< "got: " << CPUSetToString(*new_mask, cpus)
|
||||
<< " != expected: " << CPUSetToString(*large_mask, cpus);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace testing
|
||||
} // namespace gvisor
|
||||
@@ -0,0 +1,433 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// 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 <fcntl.h>
|
||||
#include <linux/aio_abi.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "test/syscalls/linux/file_base.h"
|
||||
#include "test/util/cleanup.h"
|
||||
#include "test/util/file_descriptor.h"
|
||||
#include "test/util/temp_path.h"
|
||||
#include "test/util/test_util.h"
|
||||
|
||||
namespace gvisor {
|
||||
namespace testing {
|
||||
namespace {
|
||||
|
||||
constexpr char kData[] = "hello world!";
|
||||
|
||||
int SubmitCtx(aio_context_t ctx, long nr, struct iocb** iocbpp) {
|
||||
return syscall(__NR_io_submit, ctx, nr, iocbpp);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class AIOTest : public FileTest {
|
||||
public:
|
||||
AIOTest() : ctx_(0) {}
|
||||
|
||||
int SetupContext(unsigned int nr) {
|
||||
return syscall(__NR_io_setup, nr, &ctx_);
|
||||
}
|
||||
|
||||
int Submit(long nr, struct iocb** iocbpp) {
|
||||
return SubmitCtx(ctx_, nr, iocbpp);
|
||||
}
|
||||
|
||||
int GetEvents(long min, long max, struct io_event* events,
|
||||
struct timespec* timeout) {
|
||||
return RetryEINTR(syscall)(__NR_io_getevents, ctx_, min, max, events,
|
||||
timeout);
|
||||
}
|
||||
|
||||
int DestroyContext() { return syscall(__NR_io_destroy, ctx_); }
|
||||
|
||||
void TearDown() override {
|
||||
FileTest::TearDown();
|
||||
if (ctx_ != 0) {
|
||||
ASSERT_THAT(DestroyContext(), SyscallSucceeds());
|
||||
}
|
||||
}
|
||||
|
||||
struct iocb CreateCallback() {
|
||||
struct iocb cb = {};
|
||||
cb.aio_data = 0x123;
|
||||
cb.aio_fildes = test_file_fd_.get();
|
||||
cb.aio_lio_opcode = IOCB_CMD_PWRITE;
|
||||
cb.aio_buf = reinterpret_cast<uint64_t>(kData);
|
||||
cb.aio_offset = 0;
|
||||
cb.aio_nbytes = strlen(kData);
|
||||
return cb;
|
||||
}
|
||||
|
||||
protected:
|
||||
aio_context_t ctx_;
|
||||
};
|
||||
|
||||
TEST_F(AIOTest, BasicWrite) {
|
||||
// Copied from fs/aio.c.
|
||||
constexpr unsigned AIO_RING_MAGIC = 0xa10a10a1;
|
||||
struct aio_ring {
|
||||
unsigned id;
|
||||
unsigned nr;
|
||||
unsigned head;
|
||||
unsigned tail;
|
||||
unsigned magic;
|
||||
unsigned compat_features;
|
||||
unsigned incompat_features;
|
||||
unsigned header_length;
|
||||
struct io_event io_events[0];
|
||||
};
|
||||
|
||||
// Setup a context that is 128 entries deep.
|
||||
ASSERT_THAT(SetupContext(128), SyscallSucceeds());
|
||||
|
||||
// Check that 'ctx_' points to a valid address. libaio uses it to check if
|
||||
// aio implementation uses aio_ring. gVisor doesn't and returns all zeroes.
|
||||
// Linux implements aio_ring, so skip the zeroes check.
|
||||
//
|
||||
// TODO: Remove when gVisor implements aio_ring.
|
||||
auto ring = reinterpret_cast<struct aio_ring*>(ctx_);
|
||||
auto magic = IsRunningOnGvisor() ? 0 : AIO_RING_MAGIC;
|
||||
EXPECT_EQ(ring->magic, magic);
|
||||
|
||||
struct iocb cb = CreateCallback();
|
||||
struct iocb* cbs[1] = {&cb};
|
||||
|
||||
// Submit the request.
|
||||
ASSERT_THAT(Submit(1, cbs), SyscallSucceedsWithValue(1));
|
||||
|
||||
// Get the reply.
|
||||
struct io_event events[1];
|
||||
ASSERT_THAT(GetEvents(1, 1, events, nullptr), SyscallSucceedsWithValue(1));
|
||||
|
||||
// Verify that it is as expected.
|
||||
EXPECT_EQ(events[0].data, 0x123);
|
||||
EXPECT_EQ(events[0].obj, reinterpret_cast<long>(&cb));
|
||||
EXPECT_EQ(events[0].res, strlen(kData));
|
||||
|
||||
// Verify that the file contains the contents.
|
||||
char verify_buf[32] = {};
|
||||
ASSERT_THAT(read(test_file_fd_.get(), &verify_buf[0], strlen(kData)),
|
||||
SyscallSucceeds());
|
||||
EXPECT_EQ(strcmp(kData, &verify_buf[0]), 0);
|
||||
}
|
||||
|
||||
TEST_F(AIOTest, BadWrite) {
|
||||
// Create a pipe and immediately close the read end.
|
||||
int pipefd[2];
|
||||
ASSERT_THAT(pipe(pipefd), SyscallSucceeds());
|
||||
|
||||
FileDescriptor rfd(pipefd[0]);
|
||||
FileDescriptor wfd(pipefd[1]);
|
||||
|
||||
rfd.reset(); // Close the read end.
|
||||
|
||||
// Setup a context that is 128 entries deep.
|
||||
ASSERT_THAT(SetupContext(128), SyscallSucceeds());
|
||||
|
||||
struct iocb cb = CreateCallback();
|
||||
// Try to write to the read end.
|
||||
cb.aio_fildes = wfd.get();
|
||||
struct iocb* cbs[1] = {&cb};
|
||||
|
||||
// Submit the request.
|
||||
ASSERT_THAT(Submit(1, cbs), SyscallSucceedsWithValue(1));
|
||||
|
||||
// Get the reply.
|
||||
struct io_event events[1];
|
||||
ASSERT_THAT(GetEvents(1, 1, events, nullptr), SyscallSucceedsWithValue(1));
|
||||
|
||||
// Verify that it fails with the right error code.
|
||||
EXPECT_EQ(events[0].data, 0x123);
|
||||
EXPECT_EQ(events[0].obj, reinterpret_cast<uint64_t>(&cb));
|
||||
EXPECT_LT(events[0].res, 0);
|
||||
}
|
||||
|
||||
TEST_F(AIOTest, ExitWithPendingIo) {
|
||||
// Setup a context that is 5 entries deep.
|
||||
ASSERT_THAT(SetupContext(5), SyscallSucceeds());
|
||||
|
||||
struct iocb cb = CreateCallback();
|
||||
struct iocb* cbs[] = {&cb};
|
||||
|
||||
// Submit a request but don't complete it to make it pending.
|
||||
EXPECT_THAT(Submit(1, cbs), SyscallSucceeds());
|
||||
}
|
||||
|
||||
int Submitter(void* arg) {
|
||||
auto test = reinterpret_cast<AIOTest*>(arg);
|
||||
|
||||
struct iocb cb = test->CreateCallback();
|
||||
struct iocb* cbs[1] = {&cb};
|
||||
|
||||
// Submit the request.
|
||||
TEST_CHECK(test->Submit(1, cbs) == 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
TEST_F(AIOTest, CloneVm) {
|
||||
// Setup a context that is 128 entries deep.
|
||||
ASSERT_THAT(SetupContext(128), SyscallSucceeds());
|
||||
|
||||
const size_t kStackSize = 5 * kPageSize;
|
||||
std::unique_ptr<char[]> stack(new char[kStackSize]);
|
||||
char* bp = stack.get() + kStackSize;
|
||||
pid_t child;
|
||||
ASSERT_THAT(child = clone(Submitter, bp, CLONE_VM | SIGCHLD,
|
||||
reinterpret_cast<void*>(this)),
|
||||
SyscallSucceeds());
|
||||
|
||||
// Get the reply.
|
||||
struct io_event events[1];
|
||||
ASSERT_THAT(GetEvents(1, 1, events, nullptr), SyscallSucceedsWithValue(1));
|
||||
|
||||
// Verify that it is as expected.
|
||||
EXPECT_EQ(events[0].data, 0x123);
|
||||
EXPECT_EQ(events[0].res, strlen(kData));
|
||||
|
||||
// Verify that the file contains the contents.
|
||||
char verify_buf[32] = {};
|
||||
ASSERT_THAT(read(test_file_fd_.get(), &verify_buf[0], strlen(kData)),
|
||||
SyscallSucceeds());
|
||||
EXPECT_EQ(strcmp(kData, &verify_buf[0]), 0);
|
||||
|
||||
int status;
|
||||
ASSERT_THAT(RetryEINTR(waitpid)(child, &status, 0),
|
||||
SyscallSucceedsWithValue(child));
|
||||
EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0)
|
||||
<< " status " << status;
|
||||
}
|
||||
|
||||
// Tests that AIO context can be remapped to a different address.
|
||||
TEST_F(AIOTest, Mremap) {
|
||||
// Setup a context that is 128 entries deep.
|
||||
ASSERT_THAT(SetupContext(128), SyscallSucceeds());
|
||||
|
||||
struct iocb cb = CreateCallback();
|
||||
struct iocb* cbs[1] = {&cb};
|
||||
|
||||
// Reserve address space for the mremap target so we have something safe to
|
||||
// map over.
|
||||
//
|
||||
// N.B. We reserve 2 pages because we'll attempt to remap to 2 pages below.
|
||||
// That should fail with EFAULT, but will fail with EINVAL if this mmap
|
||||
// returns the page immediately below ctx_, as
|
||||
// [new_address, new_address+2*kPageSize) overlaps [ctx_, ctx_+kPageSize).
|
||||
void* new_address = mmap(nullptr, 2 * kPageSize, PROT_READ,
|
||||
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||
ASSERT_THAT(reinterpret_cast<intptr_t>(new_address), SyscallSucceeds());
|
||||
auto mmap_cleanup = Cleanup([new_address] {
|
||||
EXPECT_THAT(munmap(new_address, 2 * kPageSize), SyscallSucceeds());
|
||||
});
|
||||
|
||||
// Test that remapping to a larger address fails.
|
||||
void* res = mremap(reinterpret_cast<void*>(ctx_), kPageSize, 2 * kPageSize,
|
||||
MREMAP_FIXED | MREMAP_MAYMOVE, new_address);
|
||||
ASSERT_THAT(reinterpret_cast<intptr_t>(res), SyscallFailsWithErrno(EFAULT));
|
||||
|
||||
// Remap context 'handle' to a different address.
|
||||
res = mremap(reinterpret_cast<void*>(ctx_), kPageSize, kPageSize,
|
||||
MREMAP_FIXED | MREMAP_MAYMOVE, new_address);
|
||||
ASSERT_THAT(
|
||||
reinterpret_cast<intptr_t>(res),
|
||||
SyscallSucceedsWithValue(reinterpret_cast<intptr_t>(new_address)));
|
||||
mmap_cleanup.Release();
|
||||
aio_context_t old_ctx = ctx_;
|
||||
ctx_ = reinterpret_cast<aio_context_t>(new_address);
|
||||
|
||||
// Check that submitting the request with the old 'ctx_' fails.
|
||||
ASSERT_THAT(SubmitCtx(old_ctx, 1, cbs), SyscallFailsWithErrno(EINVAL));
|
||||
|
||||
// Submit the request with the new 'ctx_'.
|
||||
ASSERT_THAT(Submit(1, cbs), SyscallSucceedsWithValue(1));
|
||||
|
||||
// Remap again.
|
||||
new_address =
|
||||
mmap(nullptr, kPageSize, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||
ASSERT_THAT(reinterpret_cast<int64_t>(new_address), SyscallSucceeds());
|
||||
auto mmap_cleanup2 = Cleanup([new_address] {
|
||||
EXPECT_THAT(munmap(new_address, kPageSize), SyscallSucceeds());
|
||||
});
|
||||
res = mremap(reinterpret_cast<void*>(ctx_), kPageSize, kPageSize,
|
||||
MREMAP_FIXED | MREMAP_MAYMOVE, new_address);
|
||||
ASSERT_THAT(reinterpret_cast<int64_t>(res),
|
||||
SyscallSucceedsWithValue(reinterpret_cast<int64_t>(new_address)));
|
||||
mmap_cleanup2.Release();
|
||||
ctx_ = reinterpret_cast<aio_context_t>(new_address);
|
||||
|
||||
// Get the reply with yet another 'ctx_' and verify it.
|
||||
struct io_event events[1];
|
||||
ASSERT_THAT(GetEvents(1, 1, events, nullptr), SyscallSucceedsWithValue(1));
|
||||
EXPECT_EQ(events[0].data, 0x123);
|
||||
EXPECT_EQ(events[0].obj, reinterpret_cast<long>(&cb));
|
||||
EXPECT_EQ(events[0].res, strlen(kData));
|
||||
|
||||
// Verify that the file contains the contents.
|
||||
char verify_buf[32] = {};
|
||||
ASSERT_THAT(read(test_file_fd_.get(), &verify_buf[0], strlen(kData)),
|
||||
SyscallSucceeds());
|
||||
EXPECT_EQ(strcmp(kData, &verify_buf[0]), 0);
|
||||
}
|
||||
|
||||
// Tests that AIO context can be replaced with a different mapping at the same
|
||||
// address and continue working. Don't ask why, but Linux allows it.
|
||||
TEST_F(AIOTest, MremapOver) {
|
||||
// Setup a context that is 128 entries deep.
|
||||
ASSERT_THAT(SetupContext(128), SyscallSucceeds());
|
||||
|
||||
struct iocb cb = CreateCallback();
|
||||
struct iocb* cbs[1] = {&cb};
|
||||
|
||||
ASSERT_THAT(Submit(1, cbs), SyscallSucceedsWithValue(1));
|
||||
|
||||
// Allocate a new VMA, copy 'ctx_' content over, and remap it on top
|
||||
// of 'ctx_'.
|
||||
void* new_address = mmap(nullptr, kPageSize, PROT_READ | PROT_WRITE,
|
||||
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||
ASSERT_THAT(reinterpret_cast<int64_t>(new_address), SyscallSucceeds());
|
||||
auto mmap_cleanup = Cleanup([new_address] {
|
||||
EXPECT_THAT(munmap(new_address, kPageSize), SyscallSucceeds());
|
||||
});
|
||||
|
||||
memcpy(new_address, reinterpret_cast<void*>(ctx_), kPageSize);
|
||||
void* res =
|
||||
mremap(new_address, kPageSize, kPageSize, MREMAP_FIXED | MREMAP_MAYMOVE,
|
||||
reinterpret_cast<void*>(ctx_));
|
||||
ASSERT_THAT(reinterpret_cast<int64_t>(res), SyscallSucceedsWithValue(ctx_));
|
||||
mmap_cleanup.Release();
|
||||
|
||||
// Everything continues to work just fine.
|
||||
struct io_event events[1];
|
||||
ASSERT_THAT(GetEvents(1, 1, events, nullptr), SyscallSucceedsWithValue(1));
|
||||
EXPECT_EQ(events[0].data, 0x123);
|
||||
EXPECT_EQ(events[0].obj, reinterpret_cast<long>(&cb));
|
||||
EXPECT_EQ(events[0].res, strlen(kData));
|
||||
|
||||
// Verify that the file contains the contents.
|
||||
char verify_buf[32] = {};
|
||||
ASSERT_THAT(read(test_file_fd_.get(), &verify_buf[0], strlen(kData)),
|
||||
SyscallSucceeds());
|
||||
EXPECT_EQ(strcmp(kData, &verify_buf[0]), 0);
|
||||
}
|
||||
|
||||
// Tests that AIO calls fail if context's address is inaccessible.
|
||||
TEST_F(AIOTest, Mprotect) {
|
||||
// Setup a context that is 128 entries deep.
|
||||
ASSERT_THAT(SetupContext(128), SyscallSucceeds());
|
||||
|
||||
struct iocb cb = CreateCallback();
|
||||
struct iocb* cbs[1] = {&cb};
|
||||
|
||||
ASSERT_THAT(Submit(1, cbs), SyscallSucceedsWithValue(1));
|
||||
|
||||
// Makes the context 'handle' inaccessible and check that all subsequent
|
||||
// calls fail.
|
||||
ASSERT_THAT(mprotect(reinterpret_cast<void*>(ctx_), kPageSize, PROT_NONE),
|
||||
SyscallSucceeds());
|
||||
struct io_event events[1];
|
||||
EXPECT_THAT(GetEvents(1, 1, events, nullptr), SyscallFailsWithErrno(EINVAL));
|
||||
ASSERT_THAT(Submit(1, cbs), SyscallFailsWithErrno(EINVAL));
|
||||
EXPECT_THAT(DestroyContext(), SyscallFailsWithErrno(EINVAL));
|
||||
|
||||
// Prevent TearDown from attempting to destroy the context and fail.
|
||||
ctx_ = 0;
|
||||
}
|
||||
|
||||
TEST_F(AIOTest, Timeout) {
|
||||
// Setup a context that is 128 entries deep.
|
||||
ASSERT_THAT(SetupContext(128), SyscallSucceeds());
|
||||
|
||||
struct timespec timeout;
|
||||
timeout.tv_sec = 0;
|
||||
timeout.tv_nsec = 10;
|
||||
struct io_event events[1];
|
||||
ASSERT_THAT(GetEvents(1, 1, events, &timeout), SyscallSucceedsWithValue(0));
|
||||
}
|
||||
|
||||
class AIOReadWriteParamTest : public AIOTest,
|
||||
public ::testing::WithParamInterface<int> {};
|
||||
|
||||
TEST_P(AIOReadWriteParamTest, BadOffset) {
|
||||
// Setup a context that is 128 entries deep.
|
||||
ASSERT_THAT(SetupContext(128), SyscallSucceeds());
|
||||
|
||||
struct iocb cb = CreateCallback();
|
||||
struct iocb* cbs[1] = {&cb};
|
||||
|
||||
// Create a buffer that we can write to.
|
||||
char buf[] = "hello world!";
|
||||
cb.aio_buf = reinterpret_cast<uint64_t>(buf);
|
||||
|
||||
// Set the operation on the callback and give a negative offset.
|
||||
const int opcode = GetParam();
|
||||
cb.aio_lio_opcode = opcode;
|
||||
|
||||
iovec iov = {};
|
||||
if (opcode == IOCB_CMD_PREADV || opcode == IOCB_CMD_PWRITEV) {
|
||||
// Create a valid iovec and set it in the callback.
|
||||
iov.iov_base = reinterpret_cast<void*>(buf);
|
||||
iov.iov_len = 1;
|
||||
cb.aio_buf = reinterpret_cast<uint64_t>(&iov);
|
||||
// aio_nbytes is the number of iovecs.
|
||||
cb.aio_nbytes = 1;
|
||||
}
|
||||
|
||||
// Pass a negative offset.
|
||||
cb.aio_offset = -1;
|
||||
|
||||
// Should get error on submission.
|
||||
ASSERT_THAT(Submit(1, cbs), SyscallFailsWithErrno(EINVAL));
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(BadOffset, AIOReadWriteParamTest,
|
||||
::testing::Values(IOCB_CMD_PREAD, IOCB_CMD_PWRITE,
|
||||
IOCB_CMD_PREADV, IOCB_CMD_PWRITEV));
|
||||
|
||||
class AIOVectorizedParamTest : public AIOTest,
|
||||
public ::testing::WithParamInterface<int> {};
|
||||
|
||||
TEST_P(AIOVectorizedParamTest, BadIOVecs) {
|
||||
// Setup a context that is 128 entries deep.
|
||||
ASSERT_THAT(SetupContext(128), SyscallSucceeds());
|
||||
|
||||
struct iocb cb = CreateCallback();
|
||||
struct iocb* cbs[1] = {&cb};
|
||||
|
||||
// Modify the callback to use the operation from the param.
|
||||
cb.aio_lio_opcode = GetParam();
|
||||
|
||||
// Create an iovec with address in kernel range, and pass that as the buffer.
|
||||
iovec iov = {};
|
||||
iov.iov_base = reinterpret_cast<void*>(0xFFFFFFFF00000000);
|
||||
iov.iov_len = 1;
|
||||
cb.aio_buf = reinterpret_cast<uint64_t>(&iov);
|
||||
// aio_nbytes is the number of iovecs.
|
||||
cb.aio_nbytes = 1;
|
||||
|
||||
// Should get error on submission.
|
||||
ASSERT_THAT(Submit(1, cbs), SyscallFailsWithErrno(EFAULT));
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(BadIOVecs, AIOVectorizedParamTest,
|
||||
::testing::Values(IOCB_CMD_PREADV, IOCB_CMD_PWRITEV));
|
||||
|
||||
} // namespace testing
|
||||
} // namespace gvisor
|
||||
@@ -0,0 +1,193 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// 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 <unistd.h>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "absl/time/clock.h"
|
||||
#include "absl/time/time.h"
|
||||
#include "test/util/file_descriptor.h"
|
||||
#include "test/util/logging.h"
|
||||
#include "test/util/signal_util.h"
|
||||
#include "test/util/test_util.h"
|
||||
#include "test/util/thread_util.h"
|
||||
|
||||
namespace gvisor {
|
||||
namespace testing {
|
||||
|
||||
namespace {
|
||||
|
||||
// N.B. Below, main blocks SIGALRM. Test cases must unblock it if they want
|
||||
// delivery.
|
||||
|
||||
void do_nothing_handler(int sig, siginfo_t* siginfo, void* arg) {}
|
||||
|
||||
// No random save as the test relies on alarm timing. Cooperative save tests
|
||||
// already cover the save between alarm and read.
|
||||
TEST(AlarmTest, Interrupt_NoRandomSave) {
|
||||
int pipe_fds[2];
|
||||
ASSERT_THAT(pipe(pipe_fds), SyscallSucceeds());
|
||||
|
||||
FileDescriptor read_fd(pipe_fds[0]);
|
||||
FileDescriptor write_fd(pipe_fds[1]);
|
||||
|
||||
// Use a signal handler that interrupts but does nothing rather than using the
|
||||
// default terminate action.
|
||||
struct sigaction sa;
|
||||
sa.sa_sigaction = do_nothing_handler;
|
||||
sigfillset(&sa.sa_mask);
|
||||
sa.sa_flags = 0;
|
||||
auto sa_cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGALRM, sa));
|
||||
|
||||
// Actually allow SIGALRM delivery.
|
||||
auto mask_cleanup =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_UNBLOCK, SIGALRM));
|
||||
|
||||
// Alarm in 20 second, which should be well after read blocks below.
|
||||
ASSERT_THAT(alarm(20), SyscallSucceeds());
|
||||
|
||||
char buf;
|
||||
ASSERT_THAT(read(read_fd.get(), &buf, 1), SyscallFailsWithErrno(EINTR));
|
||||
}
|
||||
|
||||
/* Count of the number of SIGALARMS handled. */
|
||||
static volatile int alarms_received = 0;
|
||||
|
||||
void inc_alarms_handler(int sig, siginfo_t* siginfo, void* arg) {
|
||||
alarms_received++;
|
||||
}
|
||||
|
||||
// No random save as the test relies on alarm timing. Cooperative save tests
|
||||
// already cover the save between alarm and read.
|
||||
TEST(AlarmTest, Restart_NoRandomSave) {
|
||||
alarms_received = 0;
|
||||
|
||||
int pipe_fds[2];
|
||||
ASSERT_THAT(pipe(pipe_fds), SyscallSucceeds());
|
||||
|
||||
FileDescriptor read_fd(pipe_fds[0]);
|
||||
// Write end closed by thread below.
|
||||
|
||||
struct sigaction sa;
|
||||
sa.sa_sigaction = inc_alarms_handler;
|
||||
sigfillset(&sa.sa_mask);
|
||||
sa.sa_flags = SA_RESTART;
|
||||
auto sa_cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGALRM, sa));
|
||||
|
||||
// Spawn a thread to eventually unblock the read below.
|
||||
ScopedThread t([pipe_fds] {
|
||||
absl::SleepFor(absl::Seconds(30));
|
||||
EXPECT_THAT(close(pipe_fds[1]), SyscallSucceeds());
|
||||
});
|
||||
|
||||
// Actually allow SIGALRM delivery.
|
||||
auto mask_cleanup =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_UNBLOCK, SIGALRM));
|
||||
|
||||
// Alarm in 20 second, which should be well after read blocks below, but
|
||||
// before it returns.
|
||||
ASSERT_THAT(alarm(20), SyscallSucceeds());
|
||||
|
||||
// Read and eventually get an EOF from the writer closing. If SA_RESTART
|
||||
// didn't work, then the alarm would not have fired and we wouldn't increment
|
||||
// our alarms_received count in our signal handler, or we would have not
|
||||
// restarted the syscall gracefully, which we expect below in order to be
|
||||
// able to get the final EOF on the pipe.
|
||||
char buf;
|
||||
ASSERT_THAT(read(read_fd.get(), &buf, 1), SyscallSucceeds());
|
||||
EXPECT_EQ(alarms_received, 1);
|
||||
|
||||
t.Join();
|
||||
}
|
||||
|
||||
// No random save as the test relies on alarm timing. Cooperative save tests
|
||||
// already cover the save between alarm and pause.
|
||||
TEST(AlarmTest, SaSiginfo_NoRandomSave) {
|
||||
// Use a signal handler that interrupts but does nothing rather than using the
|
||||
// default terminate action.
|
||||
struct sigaction sa;
|
||||
sa.sa_sigaction = do_nothing_handler;
|
||||
sigfillset(&sa.sa_mask);
|
||||
sa.sa_flags = SA_SIGINFO;
|
||||
auto sa_cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGALRM, sa));
|
||||
|
||||
// Actually allow SIGALRM delivery.
|
||||
auto mask_cleanup =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_UNBLOCK, SIGALRM));
|
||||
|
||||
// Alarm in 20 second, which should be well after pause blocks below.
|
||||
ASSERT_THAT(alarm(20), SyscallSucceeds());
|
||||
ASSERT_THAT(pause(), SyscallFailsWithErrno(EINTR));
|
||||
}
|
||||
|
||||
// No random save as the test relies on alarm timing. Cooperative save tests
|
||||
// already cover the save between alarm and pause.
|
||||
TEST(AlarmTest, SaInterrupt_NoRandomSave) {
|
||||
// Use a signal handler that interrupts but does nothing rather than using the
|
||||
// default terminate action.
|
||||
struct sigaction sa;
|
||||
sa.sa_sigaction = do_nothing_handler;
|
||||
sigfillset(&sa.sa_mask);
|
||||
sa.sa_flags = SA_INTERRUPT;
|
||||
auto sa_cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGALRM, sa));
|
||||
|
||||
// Actually allow SIGALRM delivery.
|
||||
auto mask_cleanup =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_UNBLOCK, SIGALRM));
|
||||
|
||||
// Alarm in 20 second, which should be well after pause blocks below.
|
||||
ASSERT_THAT(alarm(20), SyscallSucceeds());
|
||||
ASSERT_THAT(pause(), SyscallFailsWithErrno(EINTR));
|
||||
}
|
||||
|
||||
TEST(AlarmTest, UserModeSpinning) {
|
||||
alarms_received = 0;
|
||||
|
||||
struct sigaction sa = {};
|
||||
sa.sa_sigaction = inc_alarms_handler;
|
||||
sigfillset(&sa.sa_mask);
|
||||
sa.sa_flags = SA_SIGINFO;
|
||||
auto sa_cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGALRM, sa));
|
||||
|
||||
// Actually allow SIGALRM delivery.
|
||||
auto mask_cleanup =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_UNBLOCK, SIGALRM));
|
||||
|
||||
// Alarm in 20 second, which should be well into the loop below.
|
||||
ASSERT_THAT(alarm(20), SyscallSucceeds());
|
||||
// Make sure that the signal gets delivered even if we are spinning in user
|
||||
// mode when it arrives.
|
||||
while (!alarms_received) {
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace testing
|
||||
} // namespace gvisor
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
// These tests depend on delivering SIGALRM to the main thread. Block SIGALRM
|
||||
// so that any other threads created by TestInit will also have SIGALRM
|
||||
// blocked.
|
||||
sigset_t set;
|
||||
sigemptyset(&set);
|
||||
sigaddset(&set, SIGALRM);
|
||||
TEST_PCHECK(sigprocmask(SIG_BLOCK, &set, nullptr) == 0);
|
||||
|
||||
gvisor::testing::TestInit(&argc, &argv);
|
||||
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// 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 <asm/prctl.h>
|
||||
#include <sys/prctl.h>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "test/util/test_util.h"
|
||||
|
||||
// glibc does not provide a prototype for arch_prctl() so declare it here.
|
||||
extern "C" int arch_prctl(int code, uintptr_t addr);
|
||||
|
||||
namespace gvisor {
|
||||
namespace testing {
|
||||
|
||||
namespace {
|
||||
|
||||
TEST(ArchPrctlTest, GetSetFS) {
|
||||
uintptr_t orig;
|
||||
const uintptr_t kNonCanonicalFsbase = 0x4141414142424242;
|
||||
|
||||
// Get the original FS.base and then set it to the same value (this is
|
||||
// intentional because FS.base is the TLS pointer so we cannot change it
|
||||
// arbitrarily).
|
||||
ASSERT_THAT(arch_prctl(ARCH_GET_FS, reinterpret_cast<uintptr_t>(&orig)),
|
||||
SyscallSucceeds());
|
||||
ASSERT_THAT(arch_prctl(ARCH_SET_FS, orig), SyscallSucceeds());
|
||||
|
||||
// Trying to set FS.base to a non-canonical value should return an error.
|
||||
ASSERT_THAT(arch_prctl(ARCH_SET_FS, kNonCanonicalFsbase),
|
||||
SyscallFailsWithErrno(EPERM));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace testing
|
||||
} // namespace gvisor
|
||||
@@ -0,0 +1,39 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// 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 <sys/syscall.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "test/util/test_util.h"
|
||||
|
||||
namespace gvisor {
|
||||
namespace testing {
|
||||
|
||||
namespace {
|
||||
|
||||
TEST(BadSyscallTest, NotImplemented) {
|
||||
// get_kernel_syms is not supported in Linux > 2.6, and not implemented in
|
||||
// gVisor.
|
||||
EXPECT_THAT(syscall(SYS_get_kernel_syms), SyscallFailsWithErrno(ENOSYS));
|
||||
}
|
||||
|
||||
TEST(BadSyscallTest, NegativeOne) {
|
||||
EXPECT_THAT(syscall(-1), SyscallFailsWithErrno(ENOSYS));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace testing
|
||||
} // namespace gvisor
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user