From nobody Sun Apr 07 23:57:12 2024 X-Original-To: dev-commits-src-main@mlmmj.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mlmmj.nyi.freebsd.org (Postfix) with ESMTP id 4VCTfd1GPFz5Fbcp; Sun, 7 Apr 2024 23:57:13 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "R3" (verified OK)) by mx1.freebsd.org (Postfix) with ESMTPS id 4VCTfd0XPNz4CX2; Sun, 7 Apr 2024 23:57:13 +0000 (UTC) (envelope-from git@FreeBSD.org) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1712534233; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=Jz/OoaBaghPE1fHKQplldAB761GFGFx5em8smC6eryU=; b=GOvg0n0Gxb/on815EHVrf+CTzcw/qq6FYJfy93p2eKudnBSV+46JF/iHP6bJGnktfnslae HteUMTdCq1pQdzBJoDeUbMPpTa5CuPG3MtuuIT3FOW0sOdLozoyBurfYRY3VtVJ4v3usZ+ XafhR5wbhFDwAc9nI2lgpDzO5PyB4oNbOH9PE5IVQ/12lgfJQAf72OlSRKuCrBzysKRhOr mCnoz8zbuRcvpvc8xI9L/M/FumCPci4390WD1XewazNZCMv4LVO/LngPfF5XOHugx87JjD p0ZmktjD909ncTD78m8Y+V4zeE/jk0FgqMDmCyI5QaQXDS9DsdOg6K9IerMOKA== ARC-Seal: i=1; s=dkim; d=freebsd.org; t=1712534233; a=rsa-sha256; cv=none; b=Tkk/UYIFs67gPTldrKhhI4EmaBCnT6lGu+K5MW1YBiDCwFhRj2BDcKo8AyoCVawaZOp7ch j6yUbqBEKBi3kEqWeHdDZkQx0jD3AZSRI9np+7wyuH0a4fy55CSeiEmx6/km2b/LubzIrl Bhih7TcO0kfzqWMGfzxwtGPgomxVE2Rmxii6cNG7/ywRDpSwh+mtFy/ND5lXfiu0I3vhag dONlsCOWwGc77MrJveMRHntCpPp/IUG/ALgc4V3RQVIp7guWrYxRyWzxJPO5gBDg7nuPc+ 3nMvEdQGJOxHHuOSKf8IV2ak43QJ23sf+CnfmGAoLlkRqNsMwKxMSSZvbJFdAQ== ARC-Authentication-Results: i=1; mx1.freebsd.org; none ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1712534233; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=Jz/OoaBaghPE1fHKQplldAB761GFGFx5em8smC6eryU=; b=m1Y+D4DFfAfm7ajTstqXl2SXk2wOJORi9XssmQ8DY85pLy61BzskKgudQJmj0RIQTgGwIv yngp0D+N8RfrE3bTjMAZBy4Oh12sLSDLcJWwyN/7WGz/pK/s/q7d2LKQBOYcKgeCB9T7xG uA2gQAem4ngH1oxnFzWsQst6ur9qekDvs2fXmlVPV6UZaiCkVEpoPCFZBYDcVMVDIcgRhz qaVaf8JEVRoqT8rk9LceWx5Ff0hqbj5ogxvK+TeQLzG+S5Mt2Mo0ClomWPEA87+eJFpsGa LJ8ZfxLveHfnk2aXaewoALgKL+bNYhY1gTy/mSt479DxIi2ldES9Yfc1qBmrvg== Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (Client did not present a certificate) by mxrelay.nyi.freebsd.org (Postfix) with ESMTPS id 4VCTfd084SzpTJ; Sun, 7 Apr 2024 23:57:13 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from gitrepo.freebsd.org ([127.0.1.44]) by gitrepo.freebsd.org (8.17.1/8.17.1) with ESMTP id 437NvCSp037617; Sun, 7 Apr 2024 23:57:12 GMT (envelope-from git@gitrepo.freebsd.org) Received: (from git@localhost) by gitrepo.freebsd.org (8.17.1/8.17.1/Submit) id 437NvCKn037614; Sun, 7 Apr 2024 23:57:12 GMT (envelope-from git) Date: Sun, 7 Apr 2024 23:57:12 GMT Message-Id: <202404072357.437NvCKn037614@gitrepo.freebsd.org> To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org From: Jake Freeland Subject: git: 2f39a986641e - main - tests: Add ktrace capability violation test cases List-Id: Commit messages for the main branch of the src repository List-Archive: https://lists.freebsd.org/archives/dev-commits-src-main List-Help: List-Post: List-Subscribe: List-Unsubscribe: Sender: owner-dev-commits-src-main@freebsd.org X-BeenThere: dev-commits-src-main@freebsd.org MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: jfree X-Git-Repository: src X-Git-Refname: refs/heads/main X-Git-Reftype: branch X-Git-Commit: 2f39a986641ecebaa9080ca28118903bd9707a1f Auto-Submitted: auto-generated The branch main has been updated by jfree: URL: https://cgit.FreeBSD.org/src/commit/?id=2f39a986641ecebaa9080ca28118903bd9707a1f commit 2f39a986641ecebaa9080ca28118903bd9707a1f Author: Jake Freeland AuthorDate: 2024-04-06 18:31:30 +0000 Commit: Jake Freeland CommitDate: 2024-04-07 23:52:51 +0000 tests: Add ktrace capability violation test cases Introduce regression tests for ktrace(2) that target capability violations. These test cases ensure that ktrace(2) records these violations: - CAPFAIL_NOTCAPABLE - CAPFAIL_INCREASE - CAPFAIL_SYSCALL - CAPFAIL_SIGNAL - CAPFAIL_PROTO - CAPFAIL_SOCKADDR - CAPFAIL_NAMEI - CAPFAIL_CPUSET A portion of these test cases create processes that do NOT enter capability mode, but raise violations. This is intended behavior. Users may run `ktrace -t p` on non-Capsicumized programs to detect violations that would occur if the process were in capability mode. Reviewed by: markj Approved by: markj (mentor) MFC after: 1 month Differential Revision: https://reviews.freebsd.org/D40682 --- tests/sys/kern/Makefile | 2 + tests/sys/kern/ktrace_test.c | 488 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 490 insertions(+) diff --git a/tests/sys/kern/Makefile b/tests/sys/kern/Makefile index e54a43b5fd83..bc42f53a2849 100644 --- a/tests/sys/kern/Makefile +++ b/tests/sys/kern/Makefile @@ -21,6 +21,7 @@ ATF_TESTS_C+= kill_zombie .if ${MK_OPENSSL} != "no" ATF_TESTS_C+= ktls_test .endif +ATF_TESTS_C+= ktrace_test ATF_TESTS_C+= listener_wakeup ATF_TESTS_C+= module_test ATF_TESTS_C+= ptrace_test @@ -86,6 +87,7 @@ LIBADD.socket_msg_waitall+= pthread LIBADD.sendfile_helper+= pthread LIBADD.fdgrowtable_test+= util pthread kvm procstat LIBADD.sigwait+= rt +LIBADD.ktrace_test+= sysdecode NETBSD_ATF_TESTS_C+= lockf_test NETBSD_ATF_TESTS_C+= mqueue_test diff --git a/tests/sys/kern/ktrace_test.c b/tests/sys/kern/ktrace_test.c new file mode 100644 index 000000000000..49e2ed05fed9 --- /dev/null +++ b/tests/sys/kern/ktrace_test.c @@ -0,0 +1,488 @@ +/*- + * Copyright (c) 2015 John Baldwin + * Copyright (c) 2023 The FreeBSD Foundation + * + * This software was developed by Jake Freeland + * under sponsorship from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +/* + * A variant of ATF_REQUIRE that is suitable for use in child + * processes. This only works if the parent process is tripped up by + * the early exit and fails some requirement itself. + */ +#define CHILD_REQUIRE(exp) do { \ + if (!(exp)) \ + child_fail_require(__FILE__, __LINE__, \ + #exp " not met\n"); \ +} while (0) +#define CHILD_REQUIRE_EQ(actual, expected) do { \ + __typeof__(expected) _e = expected; \ + __typeof__(actual) _a = actual; \ + if (_e != _a) \ + child_fail_require(__FILE__, __LINE__, #actual \ + " (%jd) == " #expected " (%jd) not met\n", \ + (intmax_t)_a, (intmax_t)_e); \ +} while (0) + +static __dead2 void +child_fail_require(const char *file, int line, const char *fmt, ...) +{ + va_list ap; + char buf[1024]; + + /* Use write() not fprintf() to avoid possible duplicate output. */ + snprintf(buf, sizeof(buf), "%s:%d: ", file, line); + write(STDERR_FILENO, buf, strlen(buf)); + va_start(ap, fmt); + vsnprintf(buf, sizeof(buf), fmt, ap); + write(STDERR_FILENO, buf, strlen(buf)); + va_end(ap); + + _exit(32); +} + +/* + * Determine sysdecode ABI based on proc's ABI in sv_flags. + */ +static enum sysdecode_abi +syscallabi(u_int sv_flags) +{ + switch (sv_flags & SV_ABI_MASK) { + case SV_ABI_FREEBSD: + return (SYSDECODE_ABI_FREEBSD); + case SV_ABI_LINUX: +#ifdef __LP64__ + if ((sv_flags & SV_ILP32) != 0) + return (SYSDECODE_ABI_LINUX32); +#endif + return (SYSDECODE_ABI_LINUX); + } + return (SYSDECODE_ABI_UNKNOWN); +} + +/* + * Start tracing capability violations and notify child that it can execute. + * Return @numv capability violations from child in @v. + */ +static void +cap_trace_child(int cpid, struct ktr_cap_fail *v, int numv) +{ + struct ktr_header header; + int error, fd, i; + + ATF_REQUIRE((fd = open("ktrace.out", + O_RDONLY | O_CREAT | O_TRUNC)) != -1); + ATF_REQUIRE(ktrace("ktrace.out", KTROP_SET, + KTRFAC_CAPFAIL, cpid) != -1); + /* Notify child that we've starting tracing. */ + ATF_REQUIRE(kill(cpid, SIGUSR1) != -1); + /* Wait for child to raise violation and exit. */ + ATF_REQUIRE(waitpid(cpid, &error, 0) != -1); + ATF_REQUIRE(WIFEXITED(error)); + ATF_REQUIRE_EQ(WEXITSTATUS(error), 0); + /* Read ktrace header and ensure violation occurred. */ + for (i = 0; i < numv; ++i) { + ATF_REQUIRE((error = read(fd, &header, sizeof(header))) != -1); + ATF_REQUIRE_EQ(error, sizeof(header)); + ATF_REQUIRE_EQ(header.ktr_len, sizeof(*v)); + ATF_REQUIRE_EQ(header.ktr_pid, cpid); + /* Read the capability violation. */ + ATF_REQUIRE((error = read(fd, v + i, + sizeof(*v))) != -1); + ATF_REQUIRE_EQ(error, sizeof(*v)); + } + ATF_REQUIRE(close(fd) != -1); +} + +/* + * Test if ktrace will record an operation that is done with + * insufficient rights. + */ +ATF_TC_WITHOUT_HEAD(ktrace__cap_not_capable); +ATF_TC_BODY(ktrace__cap_not_capable, tc) +{ + struct ktr_cap_fail violation; + cap_rights_t rights; + sigset_t set = { }; + pid_t pid; + int error; + + /* Block SIGUSR1 so child does not terminate. */ + ATF_REQUIRE(sigaddset(&set, SIGUSR1) != -1); + ATF_REQUIRE(sigprocmask(SIG_BLOCK, &set, NULL) != -1); + + ATF_REQUIRE((pid = fork()) != -1); + if (pid == 0) { + /* Limit fd rights to CAP_READ. */ + cap_rights_init(&rights, CAP_READ); + CHILD_REQUIRE(caph_rights_limit(STDIN_FILENO, &rights) != -1); + CHILD_REQUIRE(caph_enter() != -1); + /* Wait until ktrace has started. */ + CHILD_REQUIRE(sigwait(&set, &error) != -1); + CHILD_REQUIRE_EQ(error, SIGUSR1); + /* Write without CAP_WRITE. */ + CHILD_REQUIRE(write(STDIN_FILENO, &pid, sizeof(pid)) == -1); + CHILD_REQUIRE_EQ(errno, ENOTCAPABLE); + exit(0); + } + + cap_trace_child(pid, &violation, 1); + ATF_REQUIRE_EQ(violation.cap_type, CAPFAIL_NOTCAPABLE); + ATF_REQUIRE(cap_rights_is_set(&violation.cap_data.cap_needed, + CAP_WRITE)); +} + +/* + * Test if ktrace will record an attempt to increase rights. + */ +ATF_TC_WITHOUT_HEAD(ktrace__cap_increase_rights); +ATF_TC_BODY(ktrace__cap_increase_rights, tc) +{ + struct ktr_cap_fail violation; + cap_rights_t rights; + sigset_t set = { }; + pid_t pid; + int error; + + /* Block SIGUSR1 so child does not terminate. */ + ATF_REQUIRE(sigaddset(&set, SIGUSR1) != -1); + ATF_REQUIRE(sigprocmask(SIG_BLOCK, &set, NULL) != -1); + + ATF_REQUIRE((pid = fork()) != -1); + if (pid == 0) { + /* Limit fd rights to CAP_READ. */ + cap_rights_init(&rights, CAP_READ); + CHILD_REQUIRE(caph_rights_limit(STDIN_FILENO, &rights) != -1); + CHILD_REQUIRE(caph_enter() != -1); + /* Wait until ktrace has started. */ + CHILD_REQUIRE(sigwait(&set, &error) != -1); + CHILD_REQUIRE_EQ(error, SIGUSR1); + /* Increase fd rights to include CAP_WRITE. */ + cap_rights_set(&rights, CAP_WRITE); + CHILD_REQUIRE(caph_rights_limit(STDIN_FILENO, &rights) == -1); + CHILD_REQUIRE_EQ(errno, ENOTCAPABLE); + exit(0); + } + + cap_trace_child(pid, &violation, 1); + ATF_REQUIRE_EQ(violation.cap_type, CAPFAIL_INCREASE); + ATF_REQUIRE(cap_rights_is_set(&violation.cap_data.cap_needed, + CAP_WRITE)); +} + +/* + * Test if disallowed syscalls are reported as capability violations. + */ +ATF_TC_WITHOUT_HEAD(ktrace__cap_syscall); +ATF_TC_BODY(ktrace__cap_syscall, tc) +{ + struct kinfo_file kinf; + struct ktr_cap_fail violation[2]; + sigset_t set = { }; + pid_t pid; + int error; + + /* Block SIGUSR1 so child does not terminate. */ + ATF_REQUIRE(sigaddset(&set, SIGUSR1) != -1); + ATF_REQUIRE(sigprocmask(SIG_BLOCK, &set, NULL) != -1); + + ATF_REQUIRE((pid = fork()) != -1); + if (pid == 0) { + /* Wait until ktrace has started. */ + CHILD_REQUIRE(sigwait(&set, &error) != -1); + CHILD_REQUIRE_EQ(error, SIGUSR1); + /* chdir() is not permitted in capability mode. */ + CHILD_REQUIRE(chdir(".") != -1); + kinf.kf_structsize = sizeof(struct kinfo_file); + /* + * fcntl() is permitted in capability mode, + * but the F_KINFO cmd is not. + */ + CHILD_REQUIRE(fcntl(STDIN_FILENO, F_KINFO, &kinf) != -1); + exit(0); + } + + cap_trace_child(pid, violation, 2); + ATF_REQUIRE_EQ(violation[0].cap_type, CAPFAIL_SYSCALL); + error = syscallabi(violation[0].cap_svflags); + ATF_REQUIRE_STREQ(sysdecode_syscallname(error, violation[0].cap_code), + "chdir"); + + ATF_REQUIRE_EQ(violation[1].cap_type, CAPFAIL_SYSCALL); + error = syscallabi(violation[1].cap_svflags); + ATF_REQUIRE_STREQ(sysdecode_syscallname(error, violation[1].cap_code), + "fcntl"); + ATF_REQUIRE_EQ(violation[1].cap_data.cap_int, F_KINFO); +} + +/* + * Test if sending a signal to another process is reported as + * a signal violation. + */ +ATF_TC_WITHOUT_HEAD(ktrace__cap_signal); +ATF_TC_BODY(ktrace__cap_signal, tc) +{ + struct ktr_cap_fail violation; + sigset_t set = { }; + pid_t pid; + int error; + + /* Block SIGUSR1 so child does not terminate. */ + ATF_REQUIRE(sigaddset(&set, SIGUSR1) != -1); + ATF_REQUIRE(sigprocmask(SIG_BLOCK, &set, NULL) != -1); + + ATF_REQUIRE((pid = fork()) != -1); + if (pid == 0) { + /* Wait until ktrace has started. */ + CHILD_REQUIRE(sigwait(&set, &error) != -1); + CHILD_REQUIRE_EQ(error, SIGUSR1); + /* + * Signals may only be sent to ourself. Sending signals + * to other processes is not allowed in capability mode. + */ + CHILD_REQUIRE(kill(getppid(), SIGCONT) != -1); + exit(0); + } + + cap_trace_child(pid, &violation, 1); + ATF_REQUIRE_EQ(violation.cap_type, CAPFAIL_SIGNAL); + error = syscallabi(violation.cap_svflags); + ATF_REQUIRE_STREQ(sysdecode_syscallname(error, violation.cap_code), + "kill"); + ATF_REQUIRE_EQ(violation.cap_data.cap_int, SIGCONT); +} + +/* + * Test if opening a socket with a restricted protocol is reported + * as a protocol violation. + */ +ATF_TC_WITHOUT_HEAD(ktrace__cap_proto); +ATF_TC_BODY(ktrace__cap_proto, tc) +{ + struct ktr_cap_fail violation; + sigset_t set = { }; + pid_t pid; + int error; + + /* Block SIGUSR1 so child does not terminate. */ + ATF_REQUIRE(sigaddset(&set, SIGUSR1) != -1); + ATF_REQUIRE(sigprocmask(SIG_BLOCK, &set, NULL) != -1); + + ATF_REQUIRE((pid = fork()) != -1); + if (pid == 0) { + /* Wait until ktrace has started. */ + CHILD_REQUIRE(sigwait(&set, &error) != -1); + CHILD_REQUIRE_EQ(error, SIGUSR1); + /* + * Certain protocols may not be used in capability mode. + * ICMP's raw-protocol interface is not allowed. + */ + CHILD_REQUIRE(close(socket(AF_INET, SOCK_RAW, + IPPROTO_ICMP)) != -1); + exit(0); + } + + cap_trace_child(pid, &violation, 1); + ATF_REQUIRE_EQ(violation.cap_type, CAPFAIL_PROTO); + error = syscallabi(violation.cap_svflags); + ATF_REQUIRE_STREQ(sysdecode_syscallname(error, violation.cap_code), + "socket"); + ATF_REQUIRE_EQ(violation.cap_data.cap_int, IPPROTO_ICMP); +} + +/* + * Test if sending data to an address using a socket is + * reported as a sockaddr violation. + */ +ATF_TC_WITHOUT_HEAD(ktrace__cap_sockaddr); +ATF_TC_BODY(ktrace__cap_sockaddr, tc) +{ + struct sockaddr_in addr = { }, *saddr; + struct ktr_cap_fail violation; + sigset_t set = { }; + pid_t pid; + int error, sfd; + + /* Block SIGUSR1 so child does not terminate. */ + ATF_REQUIRE(sigaddset(&set, SIGUSR1) != -1); + ATF_REQUIRE(sigprocmask(SIG_BLOCK, &set, NULL) != -1); + + CHILD_REQUIRE((sfd = socket(AF_INET, SOCK_DGRAM, + IPPROTO_UDP)) != -1); + addr.sin_family = AF_INET; + addr.sin_port = htons(5000); + addr.sin_addr.s_addr = INADDR_ANY; + CHILD_REQUIRE(bind(sfd, (const struct sockaddr *)&addr, + sizeof(addr)) != -1); + + ATF_REQUIRE((pid = fork()) != -1); + if (pid == 0) { + /* Wait until ktrace has started. */ + CHILD_REQUIRE(sigwait(&set, &error) != -1); + CHILD_REQUIRE_EQ(error, SIGUSR1); + /* + * Sending data to an address is not permitted. + * In this case, sending data to @addr causes a + * violation. + */ + CHILD_REQUIRE(sendto(sfd, NULL, 0, 0, + (const struct sockaddr *)&addr, sizeof(addr)) != -1); + exit(0); + } + + cap_trace_child(pid, &violation, 1); + ATF_REQUIRE_EQ(violation.cap_type, CAPFAIL_SOCKADDR); + error = syscallabi(violation.cap_svflags); + ATF_REQUIRE_STREQ(sysdecode_syscallname(error, violation.cap_code), + "sendto"); + saddr = (struct sockaddr_in *)&violation.cap_data.cap_sockaddr; + ATF_REQUIRE_EQ(saddr->sin_family, AF_INET); + ATF_REQUIRE_EQ(saddr->sin_port, htons(5000)); + ATF_REQUIRE_EQ(saddr->sin_addr.s_addr, INADDR_ANY); + close(sfd); +} + +/* + * Test if openat() with AT_FDCWD and absolute path are reported + * as namei violations. + */ +ATF_TC_WITHOUT_HEAD(ktrace__cap_namei); +ATF_TC_BODY(ktrace__cap_namei, tc) +{ + struct ktr_cap_fail violation[2]; + sigset_t set = { }; + pid_t pid; + int error; + + /* Block SIGUSR1 so child does not terminate. */ + ATF_REQUIRE(sigaddset(&set, SIGUSR1) != -1); + ATF_REQUIRE(sigprocmask(SIG_BLOCK, &set, NULL) != -1); + + ATF_REQUIRE((pid = fork()) != -1); + if (pid == 0) { + /* Wait until ktrace has started. */ + CHILD_REQUIRE(sigwait(&set, &error) != -1); + CHILD_REQUIRE_EQ(error, SIGUSR1); + /* + * The AT_FDCWD file descriptor has not been opened + * and will be inaccessible in capability mode. + */ + CHILD_REQUIRE(close(openat(AT_FDCWD, "ktrace.out", + O_RDONLY | O_CREAT)) != -1); + /* + * Absolute paths are inaccessible in capability mode. + */ + CHILD_REQUIRE(close(openat(-1, "/", O_RDONLY)) != -1); + exit(0); + } + + cap_trace_child(pid, violation, 2); + ATF_REQUIRE_EQ(violation[0].cap_type, CAPFAIL_NAMEI); + error = syscallabi(violation[0].cap_svflags); + ATF_REQUIRE_STREQ(sysdecode_syscallname(error, violation[0].cap_code), + "openat"); + ATF_REQUIRE_STREQ(violation[0].cap_data.cap_path, "AT_FDCWD"); + + ATF_REQUIRE_EQ(violation[1].cap_type, CAPFAIL_NAMEI); + error = syscallabi(violation[1].cap_svflags); + ATF_REQUIRE_STREQ(sysdecode_syscallname(error, violation[1].cap_code), + "openat"); + ATF_REQUIRE_STREQ(violation[1].cap_data.cap_path, "/"); +} + +/* + * Test if changing another process's cpu set is recorded as + * a cpuset violation. + */ +ATF_TC_WITHOUT_HEAD(ktrace__cap_cpuset); +ATF_TC_BODY(ktrace__cap_cpuset, tc) +{ + struct ktr_cap_fail violation; + cpuset_t cpuset_mask = { }; + sigset_t set = { }; + pid_t pid; + int error; + + /* Block SIGUSR1 so child does not terminate. */ + ATF_REQUIRE(sigaddset(&set, SIGUSR1) != -1); + ATF_REQUIRE(sigprocmask(SIG_BLOCK, &set, NULL) != -1); + + ATF_REQUIRE((pid = fork()) != -1); + if (pid == 0) { + /* Wait until ktrace has started. */ + CHILD_REQUIRE(sigwait(&set, &error) != -1); + CHILD_REQUIRE_EQ(error, SIGUSR1); + /* + * Set cpu 0 affinity for parent process. + * Other process's cpu sets are restricted in capability + * mode, so this will raise a violation. + */ + CPU_SET(0, &cpuset_mask); + CHILD_REQUIRE(cpuset_setaffinity(CPU_LEVEL_WHICH, CPU_WHICH_PID, + getppid(), sizeof(cpuset_mask), &cpuset_mask) != -1); + exit(0); + } + + cap_trace_child(pid, &violation, 1); + ATF_REQUIRE_EQ(violation.cap_type, CAPFAIL_CPUSET); + error = syscallabi(violation.cap_svflags); + ATF_REQUIRE_STREQ(sysdecode_syscallname(error, violation.cap_code), + "cpuset_setaffinity"); +} + +ATF_TP_ADD_TCS(tp) +{ + ATF_TP_ADD_TC(tp, ktrace__cap_not_capable); + ATF_TP_ADD_TC(tp, ktrace__cap_increase_rights); + ATF_TP_ADD_TC(tp, ktrace__cap_syscall); + ATF_TP_ADD_TC(tp, ktrace__cap_signal); + ATF_TP_ADD_TC(tp, ktrace__cap_proto); + ATF_TP_ADD_TC(tp, ktrace__cap_sockaddr); + ATF_TP_ADD_TC(tp, ktrace__cap_namei); + ATF_TP_ADD_TC(tp, ktrace__cap_cpuset); + return (atf_no_error()); +}