mirror of
https://github.com/hanzoai/visor.git
synced 2026-09-24 17:04:14 +00:00
The fork() loop in RLIMIT_NPROC tests previously used an infinite
while(1) { sleep(1); } loop followed by an unreachable _exit(1).
This caused a -Wunreachable-code warning. Additionally, invoking
sleep() immediately after a bare fork() can occasionally hang
when test frameworks are run with memory interceptors like TSan.
PiperOrigin-RevId: 957422827
196 lines
6.2 KiB
C++
196 lines
6.2 KiB
C++
// Copyright 2018 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 <linux/capability.h>
|
|
#include <stdlib.h>
|
|
#include <sys/resource.h>
|
|
#include <sys/time.h>
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
#include <unistd.h>
|
|
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "absl/algorithm/container.h"
|
|
#include "absl/strings/str_format.h"
|
|
#include "test/util/capability_util.h"
|
|
#include "test/util/cleanup.h"
|
|
#include "test/util/fs_util.h"
|
|
#include "test/util/posix_error.h"
|
|
#include "test/util/proc_util.h"
|
|
#include "test/util/test_util.h"
|
|
#include "test/util/thread_util.h"
|
|
|
|
namespace gvisor {
|
|
namespace testing {
|
|
|
|
namespace {
|
|
|
|
PosixErrorOr<ProcLimitsEntry> GetProcLimitEntryByType(LimitType limit_type) {
|
|
ASSIGN_OR_RETURN_ERRNO(std::string proc_self_limits,
|
|
GetContents("/proc/self/limits"));
|
|
ASSIGN_OR_RETURN_ERRNO(auto entries, ParseProcLimits(proc_self_limits));
|
|
auto it = absl::c_find_if(entries, [limit_type](const ProcLimitsEntry& v) {
|
|
return v.limit_type == limit_type;
|
|
});
|
|
if (it == entries.end()) {
|
|
return PosixError(ENOENT, absl::StrFormat("limit type \"%s\" not found",
|
|
LimitTypeToString(limit_type)));
|
|
}
|
|
return *it;
|
|
}
|
|
|
|
TEST(RlimitTest, SetRlimitHigher) {
|
|
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_RESOURCE)));
|
|
|
|
struct rlimit rl = {};
|
|
EXPECT_THAT(getrlimit(RLIMIT_NOFILE, &rl), SyscallSucceeds());
|
|
|
|
// Lower the rlimit first, as it may be equal to /proc/sys/fs/nr_open, in
|
|
// which case even users with CAP_SYS_RESOURCE can't raise it.
|
|
rl.rlim_cur--;
|
|
rl.rlim_max--;
|
|
ASSERT_THAT(setrlimit(RLIMIT_NOFILE, &rl), SyscallSucceeds());
|
|
|
|
// Now verify we can read the changed values via /proc/self/limits
|
|
const ProcLimitsEntry limit_entry = ASSERT_NO_ERRNO_AND_VALUE(
|
|
GetProcLimitEntryByType(LimitType::kNumberOfFiles));
|
|
EXPECT_EQ(rl.rlim_cur, limit_entry.cur_limit);
|
|
EXPECT_EQ(rl.rlim_max, limit_entry.max_limit);
|
|
|
|
rl.rlim_max++;
|
|
EXPECT_THAT(setrlimit(RLIMIT_NOFILE, &rl), SyscallSucceeds());
|
|
}
|
|
|
|
TEST(RlimitTest, UnprivilegedSetRlimit) {
|
|
// Drop privileges if necessary.
|
|
AutoCapability cap(CAP_SYS_RESOURCE, false);
|
|
|
|
struct rlimit rl = {};
|
|
rl.rlim_cur = 1000;
|
|
rl.rlim_max = 20000;
|
|
EXPECT_THAT(setrlimit(RLIMIT_NOFILE, &rl), SyscallSucceeds());
|
|
|
|
struct rlimit rl2 = {};
|
|
EXPECT_THAT(getrlimit(RLIMIT_NOFILE, &rl2), SyscallSucceeds());
|
|
EXPECT_EQ(rl.rlim_cur, rl2.rlim_cur);
|
|
EXPECT_EQ(rl.rlim_max, rl2.rlim_max);
|
|
|
|
rl.rlim_max = 100000;
|
|
EXPECT_THAT(setrlimit(RLIMIT_NOFILE, &rl), SyscallFailsWithErrno(EPERM));
|
|
}
|
|
|
|
TEST(RlimitTest, SetSoftRlimitAboveHard) {
|
|
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_RESOURCE)));
|
|
|
|
struct rlimit rl = {};
|
|
EXPECT_THAT(getrlimit(RLIMIT_NOFILE, &rl), SyscallSucceeds());
|
|
|
|
rl.rlim_cur = rl.rlim_max + 1;
|
|
EXPECT_THAT(setrlimit(RLIMIT_NOFILE, &rl), SyscallFailsWithErrno(EINVAL));
|
|
}
|
|
|
|
TEST(RlimitTest, RlimitNProc) {
|
|
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SETUID)));
|
|
|
|
// The native test can be run in a user namespace without a mapping for
|
|
// kNobody or there can be other processes that are running from the kNobody
|
|
// user.
|
|
SKIP_IF(!IsRunningOnGvisor());
|
|
|
|
// Run the test in a sub-thread to avoid changing UID of the current thread.
|
|
ScopedThread([&] {
|
|
constexpr int kNobody = 65534;
|
|
EXPECT_THAT(syscall(SYS_setuid, kNobody), SyscallSucceeds());
|
|
|
|
struct rlimit rl = {};
|
|
EXPECT_THAT(getrlimit(RLIMIT_NPROC, &rl), SyscallSucceeds());
|
|
|
|
constexpr int kNProc = 10;
|
|
rl.rlim_cur = kNProc;
|
|
EXPECT_THAT(setrlimit(RLIMIT_NPROC, &rl), SyscallSucceeds());
|
|
|
|
constexpr int kIterations = 2;
|
|
// Run test actions a few times to check that processes are not leaked.
|
|
for (int iter = 0; iter < kIterations; iter++) {
|
|
pid_t pids[kNProc];
|
|
for (int i = 0; i < kNProc; i++) {
|
|
pid_t pid = fork();
|
|
if (pid == 0) {
|
|
// Child process: Wait passively until the parent terminates us.
|
|
while (true) {
|
|
pause();
|
|
}
|
|
}
|
|
// Parent process: Record the child's PID and continue the test.
|
|
EXPECT_THAT(pid, SyscallSucceeds());
|
|
pids[i] = pid;
|
|
}
|
|
auto cleanup = Cleanup([pids] {
|
|
for (int i = 0; i < kNProc; i++) {
|
|
if (pids[i] < 0) {
|
|
continue;
|
|
}
|
|
EXPECT_THAT(kill(pids[i], SIGKILL), SyscallSucceeds());
|
|
EXPECT_THAT(waitpid(pids[i], nullptr, 0), SyscallSucceeds());
|
|
}
|
|
});
|
|
pid_t pid = fork();
|
|
if (pid == 0) {
|
|
_exit(1);
|
|
}
|
|
EXPECT_THAT(pid, SyscallFailsWithErrno(EAGAIN));
|
|
}
|
|
}).Join();
|
|
}
|
|
|
|
TEST(RlimitTest, SetRlimitRtPrio) {
|
|
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_RESOURCE)));
|
|
|
|
struct rlimit old_rl = {};
|
|
EXPECT_THAT(getrlimit(RLIMIT_RTPRIO, &old_rl), SyscallSucceeds());
|
|
|
|
struct rlimit rl = {
|
|
.rlim_cur = 5,
|
|
.rlim_max = 10,
|
|
};
|
|
int const ret = setrlimit(RLIMIT_RTPRIO, &rl);
|
|
if (ret == -1 && rl.rlim_max > old_rl.rlim_max) {
|
|
// In a non-init user namespace, raising rlim_max above the current hard
|
|
// limit fails with EPERM because CAP_SYS_RESOURCE is required in the
|
|
// initial user namespace.
|
|
EXPECT_EQ(errno, EPERM);
|
|
return;
|
|
}
|
|
EXPECT_THAT(ret, SyscallSucceeds());
|
|
|
|
EXPECT_THAT(getrlimit(RLIMIT_RTPRIO, &rl), SyscallSucceeds());
|
|
EXPECT_EQ(rl.rlim_cur, 5);
|
|
EXPECT_EQ(rl.rlim_max, 10);
|
|
}
|
|
|
|
TEST(RlimitTest, ParseProcPidLimits) {
|
|
auto proc_self_limits =
|
|
ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/self/limits"));
|
|
auto entries = ASSERT_NO_ERRNO_AND_VALUE(ParseProcLimits(proc_self_limits));
|
|
EXPECT_EQ(entries.size(), LimitTypes.size());
|
|
}
|
|
|
|
} // namespace
|
|
|
|
} // namespace testing
|
|
} // namespace gvisor
|