diff options
author | Jing Chen <chjing@google.com> | 2020-11-02 23:56:29 -0800 |
---|---|---|
committer | gVisor bot <gvisor-bot@google.com> | 2020-11-02 23:58:45 -0800 |
commit | 1a3f417f4a329339d89fcf89262bd08c18c1f27e (patch) | |
tree | 54d28b81f22b29ac979308b3cf7e642145916d2e | |
parent | 1321f837bd9f082e3c1b0a37831453b3637202c3 (diff) |
Implement command GETZCNT for semctl.
PiperOrigin-RevId: 340389884
-rw-r--r-- | pkg/sentry/kernel/semaphore/semaphore.go | 23 | ||||
-rw-r--r-- | pkg/sentry/syscalls/linux/linux64.go | 4 | ||||
-rw-r--r-- | pkg/sentry/syscalls/linux/sys_sem.go | 17 | ||||
-rw-r--r-- | test/syscalls/linux/semaphore.cc | 115 |
4 files changed, 155 insertions, 4 deletions
diff --git a/pkg/sentry/kernel/semaphore/semaphore.go b/pkg/sentry/kernel/semaphore/semaphore.go index c39ecfb8f..310762936 100644 --- a/pkg/sentry/kernel/semaphore/semaphore.go +++ b/pkg/sentry/kernel/semaphore/semaphore.go @@ -423,6 +423,29 @@ func (s *Set) GetPID(num int32, creds *auth.Credentials) (int32, error) { return sem.pid, nil } +// GetZeroWaiters returns number of waiters waiting for the sem to go to zero. +func (s *Set) GetZeroWaiters(num int32, creds *auth.Credentials) (uint16, error) { + s.mu.Lock() + defer s.mu.Unlock() + + // The calling process must have read permission on the semaphore set. + if !s.checkPerms(creds, fs.PermMask{Read: true}) { + return 0, syserror.EACCES + } + + sem := s.findSem(num) + if sem == nil { + return 0, syserror.ERANGE + } + var semzcnt uint16 + for w := sem.waiters.Front(); w != nil; w = w.Next() { + if w.value == 0 { + semzcnt++ + } + } + return semzcnt, nil +} + // ExecuteOps attempts to execute a list of operations to the set. It only // succeeds when all operations can be applied. No changes are made if it fails. // diff --git a/pkg/sentry/syscalls/linux/linux64.go b/pkg/sentry/syscalls/linux/linux64.go index 36902d177..650ca16e6 100644 --- a/pkg/sentry/syscalls/linux/linux64.go +++ b/pkg/sentry/syscalls/linux/linux64.go @@ -118,7 +118,7 @@ var AMD64 = &kernel.SyscallTable{ 63: syscalls.Supported("uname", Uname), 64: syscalls.Supported("semget", Semget), 65: syscalls.PartiallySupported("semop", Semop, "Option SEM_UNDO not supported.", nil), - 66: syscalls.PartiallySupported("semctl", Semctl, "Options IPC_INFO, SEM_INFO, SEM_STAT, SEM_STAT_ANY, GETNCNT, GETZCNT not supported.", nil), + 66: syscalls.PartiallySupported("semctl", Semctl, "Options IPC_INFO, SEM_INFO, SEM_STAT, SEM_STAT_ANY, GETNCNT not supported.", nil), 67: syscalls.Supported("shmdt", Shmdt), 68: syscalls.ErrorWithEvent("msgget", syserror.ENOSYS, "", []string{"gvisor.dev/issue/135"}), // TODO(b/29354921) 69: syscalls.ErrorWithEvent("msgsnd", syserror.ENOSYS, "", []string{"gvisor.dev/issue/135"}), // TODO(b/29354921) @@ -619,7 +619,7 @@ var ARM64 = &kernel.SyscallTable{ 188: syscalls.ErrorWithEvent("msgrcv", syserror.ENOSYS, "", []string{"gvisor.dev/issue/135"}), // TODO(b/29354921) 189: syscalls.ErrorWithEvent("msgsnd", syserror.ENOSYS, "", []string{"gvisor.dev/issue/135"}), // TODO(b/29354921) 190: syscalls.Supported("semget", Semget), - 191: syscalls.PartiallySupported("semctl", Semctl, "Options IPC_INFO, SEM_INFO, SEM_STAT, SEM_STAT_ANY, GETNCNT, GETZCNT not supported.", nil), + 191: syscalls.PartiallySupported("semctl", Semctl, "Options IPC_INFO, SEM_INFO, SEM_STAT, SEM_STAT_ANY, GETNCNT not supported.", nil), 192: syscalls.ErrorWithEvent("semtimedop", syserror.ENOSYS, "", []string{"gvisor.dev/issue/137"}), 193: syscalls.PartiallySupported("semop", Semop, "Option SEM_UNDO not supported.", nil), 194: syscalls.PartiallySupported("shmget", Shmget, "Option SHM_HUGETLB is not supported.", nil), diff --git a/pkg/sentry/syscalls/linux/sys_sem.go b/pkg/sentry/syscalls/linux/sys_sem.go index c2d4bf805..067f6be6d 100644 --- a/pkg/sentry/syscalls/linux/sys_sem.go +++ b/pkg/sentry/syscalls/linux/sys_sem.go @@ -138,12 +138,15 @@ func Semctl(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscal return 0, nil, err + case linux.GETZCNT: + v, err := getSemzcnt(t, id, num) + return uintptr(v), nil, err + case linux.IPC_INFO, linux.SEM_INFO, linux.SEM_STAT, linux.SEM_STAT_ANY, - linux.GETNCNT, - linux.GETZCNT: + linux.GETNCNT: t.Kernel().EmitUnimplementedEvent(t) fallthrough @@ -258,3 +261,13 @@ func getPID(t *kernel.Task, id int32, num int32) (int32, error) { } return int32(tg.ID()), nil } + +func getSemzcnt(t *kernel.Task, id int32, num int32) (uint16, error) { + r := t.IPCNamespace().SemaphoreRegistry() + set := r.FindByID(id) + if set == nil { + return 0, syserror.EINVAL + } + creds := auth.CredentialsFromContext(t) + return set.GetZeroWaiters(num, creds) +} diff --git a/test/syscalls/linux/semaphore.cc b/test/syscalls/linux/semaphore.cc index ed6a1c2aa..1534c15c7 100644 --- a/test/syscalls/linux/semaphore.cc +++ b/test/syscalls/linux/semaphore.cc @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include <signal.h> #include <sys/ipc.h> #include <sys/sem.h> #include <sys/types.h> @@ -542,6 +543,120 @@ TEST(SemaphoreTest, SemCtlIpcStat) { SyscallFailsWithErrno(EACCES)); } +// The funcion keeps calling semctl's GETZCNT command until +// the return value is not less than target. +int WaitSemzcnt(int semid, int target) { + constexpr absl::Duration timeout = absl::Seconds(10); + int semcnt = 0; + for (auto start = absl::Now(); absl::Now() - start < timeout;) { + semcnt = semctl(semid, 0, GETZCNT); + if (semcnt >= target) { + break; + } + absl::SleepFor(absl::Milliseconds(10)); + } + return semcnt; +} + +TEST(SemaphoreTest, SemopGetzcnt) { + // Drop CAP_IPC_OWNER which allows us to bypass semaphore permissions. + ASSERT_NO_ERRNO(SetCapability(CAP_IPC_OWNER, false)); + // Create a write only semaphore set. + AutoSem sem(semget(IPC_PRIVATE, 1, 0200 | IPC_CREAT)); + ASSERT_THAT(sem.get(), SyscallSucceeds()); + + // No read permission to retrieve semzcnt. + EXPECT_THAT(semctl(sem.get(), 0, GETZCNT), SyscallFailsWithErrno(EACCES)); + + // Remove the calling thread's read permission. + struct semid_ds ds = {}; + ds.sem_perm.uid = getuid(); + ds.sem_perm.gid = getgid(); + ds.sem_perm.mode = 0600; + ASSERT_THAT(semctl(sem.get(), 0, IPC_SET, &ds), SyscallSucceeds()); + + std::vector<pid_t> children; + ASSERT_THAT(semctl(sem.get(), 0, SETVAL, 1), SyscallSucceeds()); + + struct sembuf buf = {}; + buf.sem_num = 0; + buf.sem_op = 0; + constexpr size_t kLoops = 10; + for (auto i = 0; i < kLoops; i++) { + auto child_pid = fork(); + if (child_pid == 0) { + ASSERT_THAT(RetryEINTR(semop)(sem.get(), &buf, 1), SyscallSucceeds()); + _exit(0); + } + children.push_back(child_pid); + } + EXPECT_THAT(WaitSemzcnt(sem.get(), kLoops), SyscallSucceedsWithValue(kLoops)); + // Set semval to 0, which wakes up children that sleep on the semop. + ASSERT_THAT(semctl(sem.get(), 0, SETVAL, 0), SyscallSucceeds()); + for (const auto& child_pid : children) { + int status; + ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), + SyscallSucceedsWithValue(child_pid)); + EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0); + } + EXPECT_EQ(semctl(sem.get(), 0, GETZCNT), 0); +} + +TEST(SemaphoreTest, SemopGetzcntOnSetRemoval) { + auto semid = semget(IPC_PRIVATE, 1, 0600 | IPC_CREAT); + ASSERT_THAT(semid, SyscallSucceeds()); + ASSERT_THAT(semctl(semid, 0, SETVAL, 1), SyscallSucceeds()); + ASSERT_EQ(semctl(semid, 0, GETZCNT), 0); + + auto child_pid = fork(); + if (child_pid == 0) { + struct sembuf buf = {}; + buf.sem_num = 0; + buf.sem_op = 0; + + ASSERT_THAT(RetryEINTR(semop)(semid, &buf, 1), SyscallFails()); + // Ensure that wait will only unblock when the semaphore is removed. On + // EINTR retry it may race with deletion and return EINVAL. + ASSERT_TRUE(errno == EIDRM || errno == EINVAL) << "errno=" << errno; + _exit(0); + } + + EXPECT_THAT(WaitSemzcnt(semid, 1), SyscallSucceedsWithValue(1)); + // Remove the semaphore set, which fails the sleep semop. + ASSERT_THAT(semctl(semid, 0, IPC_RMID), SyscallSucceeds()); + int status; + ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), + SyscallSucceedsWithValue(child_pid)); + EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0); + EXPECT_THAT(semctl(semid, 0, GETZCNT), SyscallFailsWithErrno(EINVAL)); +} + +TEST(SemaphoreTest, SemopGetzcntOnSignal) { + AutoSem sem(semget(IPC_PRIVATE, 1, 0600 | IPC_CREAT)); + ASSERT_THAT(sem.get(), SyscallSucceeds()); + ASSERT_THAT(semctl(sem.get(), 0, SETVAL, 1), SyscallSucceeds()); + ASSERT_EQ(semctl(sem.get(), 0, GETZCNT), 0); + + auto child_pid = fork(); + if (child_pid == 0) { + signal(SIGHUP, [](int sig) -> void {}); + struct sembuf buf = {}; + buf.sem_num = 0; + buf.sem_op = 0; + + ASSERT_THAT(semop(sem.get(), &buf, 1), SyscallFailsWithErrno(EINTR)); + _exit(0); + } + EXPECT_THAT(WaitSemzcnt(sem.get(), 1), SyscallSucceedsWithValue(1)); + // Send a signal to the child, which fails the sleep semop. + ASSERT_EQ(kill(child_pid, SIGHUP), 0); + int status; + ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), + SyscallSucceedsWithValue(child_pid)); + EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0); + EXPECT_EQ(semctl(sem.get(), 0, GETZCNT), 0); +} + } // namespace } // namespace testing } // namespace gvisor |