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 <y347812075@163.com>
This commit is contained in:
yuerengan 2026-08-01 09:57:12 +08:00 committed by Hanlu Li
parent 7523f1ff5e
commit 8299cef200
5 changed files with 289 additions and 1 deletions

View File

@ -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:

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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