gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/test/syscalls/linux/shm.cc (about)

     1  // Copyright 2018 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  #include <stdio.h>
    16  #include <sys/ipc.h>
    17  #include <sys/mman.h>
    18  #include <sys/shm.h>
    19  #include <sys/types.h>
    20  
    21  #include "absl/time/clock.h"
    22  #include "test/util/multiprocess_util.h"
    23  #include "test/util/posix_error.h"
    24  #include "test/util/temp_path.h"
    25  #include "test/util/test_util.h"
    26  
    27  namespace gvisor {
    28  namespace testing {
    29  namespace {
    30  
    31  using ::testing::_;
    32  using ::testing::AnyOf;
    33  using ::testing::Eq;
    34  
    35  const uint64_t kAllocSize = kPageSize * 128ULL;
    36  
    37  PosixErrorOr<char*> Shmat(int shmid, const void* shmaddr, int shmflg) {
    38    const intptr_t addr =
    39        reinterpret_cast<intptr_t>(shmat(shmid, shmaddr, shmflg));
    40    if (addr == -1) {
    41      return PosixError(errno, "shmat() failed");
    42    }
    43    return reinterpret_cast<char*>(addr);
    44  }
    45  
    46  PosixError Shmdt(const char* shmaddr) {
    47    const int ret = shmdt(shmaddr);
    48    if (ret == -1) {
    49      return PosixError(errno, "shmdt() failed");
    50    }
    51    return NoError();
    52  }
    53  
    54  template <typename T>
    55  PosixErrorOr<int> Shmctl(int shmid, int cmd, T* buf) {
    56    int ret = shmctl(shmid, cmd, reinterpret_cast<struct shmid_ds*>(buf));
    57    if (ret == -1) {
    58      return PosixError(errno, "shmctl() failed");
    59    }
    60    return ret;
    61  }
    62  
    63  // ShmSegment is a RAII object for automatically cleaning up shm segments.
    64  class ShmSegment {
    65   public:
    66    explicit ShmSegment(int id) : id_(id) {}
    67  
    68    ~ShmSegment() {
    69      if (id_ >= 0) {
    70        EXPECT_NO_ERRNO(Rmid());
    71        id_ = -1;
    72      }
    73    }
    74  
    75    ShmSegment(ShmSegment&& other) : id_(other.release()) {}
    76  
    77    ShmSegment& operator=(ShmSegment&& other) {
    78      id_ = other.release();
    79      return *this;
    80    }
    81  
    82    ShmSegment(ShmSegment const& other) = delete;
    83    ShmSegment& operator=(ShmSegment const& other) = delete;
    84  
    85    int id() const { return id_; }
    86  
    87    int release() {
    88      int id = id_;
    89      id_ = -1;
    90      return id;
    91    }
    92  
    93    PosixErrorOr<int> Rmid() {
    94      RETURN_IF_ERRNO(Shmctl<void>(id_, IPC_RMID, nullptr));
    95      return release();
    96    }
    97  
    98   private:
    99    int id_ = -1;
   100  };
   101  
   102  PosixErrorOr<int> ShmgetRaw(key_t key, size_t size, int shmflg) {
   103    int id = shmget(key, size, shmflg);
   104    if (id == -1) {
   105      return PosixError(errno, "shmget() failed");
   106    }
   107    return id;
   108  }
   109  
   110  PosixErrorOr<ShmSegment> Shmget(key_t key, size_t size, int shmflg) {
   111    ASSIGN_OR_RETURN_ERRNO(int id, ShmgetRaw(key, size, shmflg));
   112    return ShmSegment(id);
   113  }
   114  
   115  TEST(ShmTest, AttachDetach) {
   116    const ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE(
   117        Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777));
   118    struct shmid_ds attr;
   119    ASSERT_NO_ERRNO(Shmctl(shm.id(), IPC_STAT, &attr));
   120    EXPECT_EQ(attr.shm_segsz, kAllocSize);
   121    EXPECT_EQ(attr.shm_nattch, 0);
   122  
   123    const char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0));
   124    ASSERT_NO_ERRNO(Shmctl(shm.id(), IPC_STAT, &attr));
   125    EXPECT_EQ(attr.shm_nattch, 1);
   126  
   127    const char* addr2 = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0));
   128    ASSERT_NO_ERRNO(Shmctl(shm.id(), IPC_STAT, &attr));
   129    EXPECT_EQ(attr.shm_nattch, 2);
   130  
   131    ASSERT_NO_ERRNO(Shmdt(addr));
   132    ASSERT_NO_ERRNO(Shmctl(shm.id(), IPC_STAT, &attr));
   133    EXPECT_EQ(attr.shm_nattch, 1);
   134  
   135    ASSERT_NO_ERRNO(Shmdt(addr2));
   136    ASSERT_NO_ERRNO(Shmctl(shm.id(), IPC_STAT, &attr));
   137    EXPECT_EQ(attr.shm_nattch, 0);
   138  }
   139  
   140  TEST(ShmTest, LookupByKey) {
   141    const TempPath keyfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
   142    const key_t key = ftok(keyfile.path().c_str(), 1);
   143    const ShmSegment shm =
   144        ASSERT_NO_ERRNO_AND_VALUE(Shmget(key, kAllocSize, IPC_CREAT | 0777));
   145    const int id2 = ASSERT_NO_ERRNO_AND_VALUE(ShmgetRaw(key, kAllocSize, 0777));
   146    EXPECT_EQ(shm.id(), id2);
   147  }
   148  
   149  TEST(ShmTest, DetachedSegmentsPersist) {
   150    const ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE(
   151        Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777));
   152    char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0));
   153    addr[0] = 'x';
   154    ASSERT_NO_ERRNO(Shmdt(addr));
   155  
   156    // We should be able to re-attach to the same segment and get our data back.
   157    addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0));
   158    EXPECT_EQ(addr[0], 'x');
   159    ASSERT_NO_ERRNO(Shmdt(addr));
   160  }
   161  
   162  TEST(ShmTest, MultipleDetachFails) {
   163    const ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE(
   164        Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777));
   165    const char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0));
   166    ASSERT_NO_ERRNO(Shmdt(addr));
   167    EXPECT_THAT(Shmdt(addr), PosixErrorIs(EINVAL, _));
   168  }
   169  
   170  TEST(ShmTest, IpcStat) {
   171    const TempPath keyfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
   172    const key_t key = ftok(keyfile.path().c_str(), 1);
   173  
   174    const time_t start = time(nullptr);
   175  
   176    const ShmSegment shm =
   177        ASSERT_NO_ERRNO_AND_VALUE(Shmget(key, kAllocSize, IPC_CREAT | 0777));
   178  
   179    const uid_t uid = getuid();
   180    const gid_t gid = getgid();
   181    const pid_t pid = getpid();
   182  
   183    struct shmid_ds attr;
   184    ASSERT_NO_ERRNO(Shmctl(shm.id(), IPC_STAT, &attr));
   185  
   186    EXPECT_EQ(attr.shm_perm.__key, key);
   187    EXPECT_EQ(attr.shm_perm.uid, uid);
   188    EXPECT_EQ(attr.shm_perm.gid, gid);
   189    EXPECT_EQ(attr.shm_perm.cuid, uid);
   190    EXPECT_EQ(attr.shm_perm.cgid, gid);
   191    EXPECT_EQ(attr.shm_perm.mode, 0777);
   192  
   193    EXPECT_EQ(attr.shm_segsz, kAllocSize);
   194  
   195    EXPECT_EQ(attr.shm_atime, 0);
   196    EXPECT_EQ(attr.shm_dtime, 0);
   197  
   198    // Change time is set on creation.
   199    EXPECT_GE(attr.shm_ctime, start);
   200  
   201    EXPECT_EQ(attr.shm_cpid, pid);
   202    EXPECT_EQ(attr.shm_lpid, 0);
   203  
   204    EXPECT_EQ(attr.shm_nattch, 0);
   205  
   206    // The timestamps only have a resolution of seconds; slow down so we actually
   207    // see the timestamps change.
   208    absl::SleepFor(absl::Seconds(1));
   209    const time_t pre_attach = time(nullptr);
   210  
   211    const char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0));
   212    ASSERT_NO_ERRNO(Shmctl(shm.id(), IPC_STAT, &attr));
   213  
   214    EXPECT_GE(attr.shm_atime, pre_attach);
   215    EXPECT_EQ(attr.shm_dtime, 0);
   216    EXPECT_LT(attr.shm_ctime, pre_attach);
   217    EXPECT_EQ(attr.shm_lpid, pid);
   218    EXPECT_EQ(attr.shm_nattch, 1);
   219  
   220    absl::SleepFor(absl::Seconds(1));
   221    const time_t pre_detach = time(nullptr);
   222  
   223    ASSERT_NO_ERRNO(Shmdt(addr));
   224    ASSERT_NO_ERRNO(Shmctl(shm.id(), IPC_STAT, &attr));
   225  
   226    EXPECT_LT(attr.shm_atime, pre_detach);
   227    EXPECT_GE(attr.shm_dtime, pre_detach);
   228    EXPECT_LT(attr.shm_ctime, pre_detach);
   229    EXPECT_EQ(attr.shm_lpid, pid);
   230    EXPECT_EQ(attr.shm_nattch, 0);
   231  }
   232  
   233  TEST(ShmTest, ShmStat) {
   234    // This test relies on the segment we create to be the first one on the
   235    // system, causing it to occupy slot 1. We can't reasonably expect this on a
   236    // general Linux host.
   237    SKIP_IF(!IsRunningOnGvisor());
   238  
   239    const ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE(
   240        Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777));
   241    struct shmid_ds attr;
   242    ASSERT_NO_ERRNO(Shmctl(1, SHM_STAT, &attr));
   243    // This does the same thing as IPC_STAT, so only test that the syscall
   244    // succeeds here.
   245  }
   246  
   247  TEST(ShmTest, IpcInfo) {
   248    struct shminfo info;
   249    ASSERT_NO_ERRNO(Shmctl(0, IPC_INFO, &info));
   250  
   251    EXPECT_EQ(info.shmmin, 1);  // This is always 1, according to the man page.
   252    EXPECT_GT(info.shmmax, info.shmmin);
   253    EXPECT_GT(info.shmmni, 0);
   254    EXPECT_GT(info.shmseg, 0);
   255    EXPECT_GT(info.shmall, 0);
   256  }
   257  
   258  TEST(ShmTest, ShmInfo) {
   259    // Take a snapshot of the system before the test runs.
   260    struct shm_info snap;
   261    ASSERT_NO_ERRNO(Shmctl(0, SHM_INFO, &snap));
   262  
   263    const ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE(
   264        Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777));
   265    const char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0));
   266  
   267    struct shm_info info;
   268    ASSERT_NO_ERRNO(Shmctl(1, SHM_INFO, &info));
   269  
   270    // We generally can't know what other processes on a linux machine do with
   271    // shared memory segments, so we can't test specific numbers on Linux. When
   272    // running under gvisor, we're guaranteed to be the only ones using shm, so
   273    // we can easily verify machine-wide numbers.
   274    if (IsRunningOnGvisor()) {
   275      ASSERT_NO_ERRNO(Shmctl(shm.id(), SHM_INFO, &info));
   276      EXPECT_EQ(info.used_ids, snap.used_ids + 1);
   277      EXPECT_EQ(info.shm_tot, snap.shm_tot + (kAllocSize / kPageSize));
   278      EXPECT_EQ(info.shm_rss, snap.shm_rss + (kAllocSize / kPageSize));
   279      EXPECT_EQ(info.shm_swp, 0);  // Gvisor currently never swaps.
   280    }
   281  
   282    ASSERT_NO_ERRNO(Shmdt(addr));
   283  }
   284  
   285  TEST(ShmTest, ShmCtlSet) {
   286    const ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE(
   287        Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777));
   288    const char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0));
   289  
   290    struct shmid_ds attr;
   291    ASSERT_NO_ERRNO(Shmctl(shm.id(), IPC_STAT, &attr));
   292    ASSERT_EQ(attr.shm_perm.mode, 0777);
   293  
   294    attr.shm_perm.mode = 0766;
   295    ASSERT_NO_ERRNO(Shmctl(shm.id(), IPC_SET, &attr));
   296  
   297    ASSERT_NO_ERRNO(Shmctl(shm.id(), IPC_STAT, &attr));
   298    ASSERT_EQ(attr.shm_perm.mode, 0766);
   299  
   300    ASSERT_NO_ERRNO(Shmdt(addr));
   301  }
   302  
   303  #ifndef SHM_DEST
   304  // Not defined in bionic
   305  #define SHM_DEST 0x200
   306  #endif
   307  
   308  TEST(ShmTest, RemovedSegmentsAreMarkedDeleted) {
   309    ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE(
   310        Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777));
   311    const char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0));
   312    const int id = ASSERT_NO_ERRNO_AND_VALUE(shm.Rmid());
   313    struct shmid_ds attr;
   314    ASSERT_NO_ERRNO(Shmctl(id, IPC_STAT, &attr));
   315    EXPECT_NE(attr.shm_perm.mode & SHM_DEST, 0);
   316    ASSERT_NO_ERRNO(Shmdt(addr));
   317  }
   318  
   319  TEST(ShmTest, RemovedSegmentsAreDestroyed) {
   320    ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE(
   321        Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777));
   322    const char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0));
   323  
   324    const uint64_t alloc_pages = kAllocSize / kPageSize;
   325  
   326    struct shm_info info;
   327    ASSERT_NO_ERRNO(Shmctl(0 /*ignored*/, SHM_INFO, &info));
   328    const uint64_t before = info.shm_tot;
   329  
   330    ASSERT_NO_ERRNO(shm.Rmid());
   331    ASSERT_NO_ERRNO(Shmdt(addr));
   332  
   333    ASSERT_NO_ERRNO(Shmctl(0 /*ignored*/, SHM_INFO, &info));
   334    if (IsRunningOnGvisor()) {
   335      // No guarantees on system-wide shm memory usage on a generic linux host.
   336      const uint64_t after = info.shm_tot;
   337      EXPECT_EQ(after, before - alloc_pages);
   338    }
   339  }
   340  
   341  TEST(ShmTest, AllowsAttachToRemovedSegmentWithRefs) {
   342    ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE(
   343        Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777));
   344    const char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0));
   345    const int id = ASSERT_NO_ERRNO_AND_VALUE(shm.Rmid());
   346    const char* addr2 = ASSERT_NO_ERRNO_AND_VALUE(Shmat(id, nullptr, 0));
   347    ASSERT_NO_ERRNO(Shmdt(addr));
   348    ASSERT_NO_ERRNO(Shmdt(addr2));
   349  }
   350  
   351  TEST(ShmTest, RemovedSegmentsAreNotDiscoverable) {
   352    const TempPath keyfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
   353    const key_t key = ftok(keyfile.path().c_str(), 1);
   354    ShmSegment shm =
   355        ASSERT_NO_ERRNO_AND_VALUE(Shmget(key, kAllocSize, IPC_CREAT | 0777));
   356    ASSERT_NO_ERRNO(shm.Rmid());
   357    EXPECT_THAT(Shmget(key, kAllocSize, 0777), PosixErrorIs(ENOENT, _));
   358  }
   359  
   360  TEST(ShmDeathTest, ReadonlySegment) {
   361    SetupGvisorDeathTest();
   362    const ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE(
   363        Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777));
   364    char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, SHM_RDONLY));
   365    // Reading succeeds.
   366    static_cast<void>(addr[0]);
   367    // Writing fails.
   368    EXPECT_EXIT(addr[0] = 'x', ::testing::KilledBySignal(SIGSEGV), "");
   369  }
   370  
   371  TEST(ShmDeathTest, SegmentNotAccessibleAfterDetach) {
   372    // This test is susceptible to races with concurrent mmaps running in parallel
   373    // gtest threads since the test relies on the address freed during a shm
   374    // segment destruction to remain unused. We run the test body in a forked
   375    // child to guarantee a single-threaded context to avoid this.
   376  
   377    SetupGvisorDeathTest();
   378  
   379    const auto rest = [&] {
   380      ShmSegment shm = TEST_CHECK_NO_ERRNO_AND_VALUE(
   381          Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777));
   382      char* addr = TEST_CHECK_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0));
   383  
   384      // Mark the segment as destroyed so it's automatically cleaned up when we
   385      // crash below. We can't rely on the standard cleanup since the destructor
   386      // will not run after the SIGSEGV. Note that this doesn't destroy the
   387      // segment immediately since we're still attached to it.
   388      TEST_CHECK_NO_ERRNO(shm.Rmid());
   389  
   390      addr[0] = 'x';
   391      TEST_CHECK_NO_ERRNO(Shmdt(addr));
   392  
   393      // This access should cause a SIGSEGV.
   394      addr[0] = 'x';
   395    };
   396  
   397    EXPECT_THAT(InForkedProcess(rest),
   398                IsPosixErrorOkAndHolds(AnyOf(Eq(W_EXITCODE(0, SIGSEGV)),
   399                                             Eq(W_EXITCODE(0, 128 + SIGSEGV)))));
   400  }
   401  
   402  TEST(ShmTest, RequestingSegmentSmallerThanSHMMINFails) {
   403    struct shminfo info;
   404    ASSERT_NO_ERRNO(Shmctl(0, IPC_INFO, &info));
   405    const uint64_t size = info.shmmin - 1;
   406    EXPECT_THAT(Shmget(IPC_PRIVATE, size, IPC_CREAT | 0777),
   407                PosixErrorIs(EINVAL, _));
   408  }
   409  
   410  TEST(ShmTest, RequestingSegmentLargerThanSHMMAXFails) {
   411    struct shminfo info;
   412    ASSERT_NO_ERRNO(Shmctl(0, IPC_INFO, &info));
   413    const uint64_t size = info.shmmax + kPageSize;
   414    EXPECT_THAT(Shmget(IPC_PRIVATE, size, IPC_CREAT | 0777),
   415                PosixErrorIs(EINVAL, _));
   416  }
   417  
   418  TEST(ShmTest, RequestingUnalignedSizeSucceeds) {
   419    EXPECT_NO_ERRNO(Shmget(IPC_PRIVATE, 4097, IPC_CREAT | 0777));
   420  }
   421  
   422  TEST(ShmTest, RequestingDuplicateCreationFails) {
   423    const TempPath keyfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
   424    const key_t key = ftok(keyfile.path().c_str(), 1);
   425    const ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE(
   426        Shmget(key, kAllocSize, IPC_CREAT | IPC_EXCL | 0777));
   427    EXPECT_THAT(Shmget(key, kAllocSize, IPC_CREAT | IPC_EXCL | 0777),
   428                PosixErrorIs(EEXIST, _));
   429  }
   430  
   431  TEST(ShmTest, NonExistentSegmentsAreNotFound) {
   432    const TempPath keyfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
   433    const key_t key = ftok(keyfile.path().c_str(), 1);
   434    // Do not request creation.
   435    EXPECT_THAT(Shmget(key, kAllocSize, 0777), PosixErrorIs(ENOENT, _));
   436  }
   437  
   438  TEST(ShmTest, SegmentsSizeFixedOnCreation) {
   439    const TempPath keyfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
   440    const key_t key = ftok(keyfile.path().c_str(), 1);
   441  
   442    // Base segment.
   443    const ShmSegment shm =
   444        ASSERT_NO_ERRNO_AND_VALUE(Shmget(key, kAllocSize, IPC_CREAT | 0777));
   445  
   446    // Ask for the same segment at half size. This succeeds.
   447    const int id2 =
   448        ASSERT_NO_ERRNO_AND_VALUE(ShmgetRaw(key, kAllocSize / 2, 0777));
   449  
   450    // Ask for the same segment at double size.
   451    EXPECT_THAT(Shmget(key, kAllocSize * 2, 0777), PosixErrorIs(EINVAL, _));
   452  
   453    char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0));
   454    char* addr2 = ASSERT_NO_ERRNO_AND_VALUE(Shmat(id2, nullptr, 0));
   455  
   456    // We have 2 different maps...
   457    EXPECT_NE(addr, addr2);
   458  
   459    // ... And both maps are kAllocSize bytes; despite asking for a half-sized
   460    // segment for the second map.
   461    addr[kAllocSize - 1] = 'x';
   462    addr2[kAllocSize - 1] = 'x';
   463  
   464    ASSERT_NO_ERRNO(Shmdt(addr));
   465    ASSERT_NO_ERRNO(Shmdt(addr2));
   466  }
   467  
   468  TEST(ShmTest, PartialUnmap) {
   469    const ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE(
   470        Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777));
   471    char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0));
   472    EXPECT_THAT(munmap(addr + (kAllocSize / 4), kAllocSize / 2),
   473                SyscallSucceeds());
   474    ASSERT_NO_ERRNO(Shmdt(addr));
   475  }
   476  
   477  // Check that sentry does not panic when asked for a zero-length private shm
   478  // segment. Regression test for b/110694797.
   479  TEST(ShmTest, GracefullyFailOnZeroLenSegmentCreation) {
   480    EXPECT_THAT(Shmget(IPC_PRIVATE, 0, 0), PosixErrorIs(EINVAL, _));
   481  }
   482  
   483  TEST(ShmTest, NoDestructionOfAttachedSegmentWithMultipleRmid) {
   484    ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE(
   485        Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777));
   486    char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0));
   487    char* addr2 = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0));
   488  
   489    // There should be 2 refs to the segment from the 2 attachments, and a single
   490    // self-reference. Mark the segment as destroyed more than 3 times through
   491    // shmctl(RMID). If there's a bug with the ref counting, this should cause the
   492    // count to drop to zero.
   493    int id = shm.release();
   494    for (int i = 0; i < 6; ++i) {
   495      ASSERT_NO_ERRNO(Shmctl<void>(id, IPC_RMID, nullptr));
   496    }
   497  
   498    // Segment should remain accessible.
   499    addr[0] = 'x';
   500    ASSERT_NO_ERRNO(Shmdt(addr));
   501  
   502    // Segment should remain accessible even after one of the two attachments are
   503    // detached.
   504    addr2[0] = 'x';
   505    ASSERT_NO_ERRNO(Shmdt(addr2));
   506  }
   507  
   508  }  // namespace
   509  }  // namespace testing
   510  }  // namespace gvisor