2019-04-29 21:25:05 +00:00
|
|
|
// Copyright 2018 The gVisor Authors.
|
2018-12-10 22:41:40 +00:00
|
|
|
//
|
|
|
|
// 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/ipc.h>
|
|
|
|
#include <sys/mman.h>
|
|
|
|
#include <sys/shm.h>
|
|
|
|
#include <sys/types.h>
|
|
|
|
|
|
|
|
#include "absl/time/clock.h"
|
|
|
|
#include "test/util/multiprocess_util.h"
|
|
|
|
#include "test/util/posix_error.h"
|
|
|
|
#include "test/util/temp_path.h"
|
|
|
|
#include "test/util/test_util.h"
|
|
|
|
|
|
|
|
namespace gvisor {
|
|
|
|
namespace testing {
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
using ::testing::_;
|
|
|
|
|
|
|
|
const uint64_t kAllocSize = kPageSize * 128ULL;
|
|
|
|
|
|
|
|
PosixErrorOr<char*> Shmat(int shmid, const void* shmaddr, int shmflg) {
|
|
|
|
const intptr_t addr =
|
|
|
|
reinterpret_cast<intptr_t>(shmat(shmid, shmaddr, shmflg));
|
|
|
|
if (addr == -1) {
|
|
|
|
return PosixError(errno, "shmat() failed");
|
|
|
|
}
|
|
|
|
return reinterpret_cast<char*>(addr);
|
|
|
|
}
|
|
|
|
|
|
|
|
PosixError Shmdt(const char* shmaddr) {
|
|
|
|
const int ret = shmdt(shmaddr);
|
|
|
|
if (ret == -1) {
|
|
|
|
return PosixError(errno, "shmdt() failed");
|
|
|
|
}
|
|
|
|
return NoError();
|
|
|
|
}
|
|
|
|
|
|
|
|
template <typename T>
|
|
|
|
PosixErrorOr<int> Shmctl(int shmid, int cmd, T* buf) {
|
|
|
|
int ret = shmctl(shmid, cmd, reinterpret_cast<struct shmid_ds*>(buf));
|
|
|
|
if (ret == -1) {
|
|
|
|
return PosixError(errno, "shmctl() failed");
|
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2018-12-13 22:24:00 +00:00
|
|
|
// ShmSegment is a RAII object for automatically cleaning up shm segments.
|
|
|
|
class ShmSegment {
|
|
|
|
public:
|
|
|
|
explicit ShmSegment(int id) : id_(id) {}
|
|
|
|
|
|
|
|
~ShmSegment() {
|
|
|
|
if (id_ >= 0) {
|
|
|
|
EXPECT_NO_ERRNO(Rmid());
|
|
|
|
id_ = -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ShmSegment(ShmSegment&& other) : id_(other.release()) {}
|
|
|
|
|
|
|
|
ShmSegment& operator=(ShmSegment&& other) {
|
|
|
|
id_ = other.release();
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
ShmSegment(ShmSegment const& other) = delete;
|
|
|
|
ShmSegment& operator=(ShmSegment const& other) = delete;
|
|
|
|
|
|
|
|
int id() const { return id_; }
|
|
|
|
|
|
|
|
int release() {
|
|
|
|
int id = id_;
|
|
|
|
id_ = -1;
|
|
|
|
return id;
|
|
|
|
}
|
|
|
|
|
|
|
|
PosixErrorOr<int> Rmid() {
|
|
|
|
RETURN_IF_ERRNO(Shmctl<void>(id_, IPC_RMID, nullptr));
|
|
|
|
return release();
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
int id_ = -1;
|
|
|
|
};
|
|
|
|
|
|
|
|
PosixErrorOr<int> ShmgetRaw(key_t key, size_t size, int shmflg) {
|
|
|
|
int id = shmget(key, size, shmflg);
|
|
|
|
if (id == -1) {
|
|
|
|
return PosixError(errno, "shmget() failed");
|
|
|
|
}
|
|
|
|
return id;
|
|
|
|
}
|
|
|
|
|
|
|
|
PosixErrorOr<ShmSegment> Shmget(key_t key, size_t size, int shmflg) {
|
|
|
|
ASSIGN_OR_RETURN_ERRNO(int id, ShmgetRaw(key, size, shmflg));
|
|
|
|
return ShmSegment(id);
|
|
|
|
}
|
|
|
|
|
2018-12-10 22:41:40 +00:00
|
|
|
TEST(ShmTest, AttachDetach) {
|
2018-12-13 22:24:00 +00:00
|
|
|
const ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE(
|
2018-12-10 22:41:40 +00:00
|
|
|
Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777));
|
|
|
|
struct shmid_ds attr;
|
2018-12-13 22:24:00 +00:00
|
|
|
ASSERT_NO_ERRNO(Shmctl(shm.id(), IPC_STAT, &attr));
|
2018-12-10 22:41:40 +00:00
|
|
|
EXPECT_EQ(attr.shm_segsz, kAllocSize);
|
|
|
|
EXPECT_EQ(attr.shm_nattch, 0);
|
|
|
|
|
2018-12-13 22:24:00 +00:00
|
|
|
const char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0));
|
|
|
|
ASSERT_NO_ERRNO(Shmctl(shm.id(), IPC_STAT, &attr));
|
2018-12-10 22:41:40 +00:00
|
|
|
EXPECT_EQ(attr.shm_nattch, 1);
|
|
|
|
|
2018-12-13 22:24:00 +00:00
|
|
|
const char* addr2 = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0));
|
|
|
|
ASSERT_NO_ERRNO(Shmctl(shm.id(), IPC_STAT, &attr));
|
2018-12-10 22:41:40 +00:00
|
|
|
EXPECT_EQ(attr.shm_nattch, 2);
|
|
|
|
|
|
|
|
ASSERT_NO_ERRNO(Shmdt(addr));
|
2018-12-13 22:24:00 +00:00
|
|
|
ASSERT_NO_ERRNO(Shmctl(shm.id(), IPC_STAT, &attr));
|
2018-12-10 22:41:40 +00:00
|
|
|
EXPECT_EQ(attr.shm_nattch, 1);
|
|
|
|
|
|
|
|
ASSERT_NO_ERRNO(Shmdt(addr2));
|
2018-12-13 22:24:00 +00:00
|
|
|
ASSERT_NO_ERRNO(Shmctl(shm.id(), IPC_STAT, &attr));
|
2018-12-10 22:41:40 +00:00
|
|
|
EXPECT_EQ(attr.shm_nattch, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(ShmTest, LookupByKey) {
|
|
|
|
const TempPath keyfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
|
|
|
|
const key_t key = ftok(keyfile.path().c_str(), 1);
|
2018-12-13 22:24:00 +00:00
|
|
|
const ShmSegment shm =
|
2018-12-10 22:41:40 +00:00
|
|
|
ASSERT_NO_ERRNO_AND_VALUE(Shmget(key, kAllocSize, IPC_CREAT | 0777));
|
2018-12-13 22:24:00 +00:00
|
|
|
const int id2 = ASSERT_NO_ERRNO_AND_VALUE(ShmgetRaw(key, kAllocSize, 0777));
|
|
|
|
EXPECT_EQ(shm.id(), id2);
|
2018-12-10 22:41:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
TEST(ShmTest, DetachedSegmentsPersist) {
|
2018-12-13 22:24:00 +00:00
|
|
|
const ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE(
|
2018-12-10 22:41:40 +00:00
|
|
|
Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777));
|
2018-12-13 22:24:00 +00:00
|
|
|
char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0));
|
2018-12-10 22:41:40 +00:00
|
|
|
addr[0] = 'x';
|
|
|
|
ASSERT_NO_ERRNO(Shmdt(addr));
|
|
|
|
|
|
|
|
// We should be able to re-attach to the same segment and get our data back.
|
2018-12-13 22:24:00 +00:00
|
|
|
addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0));
|
2018-12-10 22:41:40 +00:00
|
|
|
EXPECT_EQ(addr[0], 'x');
|
|
|
|
ASSERT_NO_ERRNO(Shmdt(addr));
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(ShmTest, MultipleDetachFails) {
|
2018-12-13 22:24:00 +00:00
|
|
|
const ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE(
|
2018-12-10 22:41:40 +00:00
|
|
|
Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777));
|
2018-12-13 22:24:00 +00:00
|
|
|
const char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0));
|
2018-12-10 22:41:40 +00:00
|
|
|
ASSERT_NO_ERRNO(Shmdt(addr));
|
|
|
|
EXPECT_THAT(Shmdt(addr), PosixErrorIs(EINVAL, _));
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(ShmTest, IpcStat) {
|
|
|
|
const TempPath keyfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
|
|
|
|
const key_t key = ftok(keyfile.path().c_str(), 1);
|
|
|
|
|
|
|
|
const time_t start = time(nullptr);
|
|
|
|
|
2018-12-13 22:24:00 +00:00
|
|
|
const ShmSegment shm =
|
2018-12-10 22:41:40 +00:00
|
|
|
ASSERT_NO_ERRNO_AND_VALUE(Shmget(key, kAllocSize, IPC_CREAT | 0777));
|
|
|
|
|
|
|
|
const uid_t uid = getuid();
|
|
|
|
const gid_t gid = getgid();
|
|
|
|
const pid_t pid = getpid();
|
|
|
|
|
|
|
|
struct shmid_ds attr;
|
2018-12-13 22:24:00 +00:00
|
|
|
ASSERT_NO_ERRNO(Shmctl(shm.id(), IPC_STAT, &attr));
|
2018-12-10 22:41:40 +00:00
|
|
|
|
|
|
|
EXPECT_EQ(attr.shm_perm.__key, key);
|
|
|
|
EXPECT_EQ(attr.shm_perm.uid, uid);
|
|
|
|
EXPECT_EQ(attr.shm_perm.gid, gid);
|
|
|
|
EXPECT_EQ(attr.shm_perm.cuid, uid);
|
|
|
|
EXPECT_EQ(attr.shm_perm.cgid, gid);
|
|
|
|
EXPECT_EQ(attr.shm_perm.mode, 0777);
|
|
|
|
|
|
|
|
EXPECT_EQ(attr.shm_segsz, kAllocSize);
|
|
|
|
|
|
|
|
EXPECT_EQ(attr.shm_atime, 0);
|
|
|
|
EXPECT_EQ(attr.shm_dtime, 0);
|
|
|
|
|
|
|
|
// Change time is set on creation.
|
|
|
|
EXPECT_GE(attr.shm_ctime, start);
|
|
|
|
|
|
|
|
EXPECT_EQ(attr.shm_cpid, pid);
|
|
|
|
EXPECT_EQ(attr.shm_lpid, 0);
|
|
|
|
|
|
|
|
EXPECT_EQ(attr.shm_nattch, 0);
|
|
|
|
|
|
|
|
// The timestamps only have a resolution of seconds; slow down so we actually
|
|
|
|
// see the timestamps change.
|
|
|
|
absl::SleepFor(absl::Seconds(1));
|
|
|
|
const time_t pre_attach = time(nullptr);
|
|
|
|
|
2018-12-13 22:24:00 +00:00
|
|
|
const char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0));
|
|
|
|
ASSERT_NO_ERRNO(Shmctl(shm.id(), IPC_STAT, &attr));
|
2018-12-10 22:41:40 +00:00
|
|
|
|
|
|
|
EXPECT_GE(attr.shm_atime, pre_attach);
|
|
|
|
EXPECT_EQ(attr.shm_dtime, 0);
|
|
|
|
EXPECT_LT(attr.shm_ctime, pre_attach);
|
|
|
|
EXPECT_EQ(attr.shm_lpid, pid);
|
|
|
|
EXPECT_EQ(attr.shm_nattch, 1);
|
|
|
|
|
|
|
|
absl::SleepFor(absl::Seconds(1));
|
|
|
|
const time_t pre_detach = time(nullptr);
|
|
|
|
|
|
|
|
ASSERT_NO_ERRNO(Shmdt(addr));
|
2018-12-13 22:24:00 +00:00
|
|
|
ASSERT_NO_ERRNO(Shmctl(shm.id(), IPC_STAT, &attr));
|
2018-12-10 22:41:40 +00:00
|
|
|
|
|
|
|
EXPECT_LT(attr.shm_atime, pre_detach);
|
|
|
|
EXPECT_GE(attr.shm_dtime, pre_detach);
|
|
|
|
EXPECT_LT(attr.shm_ctime, pre_detach);
|
|
|
|
EXPECT_EQ(attr.shm_lpid, pid);
|
|
|
|
EXPECT_EQ(attr.shm_nattch, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(ShmTest, ShmStat) {
|
|
|
|
// This test relies on the segment we create to be the first one on the
|
|
|
|
// system, causing it to occupy slot 1. We can't reasonably expect this on a
|
|
|
|
// general Linux host.
|
|
|
|
SKIP_IF(!IsRunningOnGvisor());
|
|
|
|
|
2018-12-13 22:24:00 +00:00
|
|
|
const ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE(
|
|
|
|
Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777));
|
2018-12-10 22:41:40 +00:00
|
|
|
struct shmid_ds attr;
|
|
|
|
ASSERT_NO_ERRNO(Shmctl(1, SHM_STAT, &attr));
|
|
|
|
// This does the same thing as IPC_STAT, so only test that the syscall
|
|
|
|
// succeeds here.
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(ShmTest, IpcInfo) {
|
|
|
|
struct shminfo info;
|
|
|
|
ASSERT_NO_ERRNO(Shmctl(0, IPC_INFO, &info));
|
|
|
|
|
|
|
|
EXPECT_EQ(info.shmmin, 1); // This is always 1, according to the man page.
|
|
|
|
EXPECT_GT(info.shmmax, info.shmmin);
|
|
|
|
EXPECT_GT(info.shmmni, 0);
|
|
|
|
EXPECT_GT(info.shmseg, 0);
|
|
|
|
EXPECT_GT(info.shmall, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(ShmTest, ShmInfo) {
|
|
|
|
struct shm_info info;
|
|
|
|
|
|
|
|
// We generally can't know what other processes on a linux machine
|
|
|
|
// does with shared memory segments, so we can't test specific
|
|
|
|
// numbers on Linux. When running under gvisor, we're guaranteed to
|
|
|
|
// be the only ones using shm, so we can easily verify machine-wide
|
|
|
|
// numbers.
|
|
|
|
if (IsRunningOnGvisor()) {
|
|
|
|
ASSERT_NO_ERRNO(Shmctl(0, SHM_INFO, &info));
|
|
|
|
EXPECT_EQ(info.used_ids, 0);
|
|
|
|
EXPECT_EQ(info.shm_tot, 0);
|
|
|
|
EXPECT_EQ(info.shm_rss, 0);
|
|
|
|
EXPECT_EQ(info.shm_swp, 0);
|
|
|
|
}
|
|
|
|
|
2018-12-13 22:24:00 +00:00
|
|
|
const ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE(
|
2018-12-10 22:41:40 +00:00
|
|
|
Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777));
|
2018-12-13 22:24:00 +00:00
|
|
|
const char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0));
|
2018-12-10 22:41:40 +00:00
|
|
|
|
|
|
|
ASSERT_NO_ERRNO(Shmctl(1, SHM_INFO, &info));
|
|
|
|
|
|
|
|
if (IsRunningOnGvisor()) {
|
2018-12-13 22:24:00 +00:00
|
|
|
ASSERT_NO_ERRNO(Shmctl(shm.id(), SHM_INFO, &info));
|
2018-12-10 22:41:40 +00:00
|
|
|
EXPECT_EQ(info.used_ids, 1);
|
|
|
|
EXPECT_EQ(info.shm_tot, kAllocSize / kPageSize);
|
|
|
|
EXPECT_EQ(info.shm_rss, kAllocSize / kPageSize);
|
|
|
|
EXPECT_EQ(info.shm_swp, 0); // Gvisor currently never swaps.
|
|
|
|
}
|
|
|
|
|
|
|
|
ASSERT_NO_ERRNO(Shmdt(addr));
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(ShmTest, ShmCtlSet) {
|
2018-12-13 22:24:00 +00:00
|
|
|
const ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE(
|
2018-12-10 22:41:40 +00:00
|
|
|
Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777));
|
2018-12-13 22:24:00 +00:00
|
|
|
const char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0));
|
2018-12-10 22:41:40 +00:00
|
|
|
|
|
|
|
struct shmid_ds attr;
|
2018-12-13 22:24:00 +00:00
|
|
|
ASSERT_NO_ERRNO(Shmctl(shm.id(), IPC_STAT, &attr));
|
2018-12-10 22:41:40 +00:00
|
|
|
ASSERT_EQ(attr.shm_perm.mode, 0777);
|
|
|
|
|
|
|
|
attr.shm_perm.mode = 0766;
|
2018-12-13 22:24:00 +00:00
|
|
|
ASSERT_NO_ERRNO(Shmctl(shm.id(), IPC_SET, &attr));
|
2018-12-10 22:41:40 +00:00
|
|
|
|
2018-12-13 22:24:00 +00:00
|
|
|
ASSERT_NO_ERRNO(Shmctl(shm.id(), IPC_STAT, &attr));
|
2018-12-10 22:41:40 +00:00
|
|
|
ASSERT_EQ(attr.shm_perm.mode, 0766);
|
|
|
|
|
|
|
|
ASSERT_NO_ERRNO(Shmdt(addr));
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(ShmTest, RemovedSegmentsAreMarkedDeleted) {
|
2018-12-13 22:24:00 +00:00
|
|
|
ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE(
|
2018-12-10 22:41:40 +00:00
|
|
|
Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777));
|
2018-12-13 22:24:00 +00:00
|
|
|
const char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0));
|
|
|
|
const int id = ASSERT_NO_ERRNO_AND_VALUE(shm.Rmid());
|
2018-12-10 22:41:40 +00:00
|
|
|
struct shmid_ds attr;
|
|
|
|
ASSERT_NO_ERRNO(Shmctl(id, IPC_STAT, &attr));
|
|
|
|
EXPECT_NE(attr.shm_perm.mode & SHM_DEST, 0);
|
|
|
|
ASSERT_NO_ERRNO(Shmdt(addr));
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(ShmTest, RemovedSegmentsAreDestroyed) {
|
2018-12-13 22:24:00 +00:00
|
|
|
ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE(
|
2018-12-10 22:41:40 +00:00
|
|
|
Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777));
|
2018-12-13 22:24:00 +00:00
|
|
|
const char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0));
|
2018-12-10 22:41:40 +00:00
|
|
|
|
|
|
|
const uint64_t alloc_pages = kAllocSize / kPageSize;
|
|
|
|
|
|
|
|
struct shm_info info;
|
2018-12-13 22:24:00 +00:00
|
|
|
ASSERT_NO_ERRNO(Shmctl(0 /*ignored*/, SHM_INFO, &info));
|
2018-12-10 22:41:40 +00:00
|
|
|
const uint64_t before = info.shm_tot;
|
|
|
|
|
2018-12-13 22:24:00 +00:00
|
|
|
ASSERT_NO_ERRNO(shm.Rmid());
|
2018-12-10 22:41:40 +00:00
|
|
|
ASSERT_NO_ERRNO(Shmdt(addr));
|
|
|
|
|
2018-12-13 22:24:00 +00:00
|
|
|
ASSERT_NO_ERRNO(Shmctl(0 /*ignored*/, SHM_INFO, &info));
|
|
|
|
if (IsRunningOnGvisor()) {
|
|
|
|
// No guarantees on system-wide shm memory usage on a generic linux host.
|
|
|
|
const uint64_t after = info.shm_tot;
|
|
|
|
EXPECT_EQ(after, before - alloc_pages);
|
|
|
|
}
|
2018-12-10 22:41:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
TEST(ShmTest, AllowsAttachToRemovedSegmentWithRefs) {
|
2018-12-13 22:24:00 +00:00
|
|
|
ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE(
|
2018-12-10 22:41:40 +00:00
|
|
|
Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777));
|
2018-12-13 22:24:00 +00:00
|
|
|
const char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0));
|
|
|
|
const int id = ASSERT_NO_ERRNO_AND_VALUE(shm.Rmid());
|
2018-12-10 22:41:40 +00:00
|
|
|
const char* addr2 = ASSERT_NO_ERRNO_AND_VALUE(Shmat(id, nullptr, 0));
|
|
|
|
ASSERT_NO_ERRNO(Shmdt(addr));
|
|
|
|
ASSERT_NO_ERRNO(Shmdt(addr2));
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(ShmTest, RemovedSegmentsAreNotDiscoverable) {
|
|
|
|
const TempPath keyfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
|
|
|
|
const key_t key = ftok(keyfile.path().c_str(), 1);
|
2018-12-13 22:24:00 +00:00
|
|
|
ShmSegment shm =
|
2018-12-10 22:41:40 +00:00
|
|
|
ASSERT_NO_ERRNO_AND_VALUE(Shmget(key, kAllocSize, IPC_CREAT | 0777));
|
2018-12-13 22:24:00 +00:00
|
|
|
ASSERT_NO_ERRNO(shm.Rmid());
|
2018-12-10 22:41:40 +00:00
|
|
|
EXPECT_THAT(Shmget(key, kAllocSize, 0777), PosixErrorIs(ENOENT, _));
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(ShmDeathTest, ReadonlySegment) {
|
|
|
|
SetupGvisorDeathTest();
|
2018-12-13 22:24:00 +00:00
|
|
|
const ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE(
|
2018-12-10 22:41:40 +00:00
|
|
|
Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777));
|
2018-12-13 22:24:00 +00:00
|
|
|
char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, SHM_RDONLY));
|
2018-12-10 22:41:40 +00:00
|
|
|
// Reading succeeds.
|
|
|
|
static_cast<void>(addr[0]);
|
|
|
|
// Writing fails.
|
|
|
|
EXPECT_EXIT(addr[0] = 'x', ::testing::KilledBySignal(SIGSEGV), "");
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(ShmDeathTest, SegmentNotAccessibleAfterDetach) {
|
|
|
|
// This test is susceptible to races with concurrent mmaps running in parallel
|
|
|
|
// gtest threads since the test relies on the address freed during a shm
|
|
|
|
// segment destruction to remain unused. We run the test body in a forked
|
|
|
|
// child to guarantee a single-threaded context to avoid this.
|
|
|
|
|
|
|
|
SetupGvisorDeathTest();
|
|
|
|
|
|
|
|
const auto rest = [&] {
|
2018-12-13 22:24:00 +00:00
|
|
|
ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE(
|
2018-12-10 22:41:40 +00:00
|
|
|
Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777));
|
2018-12-13 22:24:00 +00:00
|
|
|
char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0));
|
|
|
|
|
|
|
|
// Mark the segment as destroyed so it's automatically cleaned up when we
|
|
|
|
// crash below. We can't rely on the standard cleanup since the destructor
|
|
|
|
// will not run after the SIGSEGV. Note that this doesn't destroy the
|
|
|
|
// segment immediately since we're still attached to it.
|
|
|
|
ASSERT_NO_ERRNO(shm.Rmid());
|
|
|
|
|
2018-12-10 22:41:40 +00:00
|
|
|
addr[0] = 'x';
|
|
|
|
ASSERT_NO_ERRNO(Shmdt(addr));
|
|
|
|
|
|
|
|
// This access should cause a SIGSEGV.
|
|
|
|
addr[0] = 'x';
|
|
|
|
};
|
|
|
|
|
|
|
|
EXPECT_THAT(InForkedProcess(rest),
|
|
|
|
IsPosixErrorOkAndHolds(W_EXITCODE(0, SIGSEGV)));
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(ShmTest, RequestingSegmentSmallerThanSHMMINFails) {
|
|
|
|
struct shminfo info;
|
|
|
|
ASSERT_NO_ERRNO(Shmctl(0, IPC_INFO, &info));
|
|
|
|
const uint64_t size = info.shmmin - 1;
|
|
|
|
EXPECT_THAT(Shmget(IPC_PRIVATE, size, IPC_CREAT | 0777),
|
|
|
|
PosixErrorIs(EINVAL, _));
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(ShmTest, RequestingSegmentLargerThanSHMMAXFails) {
|
|
|
|
struct shminfo info;
|
|
|
|
ASSERT_NO_ERRNO(Shmctl(0, IPC_INFO, &info));
|
|
|
|
const uint64_t size = info.shmmax + kPageSize;
|
|
|
|
EXPECT_THAT(Shmget(IPC_PRIVATE, size, IPC_CREAT | 0777),
|
|
|
|
PosixErrorIs(EINVAL, _));
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(ShmTest, RequestingUnalignedSizeSucceeds) {
|
|
|
|
EXPECT_NO_ERRNO(Shmget(IPC_PRIVATE, 4097, IPC_CREAT | 0777));
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(ShmTest, RequestingDuplicateCreationFails) {
|
|
|
|
const TempPath keyfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
|
|
|
|
const key_t key = ftok(keyfile.path().c_str(), 1);
|
2018-12-13 22:24:00 +00:00
|
|
|
const ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE(
|
2018-12-10 22:41:40 +00:00
|
|
|
Shmget(key, kAllocSize, IPC_CREAT | IPC_EXCL | 0777));
|
|
|
|
EXPECT_THAT(Shmget(key, kAllocSize, IPC_CREAT | IPC_EXCL | 0777),
|
|
|
|
PosixErrorIs(EEXIST, _));
|
|
|
|
}
|
|
|
|
|
2018-12-17 22:11:10 +00:00
|
|
|
TEST(ShmTest, NonExistentSegmentsAreNotFound) {
|
|
|
|
const TempPath keyfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
|
|
|
|
const key_t key = ftok(keyfile.path().c_str(), 1);
|
|
|
|
// Do not request creation.
|
|
|
|
EXPECT_THAT(Shmget(key, kAllocSize, 0777), PosixErrorIs(ENOENT, _));
|
|
|
|
}
|
|
|
|
|
2018-12-10 22:41:40 +00:00
|
|
|
TEST(ShmTest, SegmentsSizeFixedOnCreation) {
|
|
|
|
const TempPath keyfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
|
|
|
|
const key_t key = ftok(keyfile.path().c_str(), 1);
|
|
|
|
|
|
|
|
// Base segment.
|
2018-12-13 22:24:00 +00:00
|
|
|
const ShmSegment shm =
|
2018-12-10 22:41:40 +00:00
|
|
|
ASSERT_NO_ERRNO_AND_VALUE(Shmget(key, kAllocSize, IPC_CREAT | 0777));
|
|
|
|
|
|
|
|
// Ask for the same segment at half size. This succeeds.
|
2018-12-13 22:24:00 +00:00
|
|
|
const int id2 =
|
|
|
|
ASSERT_NO_ERRNO_AND_VALUE(ShmgetRaw(key, kAllocSize / 2, 0777));
|
2018-12-10 22:41:40 +00:00
|
|
|
|
|
|
|
// Ask for the same segment at double size.
|
|
|
|
EXPECT_THAT(Shmget(key, kAllocSize * 2, 0777), PosixErrorIs(EINVAL, _));
|
|
|
|
|
2018-12-13 22:24:00 +00:00
|
|
|
char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0));
|
2018-12-10 22:41:40 +00:00
|
|
|
char* addr2 = ASSERT_NO_ERRNO_AND_VALUE(Shmat(id2, nullptr, 0));
|
|
|
|
|
|
|
|
// We have 2 different maps...
|
|
|
|
EXPECT_NE(addr, addr2);
|
|
|
|
|
|
|
|
// ... And both maps are kAllocSize bytes; despite asking for a half-sized
|
|
|
|
// segment for the second map.
|
|
|
|
addr[kAllocSize - 1] = 'x';
|
|
|
|
addr2[kAllocSize - 1] = 'x';
|
|
|
|
|
|
|
|
ASSERT_NO_ERRNO(Shmdt(addr));
|
|
|
|
ASSERT_NO_ERRNO(Shmdt(addr2));
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(ShmTest, PartialUnmap) {
|
2018-12-13 22:24:00 +00:00
|
|
|
const ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE(
|
2018-12-10 22:41:40 +00:00
|
|
|
Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777));
|
2018-12-13 22:24:00 +00:00
|
|
|
char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0));
|
2018-12-10 22:41:40 +00:00
|
|
|
EXPECT_THAT(munmap(addr + (kAllocSize / 4), kAllocSize / 2),
|
|
|
|
SyscallSucceeds());
|
|
|
|
ASSERT_NO_ERRNO(Shmdt(addr));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check that sentry does not panic when asked for a zero-length private shm
|
|
|
|
// segment.
|
|
|
|
TEST(ShmTest, GracefullyFailOnZeroLenSegmentCreation) {
|
|
|
|
EXPECT_THAT(Shmget(IPC_PRIVATE, 0, 0), PosixErrorIs(EINVAL, _));
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(ShmTest, NoDestructionOfAttachedSegmentWithMultipleRmid) {
|
2018-12-13 22:24:00 +00:00
|
|
|
ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE(
|
2018-12-10 22:41:40 +00:00
|
|
|
Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777));
|
2018-12-13 22:24:00 +00:00
|
|
|
char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0));
|
|
|
|
char* addr2 = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0));
|
2018-12-10 22:41:40 +00:00
|
|
|
|
|
|
|
// There should be 2 refs to the segment from the 2 attachments, and a single
|
|
|
|
// self-reference. Mark the segment as destroyed more than 3 times through
|
|
|
|
// shmctl(RMID). If there's a bug with the ref counting, this should cause the
|
|
|
|
// count to drop to zero.
|
2018-12-13 22:24:00 +00:00
|
|
|
int id = shm.release();
|
2018-12-10 22:41:40 +00:00
|
|
|
for (int i = 0; i < 6; ++i) {
|
|
|
|
ASSERT_NO_ERRNO(Shmctl<void>(id, IPC_RMID, nullptr));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Segment should remain accessible.
|
|
|
|
addr[0] = 'x';
|
|
|
|
ASSERT_NO_ERRNO(Shmdt(addr));
|
|
|
|
|
|
|
|
// Segment should remain accessible even after one of the two attachments are
|
|
|
|
// detached.
|
|
|
|
addr2[0] = 'x';
|
|
|
|
ASSERT_NO_ERRNO(Shmdt(addr2));
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
} // namespace testing
|
|
|
|
} // namespace gvisor
|