From 8299cef2003f9c46653139b7de20dd7eb3cb5a1e Mon Sep 17 00:00:00 2001 From: yuerengan Date: Sat, 1 Aug 2026 09:57:12 +0800 Subject: [PATCH] LATX, fix: track IPC namespace joined with setns A guest can enter an IPC namespace through setns() with either an explicit CLONE_NEWIPC type or a zero type inferred from the namespace fd. Record both successful paths so the AOT exit worker remains synchronous and cannot keep the namespace alive after guest exit. Inspect NS_GET_NSTYPE only for the zero-type namespace-fd form and preserve the real setns() result when that ioctl is not applicable. This keeps pidfd namespace masks working while handling invalid descriptors explicitly. Add integration coverage for invalid descriptors, pidfd namespace joins, and both namespace-fd forms with AOT and mqueue lifecycle validation. Signed-off-by: yuerengan --- linux-user/syscall.c | 18 ++- tests/integration/exec-with-pidfd.py | 19 +++ tests/integration/meson.build | 12 ++ tests/integration/setns-ipc-mqueue.S | 82 +++++++++++ tests/integration/test-setns-ipc-mqueue.sh | 159 +++++++++++++++++++++ 5 files changed, 289 insertions(+), 1 deletion(-) create mode 100755 tests/integration/exec-with-pidfd.py create mode 100644 tests/integration/setns-ipc-mqueue.S create mode 100755 tests/integration/test-setns-ipc-mqueue.sh diff --git a/linux-user/syscall.c b/linux-user/syscall.c index 2db3e733d6..32e7be2d95 100644 --- a/linux-user/syscall.c +++ b/linux-user/syscall.c @@ -17852,7 +17852,23 @@ defined(__loongarch__) #if defined(TARGET_NR_setns) && defined(CONFIG_SETNS) case TARGET_NR_setns: - return get_errno(setns(arg1, arg2)); + { + bool joins_ipc_namespace = (arg2 & CLONE_NEWIPC) != 0; + + if (arg2 == 0) { + int namespace_type = ioctl(arg1, NS_GET_NSTYPE); + + if (namespace_type >= 0) { + joins_ipc_namespace = namespace_type == CLONE_NEWIPC; + } + } + + ret = get_errno(setns(arg1, arg2)); + if (ret == 0 && joins_ipc_namespace) { + ((TaskState *)cpu->opaque)->ipc_namespace_isolated = true; + } + return ret; + } #endif #ifdef TARGET_NR_seccomp case TARGET_NR_seccomp: diff --git a/tests/integration/exec-with-pidfd.py b/tests/integration/exec-with-pidfd.py new file mode 100755 index 0000000000..403bf05df8 --- /dev/null +++ b/tests/integration/exec-with-pidfd.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +import os +import sys + + +def main() -> None: + if len(sys.argv) != 4: + raise SystemExit("usage: exec-with-pidfd.py PID EMULATOR GUEST") + + pidfd = os.pidfd_open(int(sys.argv[1])) + os.dup2(pidfd, 9, inheritable=True) + env = os.environ.copy() + env["LATX_AOT"] = "0" + env["LATX_KZT"] = "0" + os.execve(sys.argv[2], [sys.argv[2], sys.argv[3]], env) + + +if __name__ == "__main__": + main() diff --git a/tests/integration/meson.build b/tests/integration/meson.build index 6626793686..e3c5860a50 100644 --- a/tests/integration/meson.build +++ b/tests/integration/meson.build @@ -55,4 +55,16 @@ if host_machine.cpu_family() == 'loongarch64' and \ suite: 'latx-integration', timeout: 30, ) + test( + 'test-setns-ipc-mqueue', + find_program('test-setns-ipc-mqueue.sh'), + args: [ + emulators['latx-x86_64'], + files('setns-ipc-mqueue.S'), + files('exec-with-pidfd.py'), + ], + protocol: 'exitcode', + suite: 'latx-integration', + timeout: 30, + ) endif diff --git a/tests/integration/setns-ipc-mqueue.S b/tests/integration/setns-ipc-mqueue.S new file mode 100644 index 0000000000..80d924006a --- /dev/null +++ b/tests/integration/setns-ipc-mqueue.S @@ -0,0 +1,82 @@ +.equ __NR_close, 3 +.equ __NR_mount, 165 +.equ __NR_exit_group, 231 +.equ __NR_setns, 308 + +#ifndef SETNS_FD +#define SETNS_FD 9 +#endif + +#ifndef EXPECT_SETNS_ERRNO +#define EXPECT_SETNS_ERRNO 0 +#endif + +#ifndef SETNS_ONLY +#define SETNS_ONLY 0 +#endif + +.section .rodata +mq_source: + .asciz "mqueue" +mq_target: + .asciz "mqueue" +mq_type: + .asciz "mqueue" + +.section .text +.global _start +.type _start, @function +_start: + mov $__NR_setns, %eax + mov $SETNS_FD, %edi + mov $SETNS_FLAGS, %esi + syscall +#if EXPECT_SETNS_ERRNO + cmp $-EXPECT_SETNS_ERRNO, %rax + jne setns_error_mismatch + xor %edi, %edi + jmp exit +#else + test %rax, %rax + js setns_failed + + mov $__NR_close, %eax + mov $SETNS_FD, %edi + syscall + +#if SETNS_ONLY + xor %edi, %edi + jmp exit +#endif + + mov $__NR_mount, %eax + lea mq_source(%rip), %rdi + lea mq_target(%rip), %rsi + lea mq_type(%rip), %rdx + xor %r10d, %r10d + xor %r8d, %r8d + syscall + test %rax, %rax + js mount_failed + + xor %edi, %edi + jmp exit +#endif + +setns_failed: + mov $10, %edi + jmp exit + +setns_error_mismatch: + mov $11, %edi + jmp exit + +mount_failed: + mov $20, %edi + +exit: + mov $__NR_exit_group, %eax + syscall +.size _start, .-_start + +.section .note.GNU-stack,"",@progbits diff --git a/tests/integration/test-setns-ipc-mqueue.sh b/tests/integration/test-setns-ipc-mqueue.sh new file mode 100755 index 0000000000..b5e9ed02e6 --- /dev/null +++ b/tests/integration/test-setns-ipc-mqueue.sh @@ -0,0 +1,159 @@ +#!/bin/sh +set -eu + +emulator=$(cd "$(dirname "$1")" && pwd)/$(basename "$1") +source_file=$2 +pidfd_runner=$3 +workdir=$(mktemp -d) +trap 'rm -rf "$workdir"' EXIT HUP INT TERM + +if ! command -v unshare >/dev/null 2>&1 || + ! unshare -Urm true 2>/dev/null; then + echo "SKIP: unprivileged user and mount namespaces are unavailable" + exit 77 +fi + +compile_guest() +{ + output=$1 + shift + "$clang" --target=x86_64-linux-gnu -fuse-ld=lld -nostdlib -static \ + -Wl,--build-id=none "$@" "$source_file" -o "$output" +} + +run_invalid_fd_case() +{ + guest="$workdir/setns-invalid-fd" + + compile_guest "$guest" -DSETNS_FLAGS=0 -DSETNS_FD=-1 \ + -DEXPECT_SETNS_ERRNO=9 + LATX_AOT=0 LATX_KZT=0 "$emulator" "$guest" + echo "PASS: invalid namespace fd returned EBADF" +} + +run_pidfd_case() +{ + guest="$workdir/setns-ipc-pidfd" + + if ! command -v python3 >/dev/null 2>&1 || + ! python3 -c 'import os; fd = os.pidfd_open(os.getpid()); os.close(fd)'; + then + echo "SKIP: pidfd support is unavailable" + return + fi + + compile_guest "$guest" -DSETNS_FLAGS=134217728 -DSETNS_ONLY=1 + unshare -Ur sh -eu -c ' + emulator=$1 + guest=$2 + pidfd_runner=$3 + + unshare -i sh -c "exec sleep 300" & + holder=$! + current_ipc=$(readlink /proc/self/ns/ipc) + namespace_ready=false + attempts=0 + while [ "$attempts" -lt 100 ]; do + if [ -r "/proc/$holder/ns/ipc" ] && + [ "$(readlink "/proc/$holder/ns/ipc")" != "$current_ipc" ]; then + namespace_ready=true + break + fi + attempts=$((attempts + 1)) + sleep 0.01 + done + if [ "$namespace_ready" != true ]; then + kill "$holder" 2>/dev/null || true + wait "$holder" 2>/dev/null || true + echo "FAIL: pidfd IPC namespace holder did not start" >&2 + exit 30 + fi + + set +e + "$pidfd_runner" "$holder" "$emulator" "$guest" + ret=$? + set -e + kill "$holder" 2>/dev/null || true + wait "$holder" 2>/dev/null || true + if [ "$ret" -ne 0 ]; then + echo "FAIL: pidfd setns guest exited with status $ret" >&2 + exit "$ret" + fi + echo "PASS: pidfd joined the IPC namespace" + ' sh "$emulator" "$guest" "$pidfd_runner" +} + +if command -v clang-19 >/dev/null 2>&1; then + clang=clang-19 +elif command -v clang >/dev/null 2>&1; then + clang=clang +else + echo "SKIP: clang is required to build the x86_64 guest" + exit 77 +fi + +run_case() +{ + setns_flags=$1 + case_name=$2 + guest="$workdir/setns-ipc-mqueue-$case_name" + mkdir "$workdir/$case_name" + + compile_guest "$guest" -DSETNS_FLAGS="$setns_flags" + + unshare -Urm sh -eu -c ' + emulator=$1 + guest=$2 + case_name=$3 + workdir=$4 + cd "$workdir" + mkdir mqueue + mount --make-rprivate / + trap "umount mqueue 2>/dev/null || true" EXIT HUP INT TERM + + unshare -i sh -c "exec sleep 300" & + holder=$! + current_ipc=$(readlink /proc/self/ns/ipc) + namespace_ready=false + attempts=0 + while [ "$attempts" -lt 100 ]; do + if [ -r "/proc/$holder/ns/ipc" ] && + [ "$(readlink "/proc/$holder/ns/ipc")" != "$current_ipc" ]; then + namespace_ready=true + break + fi + attempts=$((attempts + 1)) + sleep 0.01 + done + if [ "$namespace_ready" != true ]; then + kill "$holder" 2>/dev/null || true + wait "$holder" 2>/dev/null || true + echo "FAIL: IPC namespace holder did not start" >&2 + exit 30 + fi + + exec 9<"/proc/$holder/ns/ipc" + kill "$holder" + wait "$holder" 2>/dev/null || true + + LATX_AOT=1 LATX_KZT=0 "$emulator" "$guest" & + emulator_pid=$! + exec 9<&- + wait "$emulator_pid" || { + ret=$? + echo "FAIL: $case_name guest exited with status $ret" >&2 + exit "$ret" + } + + if touch mqueue/MQ2 2>/dev/null; then + echo "FAIL: $case_name left the IPC namespace alive" >&2 + exit 40 + fi + echo "PASS: $case_name released the IPC namespace" + ' sh "$emulator" "$guest" "$case_name" "$workdir/$case_name" +} + +run_invalid_fd_case +run_pidfd_case +run_case 134217728 explicit-CLONE_NEWIPC +run_case 0 inferred-from-fd