Loading...
--- xnu/xnu-12377.121.6/tests/sysv_msgctl_race.c
+++ /dev/null
@@ -1,305 +0,0 @@
-/*
- * Copyright (c) 2026 Apple Computer, Inc. All rights reserved.
- *
- * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
- *
- * This file contains Original Code and/or Modifications of Original Code
- * as defined in and that are subject to the Apple Public Source License
- * Version 2.0 (the 'License'). You may not use this file except in
- * compliance with the License. The rights granted to you under the License
- * may not be used to create, or enable the creation or redistribution of,
- * unlawful or unlicensed copies of an Apple operating system, or to
- * circumvent, violate, or enable the circumvention or violation of, any
- * terms of an Apple operating system software license agreement.
- *
- * Please obtain a copy of the License at
- * http://www.opensource.apple.com/apsl/ and read it before using this file.
- *
- * The Original Code and all software distributed under the License are
- * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
- * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
- * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
- * Please see the License for the specific language governing rights and
- * limitations under the License.
- *
- * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
- */
-
-/*
- * sysv_msgctl_race.c — Test for msgctl(IPC_SET) TOCTOU race (rdar://172502094)
- *
- * Verifies that msgctl(IPC_SET) does not corrupt a queue that was
- * freed and reallocated while IPC_SET is in progress. The fix holds
- * the SYSV_MSG_SUBSYS lock through copyin, consistent with how
- * sysv_sem.c and sysv_shm.c handle IPC_SET.
- *
- * The test forks a victim process and races IPC_SET + IPC_RMID +
- * msgget across 100 iterations. If IPC_SET ever writes attacker
- * values to a victim's queue, the test fails.
- */
-
-#include <darwintest.h>
-#include <sys/types.h>
-#include <sys/ipc.h>
-#include <sys/msg.h>
-#include <sys/mman.h>
-#include <sys/wait.h>
-#include <pthread.h>
-#include <unistd.h>
-#include <string.h>
-#include <stdatomic.h>
-#include <errno.h>
-
-T_GLOBAL_META(
- T_META_NAMESPACE("xnu.ipc"),
- T_META_RADAR_COMPONENT_NAME("xnu"),
- T_META_RADAR_COMPONENT_VERSION("IPC"),
- T_META_RUN_CONCURRENTLY(true));
-
-#define NUM_ITERATIONS 100
-#define ATTACKER_UID 31337
-#define ATTACKER_GID 31337
-#define ATTACKER_MODE 0777
-#define NUM_FILLER_QUEUES 10
-
-struct shared_state {
- _Atomic int stop_flag;
- _Atomic int race_won;
- _Atomic long victim_checks;
-
- _Atomic int target_msqid;
- _Atomic int target_ready;
- _Atomic int do_delete;
-};
-
-static struct shared_state *shared;
-
-/*
- * Attacker IPC_SET thread: continuously tries to overwrite the
- * target queue's uid/gid/mode with attacker values.
- */
-static void *
-attacker_set_thread(void *arg)
-{
- (void)arg;
- struct msqid_ds buf;
-
- memset(&buf, 0, sizeof(buf));
- buf.msg_perm.uid = ATTACKER_UID;
- buf.msg_perm.gid = ATTACKER_GID;
- buf.msg_perm.mode = ATTACKER_MODE;
- buf.msg_qbytes = 2048;
-
- while (!atomic_load(&shared->stop_flag)) {
- if (atomic_load(&shared->target_ready)) {
- int msqid = atomic_load(&shared->target_msqid);
- if (msqid != -1) {
- msgctl(msqid, IPC_SET, &buf);
- }
- }
- }
- return NULL;
-}
-
-/*
- * Attacker IPC_RMID thread: continuously tries to delete the
- * target queue to free its slot for reuse.
- */
-static void *
-attacker_rmid_thread(void *arg)
-{
- (void)arg;
-
- while (!atomic_load(&shared->stop_flag)) {
- if (atomic_load(&shared->do_delete)) {
- int msqid = atomic_load(&shared->target_msqid);
- if (msqid != -1) {
- msgctl(msqid, IPC_RMID, NULL);
- }
- }
- }
- return NULL;
-}
-
-/*
- * Victim process (child).
- *
- * Continuously creates queues and checks if their ownership was
- * corrupted to the attacker's values. Any corruption proves the
- * race was exploited across process boundaries.
- */
-static void __attribute__((noreturn))
-victim_process(void)
-{
- uid_t my_uid = getuid();
- struct msqid_ds buf;
- int corrupted = 0;
-
- while (!atomic_load(&shared->stop_flag)) {
- int msqid = msgget(IPC_PRIVATE, IPC_CREAT | 0600);
- if (msqid == -1) {
- usleep(100);
- continue;
- }
-
- if (msgctl(msqid, IPC_STAT, &buf) == 0) {
- atomic_fetch_add(&shared->victim_checks, 1);
-
- if (buf.msg_perm.uid == ATTACKER_UID &&
- my_uid != ATTACKER_UID) {
- atomic_store(&shared->race_won, 1);
- atomic_store(&shared->stop_flag, 1);
- corrupted = 1;
- }
- }
-
- msgctl(msqid, IPC_RMID, NULL);
-
- if (corrupted) {
- break;
- }
- }
-
- _exit(corrupted ? 1 : 0);
-}
-
-T_DECL(sysv_msgctl_ipc_set_race,
- "Verify msgctl(IPC_SET) does not corrupt a reused queue slot (rdar://172502094)",
- T_META_ASROOT(true),
- T_META_ENABLED(TARGET_OS_OSX))
-{
-#if !TARGET_OS_OSX
- T_SKIP("msgctl is only available on macos");
-#endif
-
- pthread_t threads[4];
- int filler_queues[NUM_FILLER_QUEUES];
- int num_fillers = 0;
- int ret;
-
- shared = mmap(NULL, sizeof(*shared),
- PROT_READ | PROT_WRITE,
- MAP_SHARED | MAP_ANONYMOUS, -1, 0);
- T_ASSERT_NE(shared, MAP_FAILED, "mmap shared state");
-
- atomic_store(&shared->stop_flag, 0);
- atomic_store(&shared->race_won, 0);
- atomic_store(&shared->victim_checks, 0);
- atomic_store(&shared->target_msqid, -1);
- atomic_store(&shared->target_ready, 0);
- atomic_store(&shared->do_delete, 0);
-
- /* Pre-fill some queue slots for predictable index reuse */
- for (int i = 0; i < NUM_FILLER_QUEUES; i++) {
- filler_queues[i] = msgget(IPC_PRIVATE, IPC_CREAT | 0600);
- if (filler_queues[i] == -1) {
- T_LOG("Filler queue allocation stopped at %d: %s",
- i, strerror(errno));
- break;
- }
- num_fillers++;
- }
- T_LOG("Allocated %d filler queues", num_fillers);
-
- /* Fork victim process */
- pid_t victim_pid = fork();
- T_ASSERT_POSIX_SUCCESS(victim_pid, "fork victim");
-
- if (victim_pid == 0) {
- victim_process();
- /* not reached */
- }
-
- T_LOG("Attacker PID %d, victim PID %d", getpid(), victim_pid);
-
- /* Start attacker threads */
- ret = pthread_create(&threads[0], NULL, attacker_set_thread, NULL);
- T_QUIET; T_ASSERT_POSIX_ZERO(ret, "pthread_create set_thread 0");
- ret = pthread_create(&threads[1], NULL, attacker_set_thread, NULL);
- T_QUIET; T_ASSERT_POSIX_ZERO(ret, "pthread_create set_thread 1");
- ret = pthread_create(&threads[2], NULL, attacker_rmid_thread, NULL);
- T_QUIET; T_ASSERT_POSIX_ZERO(ret, "pthread_create rmid_thread 0");
- ret = pthread_create(&threads[3], NULL, attacker_rmid_thread, NULL);
- T_QUIET; T_ASSERT_POSIX_ZERO(ret, "pthread_create rmid_thread 1");
-
- for (int i = 0; i < NUM_ITERATIONS && !atomic_load(&shared->stop_flag); i++) {
- int msqid = msgget(IPC_PRIVATE, IPC_CREAT | 0600);
- if (msqid == -1) {
- usleep(100);
- continue;
- }
- atomic_store(&shared->target_msqid, msqid);
-
- atomic_store(&shared->target_ready, 1);
- atomic_store(&shared->do_delete, 1);
-
- /* Let threads and victim race */
- usleep(1000);
-
- atomic_store(&shared->target_ready, 0);
- atomic_store(&shared->do_delete, 0);
-
- msgctl(msqid, IPC_RMID, NULL);
- atomic_store(&shared->target_msqid, -1);
- }
-
- atomic_store(&shared->stop_flag, 1);
-
- for (int i = 0; i < 4; i++) {
- ret = pthread_join(threads[i], NULL);
- T_QUIET; T_ASSERT_POSIX_ZERO(ret, "pthread_join thread %d", i);
- }
-
- int status;
- ret = waitpid(victim_pid, &status, 0);
- T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "waitpid victim");
-
- /* Cleanup filler queues */
- for (int i = 0; i < num_fillers; i++) {
- if (filler_queues[i] != -1) {
- msgctl(filler_queues[i], IPC_RMID, NULL);
- }
- }
-
- T_LOG("Victim performed %ld ownership checks",
- atomic_load(&shared->victim_checks));
-
- T_ASSERT_GT_LONG(atomic_load(&shared->victim_checks), 0L,
- "Victim performed at least one ownership check");
-
- T_ASSERT_EQ(atomic_load(&shared->race_won), 0,
- "Victim queue ownership must never be corrupted by attacker's IPC_SET");
-
- ret = munmap(shared, sizeof(*shared));
- T_QUIET; T_ASSERT_POSIX_ZERO(ret, "munmap shared state");
-}
-
-T_DECL(sysv_msgctl_ipc_set_copyin_fault,
- "Verify we unlock the msqid global lock after copyin failed",
- T_META_ASROOT(true),
- T_META_ENABLED(TARGET_OS_OSX))
-{
-#if !TARGET_OS_OSX
- T_SKIP("msgctl is only available on macos");
-#endif
-
- int msqid;
- struct msqid_ds buf;
- int ret;
-
- msqid = msgget(IPC_PRIVATE, IPC_CREAT | 0600);
- T_ASSERT_POSIX_SUCCESS(msqid, "msgget create queue");
-
- /* Pass an invalid pointer — should fail with EFAULT */
- ret = msgctl(msqid, IPC_SET, (struct msqid_ds *)(uintptr_t)0x1);
- T_ASSERT_POSIX_FAILURE(ret, EFAULT,
- "msgctl IPC_SET with invalid pointer returns EFAULT");
-
- /* Verify that we unlock after copyin failed */
- ret = msgctl(msqid, IPC_STAT, &buf);
- T_ASSERT_POSIX_SUCCESS(ret, "msgctl IPC_STAT after fault");
-
- ret = msgctl(msqid, IPC_RMID, NULL);
- T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "msgctl IPC_RMID cleanup");
-}