mirror of
https://github.com/hanzoai/visor.git
synced 2026-09-24 17:04:14 +00:00
The `last` fd argument can be up to max uint32, and some applications call it with this maximum: https://github.com/GNOME/glib/blob/26bc1d08ec574b387ff4bcd919a020a586727bbf/glib/glib-unix.c#L890 PiperOrigin-RevId: 718526878
429 lines
13 KiB
C++
429 lines
13 KiB
C++
// Copyright 2022 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 <asm-generic/errno-base.h>
|
|
#include <unistd.h>
|
|
|
|
#include <vector>
|
|
|
|
#include "gtest/gtest.h"
|
|
#include "absl/base/macros.h"
|
|
#include "test/util/file_descriptor.h"
|
|
#include "test/util/posix_error.h"
|
|
#include "test/util/temp_path.h"
|
|
#include "test/util/test_util.h"
|
|
#include "test/util/thread_util.h"
|
|
|
|
namespace gvisor {
|
|
namespace testing {
|
|
|
|
namespace {
|
|
|
|
#ifndef CLOSE_RANGE_UNSHARE
|
|
#define CLOSE_RANGE_UNSHARE (1U << 1)
|
|
#endif
|
|
#ifndef CLOSE_RANGE_CLOEXEC
|
|
#define CLOSE_RANGE_CLOEXEC (1U << 2)
|
|
#endif
|
|
|
|
#ifndef SYS_close_range
|
|
#if defined(__x86_64__) || defined(__aarch64__)
|
|
#define SYS_close_range 436
|
|
#else
|
|
#error "Unknown architecture"
|
|
#endif
|
|
#endif // SYS_close_range
|
|
|
|
int close_range(unsigned int first, unsigned int last, unsigned int flags) {
|
|
return syscall(SYS_close_range, first, last, flags);
|
|
}
|
|
|
|
class CloseRangeTest : public ::testing::Test {
|
|
public:
|
|
void CreateFiles(int num_files) {
|
|
file_names_.reserve(num_files);
|
|
for (int i = 0; i < num_files; ++i) {
|
|
file_names_.push_back(NewTempAbsPath());
|
|
int fd;
|
|
ASSERT_THAT(fd = open(file_names_[i].c_str(), O_CREAT, 0644),
|
|
SyscallSucceeds());
|
|
ASSERT_THAT(close(fd), SyscallSucceeds());
|
|
}
|
|
}
|
|
|
|
void OpenFilesRdwr() {
|
|
fds_.clear();
|
|
fds_.reserve(file_names_.size());
|
|
for (std::string &file_name : file_names_) {
|
|
int fd;
|
|
ASSERT_THAT(fd = open(file_name.c_str(), O_RDWR), SyscallSucceeds());
|
|
fds_.push_back(fd);
|
|
}
|
|
}
|
|
|
|
private:
|
|
void TearDown() override {
|
|
for (std::string &name : file_names_) {
|
|
unlink(name.c_str());
|
|
}
|
|
}
|
|
|
|
protected:
|
|
std::vector<std::string> file_names_;
|
|
std::vector<unsigned int> fds_;
|
|
};
|
|
|
|
// Base test to confirm that all files in contiguous range get closed.
|
|
TEST_F(CloseRangeTest, ContiguousRange) {
|
|
SKIP_IF(!IsRunningOnGvisor() && close_range(1, 0, 0) < 0 && errno == ENOSYS);
|
|
int num_files_in_range = 10;
|
|
unsigned int flags = 0;
|
|
|
|
CreateFiles(num_files_in_range);
|
|
OpenFilesRdwr();
|
|
|
|
for (int fd : fds_) {
|
|
auto ret = ReadAllFd(fd);
|
|
EXPECT_THAT(ret, IsPosixErrorOkMatcher());
|
|
}
|
|
|
|
EXPECT_THAT(close_range(fds_[0], fds_[num_files_in_range - 1], flags),
|
|
SyscallSucceeds());
|
|
for (int fd : fds_) {
|
|
auto ret = ReadAllFd(fd);
|
|
EXPECT_THAT(ret, PosixErrorIs(EBADF));
|
|
}
|
|
}
|
|
|
|
// Test to confirm that a range with files already closed in the range still
|
|
// closes the remaining files.
|
|
TEST_F(CloseRangeTest, RangeWithHoles) {
|
|
SKIP_IF(!IsRunningOnGvisor() && close_range(1, 0, 0) < 0 && errno == ENOSYS);
|
|
int num_files_in_range = 10;
|
|
unsigned int flags = 0;
|
|
|
|
CreateFiles(num_files_in_range);
|
|
OpenFilesRdwr();
|
|
|
|
for (int fd : fds_) {
|
|
auto ret = ReadAllFd(fd);
|
|
EXPECT_THAT(ret, IsPosixErrorOkMatcher());
|
|
}
|
|
|
|
EXPECT_THAT(close(fds_[2]), SyscallSucceeds());
|
|
EXPECT_THAT(close(fds_[7]), SyscallSucceeds());
|
|
|
|
EXPECT_THAT(close_range(fds_[0], fds_[num_files_in_range - 1], flags),
|
|
SyscallSucceeds());
|
|
for (int fd : fds_) {
|
|
auto ret = ReadAllFd(fd);
|
|
EXPECT_THAT(ret, PosixErrorIs(EBADF));
|
|
}
|
|
}
|
|
|
|
// Test to confirm that closing a range with fds preceding and following the
|
|
// range leaves those other fds open.
|
|
TEST_F(CloseRangeTest, RangeInMiddleOfOpenFiles) {
|
|
SKIP_IF(!IsRunningOnGvisor() && close_range(1, 0, 0) < 0 && errno == ENOSYS);
|
|
int num_files_in_range = 10;
|
|
unsigned int flags = 0;
|
|
|
|
CreateFiles(num_files_in_range);
|
|
OpenFilesRdwr();
|
|
|
|
for (int fd : fds_) {
|
|
auto ret = ReadAllFd(fd);
|
|
EXPECT_THAT(ret, IsPosixErrorOkMatcher());
|
|
}
|
|
|
|
size_t slice_start = 4;
|
|
size_t slice_end = 7;
|
|
EXPECT_THAT(close_range(fds_[slice_start], fds_[slice_end], flags),
|
|
SyscallSucceeds());
|
|
for (int fd :
|
|
std::vector(fds_.begin() + slice_start, fds_.begin() + slice_end + 1)) {
|
|
auto ret = ReadAllFd(fd);
|
|
EXPECT_THAT(ret, PosixErrorIs(EBADF));
|
|
}
|
|
for (int fd : std::vector(fds_.begin(), fds_.begin() + slice_start)) {
|
|
auto ret = ReadAllFd(fd);
|
|
EXPECT_THAT(ret, IsPosixErrorOkMatcher());
|
|
}
|
|
for (int fd : std::vector(fds_.begin() + slice_end + 1, fds_.end())) {
|
|
auto ret = ReadAllFd(fd);
|
|
EXPECT_THAT(ret, IsPosixErrorOkMatcher());
|
|
}
|
|
}
|
|
|
|
// Test to confirm that calling close_range on just one file succeeds.
|
|
TEST_F(CloseRangeTest, SingleFile) {
|
|
SKIP_IF(!IsRunningOnGvisor() && close_range(1, 0, 0) < 0 && errno == ENOSYS);
|
|
int num_files_in_range = 1;
|
|
unsigned int flags = 0; /* */
|
|
|
|
CreateFiles(num_files_in_range);
|
|
OpenFilesRdwr();
|
|
|
|
auto ret = ReadAllFd(fds_[0]);
|
|
EXPECT_THAT(ret, IsPosixErrorOkMatcher());
|
|
|
|
EXPECT_THAT(close_range(fds_[0], fds_[0], flags), SyscallSucceeds());
|
|
|
|
ret = ReadAllFd(fds_[0]);
|
|
EXPECT_THAT(ret, PosixErrorIs(EBADF));
|
|
}
|
|
|
|
// Test to confirm that calling close_range twice on the same range does not
|
|
// cause errors.
|
|
TEST_F(CloseRangeTest, CallCloseRangeTwice) {
|
|
SKIP_IF(!IsRunningOnGvisor() && close_range(1, 0, 0) < 0 && errno == ENOSYS);
|
|
int num_files_in_range = 10;
|
|
unsigned int flags = 0;
|
|
|
|
CreateFiles(num_files_in_range);
|
|
OpenFilesRdwr();
|
|
|
|
for (int fd : fds_) {
|
|
auto ret = ReadAllFd(fd);
|
|
EXPECT_THAT(ret, IsPosixErrorOkMatcher());
|
|
}
|
|
|
|
EXPECT_THAT(close_range(fds_[0], fds_[num_files_in_range - 1], flags),
|
|
SyscallSucceeds());
|
|
for (int fd : fds_) {
|
|
auto ret = ReadAllFd(fd);
|
|
EXPECT_THAT(ret, PosixErrorIs(EBADF));
|
|
}
|
|
|
|
EXPECT_THAT(close_range(fds_[0], fds_[num_files_in_range - 1], flags),
|
|
SyscallSucceeds());
|
|
}
|
|
|
|
// Test that using CLOEXEC flag does not close the file for this process.
|
|
TEST_F(CloseRangeTest, CloexecFlagTest) {
|
|
SKIP_IF(!IsRunningOnGvisor() && close_range(1, 0, 0) < 0 && errno == ENOSYS);
|
|
int num_files_in_range = 10;
|
|
unsigned int flags = CLOSE_RANGE_CLOEXEC;
|
|
|
|
// CLOEXEC is only available on >=5.11 kernels.
|
|
SKIP_IF(close_range(1, 0, flags) < 0 && errno == EINVAL);
|
|
|
|
CreateFiles(num_files_in_range);
|
|
OpenFilesRdwr();
|
|
|
|
for (int fd : fds_) {
|
|
auto ret = ReadAllFd(fd);
|
|
EXPECT_THAT(ret, IsPosixErrorOkMatcher());
|
|
}
|
|
|
|
EXPECT_THAT(close_range(fds_[0], fds_[num_files_in_range - 1], flags),
|
|
SyscallSucceeds());
|
|
for (int fd : fds_) {
|
|
auto ret = ReadAllFd(fd);
|
|
EXPECT_THAT(ret, IsPosixErrorOkMatcher());
|
|
}
|
|
}
|
|
|
|
// Test that using UNSHARE flag still properly closes the files.
|
|
TEST_F(CloseRangeTest, UnshareFlagTest) {
|
|
SKIP_IF(!IsRunningOnGvisor() && close_range(1, 0, 0) < 0 && errno == ENOSYS);
|
|
int num_files_in_range = 10;
|
|
unsigned int flags = CLOSE_RANGE_UNSHARE;
|
|
|
|
CreateFiles(num_files_in_range);
|
|
OpenFilesRdwr();
|
|
|
|
for (int fd : fds_) {
|
|
auto ret = ReadAllFd(fd);
|
|
EXPECT_THAT(ret, IsPosixErrorOkMatcher());
|
|
}
|
|
|
|
EXPECT_THAT(close_range(fds_[0], fds_[num_files_in_range - 1], flags),
|
|
SyscallSucceeds());
|
|
for (int fd : fds_) {
|
|
auto ret = ReadAllFd(fd);
|
|
EXPECT_THAT(ret, PosixErrorIs(EBADF));
|
|
}
|
|
}
|
|
|
|
// Test that using the UNSHARE flag and closing files at the start of the range
|
|
// still leaves the latter files opened.
|
|
TEST_F(CloseRangeTest, UnshareFlagAndCloseRangeAtStart) {
|
|
SKIP_IF(!IsRunningOnGvisor() && close_range(1, 0, 0) < 0 && errno == ENOSYS);
|
|
int num_files_in_range = 10;
|
|
unsigned int flags = CLOSE_RANGE_UNSHARE;
|
|
|
|
CreateFiles(num_files_in_range);
|
|
OpenFilesRdwr();
|
|
|
|
for (int fd : fds_) {
|
|
auto ret = ReadAllFd(fd);
|
|
EXPECT_THAT(ret, IsPosixErrorOkMatcher());
|
|
}
|
|
|
|
size_t range_split = 5;
|
|
EXPECT_THAT(close_range(fds_[0], fds_[range_split - 1], flags),
|
|
SyscallSucceeds());
|
|
for (int fd : std::vector(fds_.begin(), fds_.begin() + range_split)) {
|
|
auto ret = ReadAllFd(fd);
|
|
EXPECT_THAT(ret, PosixErrorIs(EBADF));
|
|
}
|
|
for (int fd : std::vector(fds_.begin() + range_split, fds_.end())) {
|
|
auto ret = ReadAllFd(fd);
|
|
EXPECT_THAT(ret, IsPosixErrorOkMatcher());
|
|
}
|
|
}
|
|
|
|
// Test that using the UNSHARE flag and closing files at the end of the range
|
|
// still leaves the earlier files opened.
|
|
TEST_F(CloseRangeTest, UnshareFlagAndCloseRangeAtEnd) {
|
|
SKIP_IF(!IsRunningOnGvisor() && close_range(1, 0, 0) < 0 && errno == ENOSYS);
|
|
int num_files_in_range = 10;
|
|
unsigned int flags = CLOSE_RANGE_UNSHARE;
|
|
|
|
CreateFiles(num_files_in_range);
|
|
OpenFilesRdwr();
|
|
|
|
for (int fd : fds_) {
|
|
auto ret = ReadAllFd(fd);
|
|
EXPECT_THAT(ret, IsPosixErrorOkMatcher());
|
|
}
|
|
|
|
size_t range_split = 5;
|
|
EXPECT_THAT(
|
|
close_range(fds_[range_split], fds_[num_files_in_range - 1], flags),
|
|
SyscallSucceeds());
|
|
for (int fd : std::vector(fds_.begin(), fds_.begin() + range_split)) {
|
|
auto ret = ReadAllFd(fd);
|
|
EXPECT_THAT(ret, IsPosixErrorOkMatcher());
|
|
}
|
|
for (int fd : std::vector(fds_.begin() + range_split, fds_.end())) {
|
|
auto ret = ReadAllFd(fd);
|
|
EXPECT_THAT(ret, PosixErrorIs(EBADF));
|
|
}
|
|
}
|
|
|
|
// Test that using both CLOEXEC and UNSHARE flags does not close files for this
|
|
// process.
|
|
TEST_F(CloseRangeTest, CloexecAndUnshareFlagTest) {
|
|
SKIP_IF(!IsRunningOnGvisor() && close_range(1, 0, 0) < 0 && errno == ENOSYS);
|
|
int num_files_in_range = 10;
|
|
unsigned int flags = CLOSE_RANGE_CLOEXEC | CLOSE_RANGE_UNSHARE;
|
|
|
|
// CLOEXEC is only available on >=5.11 kernels.
|
|
SKIP_IF(close_range(1, 0, flags) < 0 && errno == EINVAL);
|
|
|
|
CreateFiles(num_files_in_range);
|
|
OpenFilesRdwr();
|
|
|
|
for (int fd : fds_) {
|
|
auto ret = ReadAllFd(fd);
|
|
EXPECT_THAT(ret, IsPosixErrorOkMatcher());
|
|
}
|
|
|
|
EXPECT_THAT(close_range(fds_[0], fds_[num_files_in_range - 1], flags),
|
|
SyscallSucceeds());
|
|
for (int fd : fds_) {
|
|
auto ret = ReadAllFd(fd);
|
|
EXPECT_THAT(ret, IsPosixErrorOkMatcher());
|
|
}
|
|
}
|
|
|
|
// Test that calling with invalid range does not succeed.
|
|
TEST_F(CloseRangeTest, RangeFirstGreaterThanLast) {
|
|
SKIP_IF(!IsRunningOnGvisor() && close_range(1, 0, 0) < 0 && errno == ENOSYS);
|
|
int num_files_in_range = 10;
|
|
unsigned int flags = 0;
|
|
|
|
CreateFiles(num_files_in_range);
|
|
OpenFilesRdwr();
|
|
|
|
EXPECT_THAT(close_range(fds_[num_files_in_range - 1], fds_[0], flags),
|
|
SyscallFailsWithErrno(EINVAL));
|
|
}
|
|
|
|
// Test that calling with maximum last argument succeeds and closes all fds in
|
|
// the range.
|
|
TEST_F(CloseRangeTest, RangeMaxLast) {
|
|
SKIP_IF(!IsRunningOnGvisor() && close_range(1, 0, 0) < 0 && errno == ENOSYS);
|
|
int num_files_in_range = 10;
|
|
unsigned int flags = 0;
|
|
|
|
CreateFiles(num_files_in_range);
|
|
OpenFilesRdwr();
|
|
|
|
EXPECT_THAT(close_range(fds_[0], -1, flags), SyscallSucceeds());
|
|
for (int fd : fds_) {
|
|
auto ret = ReadAllFd(fd);
|
|
EXPECT_THAT(ret, PosixErrorIs(EBADF));
|
|
}
|
|
}
|
|
|
|
// Test that calling with invalid flags does not succeed.
|
|
TEST_F(CloseRangeTest, InvalidFlags) {
|
|
SKIP_IF(!IsRunningOnGvisor() && close_range(1, 0, 0) < 0 && errno == ENOSYS);
|
|
int num_files_in_range = 10;
|
|
|
|
CreateFiles(num_files_in_range);
|
|
OpenFilesRdwr();
|
|
|
|
unsigned int flags = CLOSE_RANGE_CLOEXEC | CLOSE_RANGE_UNSHARE | 0xF;
|
|
EXPECT_THAT(close_range(fds_[0], fds_[num_files_in_range - 1], flags),
|
|
SyscallFailsWithErrno(EINVAL));
|
|
|
|
flags = 0xF0;
|
|
EXPECT_THAT(close_range(fds_[0], fds_[num_files_in_range - 1], flags),
|
|
SyscallFailsWithErrno(EINVAL));
|
|
|
|
flags = CLOSE_RANGE_CLOEXEC | 0xF00;
|
|
EXPECT_THAT(close_range(fds_[0], fds_[num_files_in_range - 1], flags),
|
|
SyscallFailsWithErrno(EINVAL));
|
|
}
|
|
|
|
// Test that calling close_range concurrently while creating new files yields
|
|
// expected results.
|
|
TEST_F(CloseRangeTest, ConcurrentCalls) {
|
|
SKIP_IF(!IsRunningOnGvisor() && close_range(1, 0, 0) < 0 && errno == ENOSYS);
|
|
const int num_files_in_range = 10;
|
|
const unsigned int flags = CLOSE_RANGE_UNSHARE;
|
|
const int num_threads = 100;
|
|
std::unique_ptr<ScopedThread> threads[num_threads];
|
|
|
|
CreateFiles(num_files_in_range);
|
|
OpenFilesRdwr();
|
|
|
|
auto cr_call = []() {
|
|
EXPECT_THAT(close_range(num_files_in_range / 2,
|
|
num_files_in_range + num_threads, flags),
|
|
SyscallSucceeds());
|
|
};
|
|
auto open_file_call = []() {
|
|
auto file = NewTempAbsPath();
|
|
EXPECT_THAT(open(file.c_str(), O_CREAT, 0644), SyscallSucceeds());
|
|
};
|
|
|
|
for (int i = 0; i < num_threads; i++) {
|
|
if (i % 2 == 0) {
|
|
threads[i] = std::make_unique<ScopedThread>(cr_call);
|
|
} else {
|
|
threads[i] = std::make_unique<ScopedThread>(open_file_call);
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
} // namespace testing
|
|
} // namespace gvisor
|