From nobody Tue Aug 18 13:51:05 2026 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 4hPWLy1J9Cz6pDVn for ; Tue, 18 Aug 2026 13:51:06 +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 "YR2" (not verified)) by mx1.freebsd.org (Postfix) with ESMTPS id 4hPWLy0DxJz3VFT for ; Tue, 18 Aug 2026 13:51:06 +0000 (UTC) (envelope-from git@FreeBSD.org) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1787061066; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=qfqYKJzAbBMMFbkSx9o76y1riz1CeR5pc0zJLK/8Vuw=; b=FlpzIUpJjL+cY6SrZej11iJ/MhxAIuuoj9JyQkmbR1KoXIJIBb0grug/AfhFaBTG7YSv76 sgItAEhCk+NGOptDTYOwP8myXu6ZPwBM/3ilXKZA+ykyiYP9PQtnid6ntbgCd4FPU44M8d bxOygYmBp4/QlWgOREnOOXQyeTMTTn0eJAE8R24RjgtUwLRKzfWIlutikBMw/dm9aM7F8s dJ1odAUxk0UYsrWIPMZn7ZxyS35BWeNACoMJSuDdyjOQDGUt6fhgEbM/SVJ2BF12qa57UR 2i3l11m1bUEM1fwe2x+7X2GMdWlsOfaLDu80KueX5T01gpd8TY1+XTgk0Nzx6w== ARC-Seal: i=1; s=dkim; d=freebsd.org; t=1787061066; a=rsa-sha256; cv=none; b=Lbm4hn7FE4fTJ+5QJAxe3ag1pfes7WeVgAprKo+j+8uQV0sy+CFZM9xPuwXI+XAO74SC5x el/kuJ4kzZvsEav59uuoJX/bcOnpab3FN4RRb6F0UGLuprJAg0tL2IfTJdG7ALI8RJI8Ir XYO6nlwO3n/L0cF1l+9ci+/SINsU+ziSUkpfXiNcCVhKdwUGFg0SyCzowg+6Xn+q3r7glX 0r3K+TD4hCtZFvSnnaFuIsyXmphxbVaStbbNVAJBofWc78gF8bA0XAUhRXjcF365AzEJoT bvn0rwDuzHSWuQJFfv9YIVLSz1eEjibJtEgueQw736Lt7nZIxh26U3xPJea4dA== 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=1787061066; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=qfqYKJzAbBMMFbkSx9o76y1riz1CeR5pc0zJLK/8Vuw=; b=EthBAKFNNXzl7YfUCr38cgD08NTwkg4TwkM9QEkcECHvHa2yv8Qiy2pMa7xpbURVYcN6fn porOwYqG3TNDdXhuKSjUpqhAN7UjYszORUdxbIPrB3Xv/cAKNnjKB4gO0+ehESERJIVgtm 37Yw7jihtqztcMsNS3h9aedgDXtGcqPcZJG5vRdG3lLiA5MG8kgwBDHp0eobpfHiHsGcB/ eIVWx3XyPXLBdbPCTcrnMfkSUM74JNcOiM+2Mqr+666A0hnDoz6bbfp0CG8chx17tV1kgC T8gIf7kvGJK7Jk7jyr684NoCJ1PbkU9nZ+t3X9sfQDqrDby+YcAcSIt+ENVmGQ== Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) by mxrelay.nyi.freebsd.org (Postfix) with ESMTP id 4hPWLx68vSznF8 for ; Tue, 18 Aug 2026 13:51:05 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from git (uid 1279) (envelope-from git@FreeBSD.org) id 18331 by gitrepo.freebsd.org (DragonFly Mail Agent v0.13+ on gitrepo.freebsd.org); Tue, 18 Aug 2026 13:51:05 +0000 To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org Cc: Jitendra Bhati From: Alan Somers Subject: git: 31f9f09bf7b9 - main - fts: add fts_capsicum_test.c 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: X-BeenThere: dev-commits-src-main@freebsd.org Sender: owner-dev-commits-src-main@FreeBSD.org List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: asomers X-Git-Repository: src X-Git-Refname: refs/heads/main X-Git-Reftype: branch X-Git-Commit: 31f9f09bf7b9b53533c9b731d9fade73eae379cc Auto-Submitted: auto-generated Date: Tue, 18 Aug 2026 13:51:05 +0000 Message-Id: <6a846349.18331.5318604b@gitrepo.freebsd.org> The branch main has been updated by asomers: URL: https://cgit.FreeBSD.org/src/commit/?id=31f9f09bf7b9b53533c9b731d9fade73eae379cc commit 31f9f09bf7b9b53533c9b731d9fade73eae379cc Author: Jitendra Bhati AuthorDate: 2026-07-18 23:19:52 +0000 Commit: Alan Somers CommitDate: 2026-08-18 13:50:41 +0000 fts: add fts_capsicum_test.c Add three test cases verifying fts(3) Capsicum capability mode: - fts_dirfd_valid: verifies fts_dirfd is set for all non-root entries and openat(fts_dirfd, fts_name) identifies the same inode as fts_accpath - fts_dirfd_capsicum: verifies complete fts traversal works in Capsicum capability mode using fts_openat() and fts_dirfd - fts_dirfd_deep_tree: verifies fts_dirfd + fts_name is correct at all directory depths (7 non-root entries) Sponsored by: Google LLC (GSoC 2026) Reviewed by: asomers Pull Request: https://github.com/freebsd/freebsd-src/pull/2332 --- lib/libc/tests/gen/Makefile | 1 + lib/libc/tests/gen/fts_capsicum_test.c | 210 +++++++++++++++++++++++++++++++++ 2 files changed, 211 insertions(+) diff --git a/lib/libc/tests/gen/Makefile b/lib/libc/tests/gen/Makefile index 1e26e46e8ddb..56d029531c54 100644 --- a/lib/libc/tests/gen/Makefile +++ b/lib/libc/tests/gen/Makefile @@ -13,6 +13,7 @@ ATF_TESTS_C+= fpclassify2_test .if ${COMPILER_FEATURES:Mblocks} ATF_TESTS_C+= fts_blocks_test .endif +ATF_TESTS_C+= fts_capsicum_test ATF_TESTS_C+= fts_children_test ATF_TESTS_C+= fts_misc_test ATF_TESTS_C+= fts_open_test diff --git a/lib/libc/tests/gen/fts_capsicum_test.c b/lib/libc/tests/gen/fts_capsicum_test.c new file mode 100644 index 000000000000..d48512fc70ae --- /dev/null +++ b/lib/libc/tests/gen/fts_capsicum_test.c @@ -0,0 +1,210 @@ +/* + * Copyright (c) 2026 Jitendra Bhati + * + * SPDX-License-Identifier: BSD-2-Clause + * + * Tests for fts(3) in Capsicum capability mode using fts_dirfd. + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +/* + * Verify that fts_dirfd is set to a valid file descriptor for every + * entry returned by fts_read(), and that openat(fts_dirfd, fts_name) + * correctly identifies the same file as fts_accpath. + */ +ATF_TC(fts_dirfd_valid); +ATF_TC_HEAD(fts_dirfd_valid, tc) +{ + atf_tc_set_md_var(tc, "descr", + "fts_dirfd is valid for all non-root entries"); +} +ATF_TC_BODY(fts_dirfd_valid, tc) +{ + char *paths[] = { "dir", NULL }; + FTS *fts; + FTSENT *ent; + struct stat sb_accpath, sb_dirfd; + + ATF_REQUIRE_EQ(0, mkdir("dir", 0755)); + ATF_REQUIRE_EQ(0, mkdir("dir/sub", 0755)); + ATF_REQUIRE_EQ(0, close(creat("dir/sub/file", 0644))); + ATF_REQUIRE_EQ(0, close(creat("dir/other", 0644))); + + ATF_REQUIRE((fts = fts_open(paths, FTS_PHYSICAL, NULL)) != NULL); + + while ((ent = fts_read(fts)) != NULL) { + if (ent->fts_info == FTS_D || ent->fts_info == FTS_DP) + ATF_REQUIRE_MSG(ent->fts_dirfd >= 0, + "fts_dirfd must be valid for dir '%s' level %ld", + ent->fts_name, ent->fts_level); + + ATF_REQUIRE_EQ_MSG(0, + fstatat(ent->fts_parent->fts_dirfd, ent->fts_name, &sb_dirfd, + AT_SYMLINK_NOFOLLOW), + "fstatat(fts_dirfd, fts_name) failed for '%s': %m", + ent->fts_name); + ATF_REQUIRE_EQ_MSG(0, + lstat(ent->fts_accpath, &sb_accpath), + "lstat(fts_accpath) failed for '%s': %m", + ent->fts_accpath); + + ATF_CHECK_EQ_MSG(sb_accpath.st_ino, sb_dirfd.st_ino, + "inode mismatch for '%s': " + "fts_accpath ino=%ju fts_dirfd ino=%ju", + ent->fts_name, + (uintmax_t)sb_accpath.st_ino, + (uintmax_t)sb_dirfd.st_ino); + ATF_CHECK_EQ_MSG(sb_accpath.st_dev, sb_dirfd.st_dev, + "device mismatch for '%s'", ent->fts_name); + } + + ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close(): %m"); +} + +/* + * Verify that fts traversal works correctly in Capsicum capability + * mode using openat(fts_dirfd, fts_name) to access files. + */ +ATF_TC(fts_dirfd_capsicum); +ATF_TC_HEAD(fts_dirfd_capsicum, tc) +{ + atf_tc_set_md_var(tc, "descr", + "fts traversal using fts_dirfd works in Capsicum capability mode"); +} +ATF_TC_BODY(fts_dirfd_capsicum, tc) +{ + if (!feature_present("security_capabilities") || + !feature_present("security_capability_mode")) + atf_tc_skip("Capsicum not available"); + + char *paths[] = { ".", NULL }; + FTS *fts; + FTSENT *ent; + int dirfd; + bool saw_file, saw_sub; + + ATF_REQUIRE_EQ(0, mkdir("dir", 0755)); + ATF_REQUIRE_EQ(0, mkdir("dir/sub", 0755)); + ATF_REQUIRE_EQ(0, close(creat("dir/sub/file", 0644))); + ATF_REQUIRE_EQ(0, close(creat("dir/other", 0644))); + + ATF_REQUIRE((dirfd = open("dir", O_RDONLY | O_DIRECTORY)) >= 0); + + /* Enter capability mode before fts_openat. */ + ATF_REQUIRE_EQ(0, cap_enter()); + + ATF_REQUIRE((fts = fts_openat(dirfd, paths, + FTS_PHYSICAL | FTS_NOCHDIR, NULL)) != NULL); + close(dirfd); + + saw_file = false; + saw_sub = false; + + while ((ent = fts_read(fts)) != NULL) { + if (ent->fts_info == FTS_DP) + continue; + + if (strcmp(ent->fts_name, "sub") == 0 && + ent->fts_info == FTS_D) + saw_sub = true; + + if (strcmp(ent->fts_name, "file") == 0) + saw_file = true; + + if (ent->fts_level == FTS_ROOTLEVEL) + continue; + + struct stat sb; + ATF_CHECK_EQ_MSG(0, + fstatat(ent->fts_parent->fts_dirfd, ent->fts_name, &sb, + AT_SYMLINK_NOFOLLOW), + "fstatat(fts_dirfd, fts_name) failed for " + "'%s' in capability mode: %m", + ent->fts_name); + } + + ATF_CHECK_MSG(saw_sub, "must have visited 'sub' directory"); + ATF_CHECK_MSG(saw_file, "must have visited 'file'"); + ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close(): %m"); +} + +/* + * Verify that fts_dirfd + fts_name correctly identifies files even + * when fts has internally chdir'd into subdirectories. + */ +ATF_TC(fts_dirfd_deep_tree); +ATF_TC_HEAD(fts_dirfd_deep_tree, tc) +{ + atf_tc_set_md_var(tc, "descr", + "fts_dirfd + fts_name is correct at all directory depths"); +} +ATF_TC_BODY(fts_dirfd_deep_tree, tc) +{ + char *paths[] = { "dir", NULL }; + FTS *fts; + FTSENT *ent; + struct stat sb_dirfd, sb_accpath; + int depth_checked = 0; + + ATF_REQUIRE_EQ(0, mkdir("dir", 0755)); + ATF_REQUIRE_EQ(0, mkdir("dir/a", 0755)); + ATF_REQUIRE_EQ(0, mkdir("dir/a/b", 0755)); + ATF_REQUIRE_EQ(0, mkdir("dir/a/b/c", 0755)); + ATF_REQUIRE_EQ(0, close(creat("dir/a/b/c/deep", 0644))); + ATF_REQUIRE_EQ(0, close(creat("dir/a/b/mid", 0644))); + ATF_REQUIRE_EQ(0, close(creat("dir/a/top", 0644))); + ATF_REQUIRE_EQ(0, close(creat("dir/root", 0644))); + + ATF_REQUIRE((fts = fts_open(paths, FTS_PHYSICAL, NULL)) != NULL); + + while ((ent = fts_read(fts)) != NULL) { + if (ent->fts_info == FTS_DP || + ent->fts_level == FTS_ROOTLEVEL) + continue; + + ATF_REQUIRE_MSG(ent->fts_parent->fts_dirfd >= 0, + "fts_parent->fts_dirfd must be valid for '%s' at level %ld", + ent->fts_name, ent->fts_level); + + ATF_REQUIRE_EQ_MSG(0, + fstatat(ent->fts_parent->fts_dirfd, ent->fts_name, &sb_dirfd, + AT_SYMLINK_NOFOLLOW), + "fstatat failed for '%s': %m", ent->fts_name); + ATF_REQUIRE_EQ_MSG(0, + lstat(ent->fts_accpath, &sb_accpath), + "lstat failed for '%s': %m", ent->fts_accpath); + + ATF_CHECK_EQ_MSG(sb_accpath.st_ino, sb_dirfd.st_ino, + "inode mismatch at depth %ld for '%s'", + ent->fts_level, ent->fts_name); + + depth_checked++; + } + + /* 4 files (deep, mid, top, root) + 3 dirs (a, b, c) = 7 entries */ + ATF_CHECK_EQ_MSG(7, depth_checked, + "expected 7 entries, got %d", depth_checked); + + ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close(): %m"); +} + +ATF_TP_ADD_TCS(tp) +{ + ATF_TP_ADD_TC(tp, fts_dirfd_valid); + ATF_TP_ADD_TC(tp, fts_dirfd_capsicum); + ATF_TP_ADD_TC(tp, fts_dirfd_deep_tree); + + return (atf_no_error()); +}