From 2b0deb59099a3ad616e6dbabee1a04abeb8ba53c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E9=82=A3=E5=87=A0?= <2078634206@qq.com> Date: Sat, 22 Aug 2026 01:05:16 +0800 Subject: [PATCH] selftests: tkernel: cover the ttools ptrace protection ioctls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ttools misc device exposes a stable ioctl ABI (TTOOLS_PTRACE_PROTECT / TTOOLS_PTRACE_UNPROTECT / TTOOLS_GET_FD_REFS_CNT on /dev/ttools) but has no test coverage at all, while two in-flight fixes (#954, #967) touch its module init path. Add ttools_test.c implementing the four contract checks and a ttools.sh runner following the existing tkernel selftest style: attach works on an unprotected child, is rejected with EPERM after TTOOLS_PTRACE_PROTECT, works again after TTOOLS_PTRACE_UNPROTECT, and TTOOLS_GET_FD_REFS_CNT reports a positive reference count for a held fd. The script skips when the device is absent and the ttools module cannot be loaded. Upstream status: downstream-only Signed-off-by: 陈那几 <2078634206@qq.com> --- tools/testing/selftests/tkernel/Makefile | 4 +- tools/testing/selftests/tkernel/ttools.sh | 67 +++++++++ tools/testing/selftests/tkernel/ttools_test.c | 130 ++++++++++++++++++ 3 files changed, 199 insertions(+), 2 deletions(-) create mode 100755 tools/testing/selftests/tkernel/ttools.sh create mode 100644 tools/testing/selftests/tkernel/ttools_test.c diff --git a/tools/testing/selftests/tkernel/Makefile b/tools/testing/selftests/tkernel/Makefile index aa12e4919685..c83938e17631 100644 --- a/tools/testing/selftests/tkernel/Makefile +++ b/tools/testing/selftests/tkernel/Makefile @@ -1,8 +1,8 @@ # SPDX-License-Identifier: GPL-2.0 -TEST_GEN_FILES := netbind_test shield_mounts_test signal_test +TEST_GEN_FILES := netbind_test shield_mounts_test signal_test ttools_test TEST_PROGS := netbind.sh killprotect.sh killblock.sh irqlatency.sh \ - memcg_async.sh shield_mounts.sh + memcg_async.sh shield_mounts.sh ttools.sh TEST_FILES := tkernel.sh include ../lib.mk diff --git a/tools/testing/selftests/tkernel/ttools.sh b/tools/testing/selftests/tkernel/ttools.sh new file mode 100755 index 000000000000..7c2da97344b3 --- /dev/null +++ b/tools/testing/selftests/tkernel/ttools.sh @@ -0,0 +1,67 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 + +DEV=/dev/ttools +TESTS=4 +module_loaded=0 + +script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +helper="$script_dir/ttools_test" +. "$script_dir/tkernel.sh" + +skip_unavailable() +{ + if [ "$module_loaded" -eq 1 ]; then + modprobe -r ttools 2>/dev/null || true + fi + ksft_skip_all "$1" +} + +cleanup() +{ + if [ -n "$was_protected" ]; then + "$helper" attach_unprotected_again >/dev/null 2>&1 || true + fi + if [ "$module_loaded" -eq 1 ]; then + modprobe -r ttools 2>/dev/null || true + fi +} + +echo "TAP version 13" + +[ "$(id -u)" -eq 0 ] || skip_unavailable "root privileges are required" +[ -x "$helper" ] || skip_unavailable "ttools_test helper is missing" + +if [ ! -e "$DEV" ] && command -v modprobe >/dev/null 2>&1; then + if modprobe ttools 2>/dev/null; then + module_loaded=1 + fi +fi +[ -c "$DEV" ] || skip_unavailable "CONFIG_TKERNEL_TTOOLS is not enabled" + +trap cleanup EXIT INT TERM +ksft_plan "$TESTS" + +"$helper" attach_unprotected +rc=$? +[ "$rc" -ne 2 ] +ksft_result $? "an unprotected process can be ptraced" + +"$helper" attach_protected +rc=$? +[ "$rc" -eq 1 ] +was_protected=1 +ksft_result $? "a protected process rejects PTRACE_ATTACH" + +"$helper" attach_unprotected_again +rc=$? +was_protected="" +[ "$rc" -eq 0 ] +ksft_result $? "protection can be removed again" + +"$helper" fd_refs +rc=$? +[ "$rc" -eq 0 ] +ksft_result $? "fd reference counts can be queried" + +ksft_finished diff --git a/tools/testing/selftests/tkernel/ttools_test.c b/tools/testing/selftests/tkernel/ttools_test.c new file mode 100644 index 000000000000..5804cdd9644b --- /dev/null +++ b/tools/testing/selftests/tkernel/ttools_test.c @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: GPL-2.0 +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define KSFT_SKIP 4 + +#define TTOOLS_IO 0xEE + +struct ttools_fd_ref { + int fd; + long ref_cnt; +}; + +#define TTOOLS_PTRACE_PROTECT _IO(TTOOLS_IO, 0x00) +#define TTOOLS_PTRACE_UNPROTECT _IO(TTOOLS_IO, 0x01) +#define TTOOLS_GET_FD_REFS_CNT _IOWR(TTOOLS_IO, 0x02, struct ttools_fd_ref) + +static int dev_fd = -1; + +static int open_device(void) +{ + dev_fd = open("/dev/ttools", O_RDWR | O_CLOEXEC); + return dev_fd < 0 ? -1 : 0; +} + +/* + * ptrace attach a child and report whether the attach was allowed. + * Returns 0 when the child was traced (and detached again), 1 when + * the attach was denied, 2 on harness errors. + */ +static int try_attach(pid_t pid) +{ + int status; + + if (ptrace(PTRACE_ATTACH, pid, NULL, NULL) == 0) { + if (waitpid(pid, &status, 0) != pid) + return 2; + if (ptrace(PTRACE_DETACH, pid, NULL, NULL)) + return 2; + return 0; + } + if (errno == EPERM) + return 1; + return 2; +} + +static int run_attach_child(void) +{ + pid_t pid; + int rc; + + pid = fork(); + if (pid < 0) + return 2; + if (pid == 0) { + pause(); + _exit(0); + } + + rc = try_attach(pid); + + kill(pid, SIGKILL); + waitpid(pid, NULL, 0); + return rc; +} + +int main(int argc, char **argv) +{ + struct ttools_fd_ref ref; + const char *command; + + if (argc < 2) { + fprintf(stderr, "usage: %s \n", argv[0]); + return 2; + } + command = argv[1]; + + if (open_device()) { + if (errno == ENOENT) + return KSFT_SKIP; + perror("open /dev/ttools"); + return 2; + } + + if (!strcmp(command, "attach_unprotected")) + return run_attach_child(); + + if (!strcmp(command, "attach_protected")) { + if (ioctl(dev_fd, TTOOLS_PTRACE_PROTECT)) { + perror("TTOOLS_PTRACE_PROTECT"); + return 2; + } + return run_attach_child(); + } + + if (!strcmp(command, "attach_unprotected_again")) { + if (ioctl(dev_fd, TTOOLS_PTRACE_UNPROTECT)) { + perror("TTOOLS_PTRACE_UNPROTECT"); + return 2; + } + return run_attach_child(); + } + + if (!strcmp(command, "fd_refs")) { + memset(&ref, 0, sizeof(ref)); + ref.fd = dev_fd; + if (ioctl(dev_fd, TTOOLS_GET_FD_REFS_CNT, &ref)) { + perror("TTOOLS_GET_FD_REFS_CNT"); + return 2; + } + if (ref.ref_cnt < 1) { + fprintf(stderr, "unexpected ref count %ld\n", ref.ref_cnt); + return 1; + } + return 0; + } + + fprintf(stderr, "unknown command: %s\n", command); + return 2; +} -- Gitee