[PATCH v4 1/8] fs/acl: Add ACL_USER_OBJ permissions test
Sachin Sant <[email protected]>
| Newsgroups | gmane.linux.ltp |
|---|---|
| Message-ID | <[email protected]> |
Add acl_user_obj01 test to validate ACL_USER_OBJ (owner) permissions. This test verifies that: - Traditional permission bits restrict owner access - ACL_USER_OBJ entries override permission bits - Owner permissions work correctly with ACL entries The test creates a directory with restrictive permissions, verifies that file creation is denied, then sets ACL_USER_OBJ with write permissions and confirms that file creation succeeds. The patch also adds acl_lib.h containing shared helper functions and definitions for ACL and extended attribute tests. This library provides: - Test user management (ltpuser1, ltpuser2, ltpuser3) - ACL entry creation and manipulation helpers - File operation helpers with different uid/gid - Test path management and cleanup functions - Extended attribute test definitions - reset_test_path_no_chown variant to skip chown step Signed-off-by: Sachin Sant <[email protected]> --- V4 changes: - Add -U flag in create_user_if_needed() to useradd for guaranteed user-private groups. - Move EOPNOTSUPP handling into set_acl_file() helper - v3 link https://lore.kernel.org/ltp/[email protected]/T/#t V3 changes: - Updated copyright header as per LTP format. - v2 link https://lore.kernel.org/ltp/[email protected]/T/#t V2 changes: - Added reset_test_path_no_chown variant to skip chown step. acl_link01 and xattr_test01 tests are updated to use this variant. - Updated acl_user_obj01.c to correct incorrect description - v1 link https://lore.kernel.org/ltp/[email protected]/T/#t V1 changes: - Use ACL_LIBS variable instead of hardcoded -lacl in Makefile - Move ACL header includes inside feature guards in acl_lib.h - Use HAVE_LIBACL guards in .c code - Report TCONF when libacl is not available - rfc link https://lore.kernel.org/ltp/[email protected]/T/#t --- runtest/fs | 3 + testcases/kernel/fs/acl/.gitignore | 1 + testcases/kernel/fs/acl/Makefile | 10 + testcases/kernel/fs/acl/acl_lib.h | 357 +++++++++++++++++++++++ testcases/kernel/fs/acl/acl_user_obj01.c | 116 ++++++++ 5 files changed, 487 insertions(+) create mode 100644 testcases/kernel/fs/acl/.gitignore create mode 100644 testcases/kernel/fs/acl/Makefile create mode 100644 testcases/kernel/fs/acl/acl_lib.h create mode 100644 testcases/kernel/fs/acl/acl_user_obj01.c diff --git a/runtest/fs b/runtest/fs index 1d753e0dd..2a878744b 100644 --- a/runtest/fs +++ b/runtest/fs @@ -87,3 +87,6 @@ binfmt_misc01 binfmt_misc01.sh binfmt_misc02 binfmt_misc02.sh squashfs01 squashfs01 + +# Run the acl tests +acl_user_obj01 acl_user_obj01 diff --git a/testcases/kernel/fs/acl/.gitignore b/testcases/kernel/fs/acl/.gitignore new file mode 100644 index 000000000..d9c46db11 --- /dev/null +++ b/testcases/kernel/fs/acl/.gitignore @@ -0,0 +1 @@ +/acl_user_obj01 diff --git a/testcases/kernel/fs/acl/Makefile b/testcases/kernel/fs/acl/Makefile new file mode 100644 index 000000000..e094549a7 --- /dev/null +++ b/testcases/kernel/fs/acl/Makefile @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 IBM + +top_srcdir ?= ../../../.. + +include $(top_srcdir)/include/mk/testcases.mk + +LDLIBS += $(ACL_LIBS) + +include $(top_srcdir)/include/mk/generic_leaf_target.mk diff --git a/testcases/kernel/fs/acl/acl_lib.h b/testcases/kernel/fs/acl/acl_lib.h new file mode 100644 index 000000000..7d238ce0f --- /dev/null +++ b/testcases/kernel/fs/acl/acl_lib.h @@ -0,0 +1,357 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2026 IBM + * Original shell test by Kai Zhao ([email protected]) + * Converted to C by Sachin Sant <[email protected]> + * + * Common library for ACL and extended attribute tests + */ + +#ifndef ACL_LIB_H +#define ACL_LIB_H + +#include <pwd.h> +#include <grp.h> +#include <errno.h> +#include <fcntl.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/xattr.h> + +#include "config.h" +#include "tst_test.h" +#include "tst_safe_stdio.h" + +#ifdef HAVE_LIBACL +#include <sys/acl.h> +#include <acl/libacl.h> +#endif + +#define MNTPOINT "mntpoint" +#define TESTDIR MNTPOINT "/testdir" +#define TESTFILE TESTDIR "/testfile" +#define TESTSYMLINK TESTDIR "/testsymlink" +#define XATTR_BACKUP_FILE MNTPOINT "/xattr_backup.txt" +#define TEST_USER1 "ltpuser1" +#define TEST_USER2 "ltpuser2" +#define TEST_USER3 "ltpuser3" + +#define USER1_CREATED (1 << 0) +#define USER2_CREATED (1 << 1) +#define USER3_CREATED (1 << 2) + +/* Extended attribute test values */ +#define XATTR_TEST_DIR_NAME "user.test_attr" +#define XATTR_TEST_DIR_VALUE "test_value" +#define XATTR_TEST_DIR_SIZE 10 +#define XATTR_TEST_FILE_NAME "user.file_attr" +#define XATTR_TEST_FILE_VALUE "file_val" +#define XATTR_TEST_FILE_SIZE 8 +#define XATTR_TEST1_NAME "user.test1" +#define XATTR_TEST1_VALUE "value1" +#define XATTR_TEST1_SIZE 6 +#define XATTR_TEST2_NAME "user.test2" +#define XATTR_TEST2_VALUE "value2" +#define XATTR_TEST2_SIZE 6 + +/* Global variables for test users */ +extern uid_t user1_uid, user2_uid, user3_uid; +extern gid_t user1_gid, user2_gid, user3_gid; +extern int users_created; + +/* Helper functions */ +static inline void reset_test_path_no_chown(void) +{ + if (unlink(TESTSYMLINK) == -1 && errno != ENOENT) + tst_res(TWARN | TERRNO, "unlink(%s) failed", TESTSYMLINK); + + if (unlink(TESTFILE) == -1 && errno != ENOENT) + tst_res(TWARN | TERRNO, "unlink(%s) failed", TESTFILE); + + if (rmdir(TESTDIR) == -1 && errno != ENOENT) + tst_res(TWARN | TERRNO, "rmdir(%s) failed", TESTDIR); + + SAFE_MKDIR(TESTDIR, 0755); +} + +static inline void reset_test_path(void) +{ + reset_test_path_no_chown(); + SAFE_CHOWN(TESTDIR, user1_uid, user1_gid); +} + +static inline void cleanup_testfile(void) +{ + if (unlink(TESTFILE) == -1 && errno != ENOENT) + tst_res(TWARN | TERRNO, "unlink(%s) failed", TESTFILE); +} + +#ifdef HAVE_LIBACL +static inline void safe_acl_free(void *ptr) +{ + if (ptr && acl_free(ptr) == -1) + tst_res(TWARN | TERRNO, "acl_free failed"); +} + +static inline void safe_add_perm(acl_permset_t permset, acl_perm_t perm) +{ + if (acl_add_perm(permset, perm) == -1) + tst_brk(TBROK | TERRNO, "acl_add_perm failed"); +} + +static inline void safe_fill_permset(acl_permset_t permset, int perms) +{ + if (acl_clear_perms(permset) == -1) + tst_brk(TBROK | TERRNO, "acl_clear_perms failed"); + + if (perms & ACL_READ) + safe_add_perm(permset, ACL_READ); + + if (perms & ACL_WRITE) + safe_add_perm(permset, ACL_WRITE); + + if (perms & ACL_EXECUTE) + safe_add_perm(permset, ACL_EXECUTE); +} + +/* + * Helper to clear ACL mask permissions. + * Finds the ACL_MASK entry in the given ACL and sets its permissions to 0. + */ +static inline void clear_acl_mask_perms(acl_t acl) +{ + acl_entry_t entry; + acl_permset_t permset; + acl_tag_t tag; + int i; + + for (i = ACL_FIRST_ENTRY; ; i = ACL_NEXT_ENTRY) { + int ret = acl_get_entry(acl, i, &entry); + + if (ret == -1) + tst_brk(TBROK | TERRNO, "acl_get_entry failed"); + + if (ret != 1) + break; + + if (acl_get_tag_type(entry, &tag) == -1) + tst_brk(TBROK | TERRNO, "acl_get_tag_type failed"); + + if (tag != ACL_MASK) + continue; + + if (acl_get_permset(entry, &permset) == -1) + tst_brk(TBROK | TERRNO, "acl_get_permset failed"); + + safe_fill_permset(permset, 0); + + if (acl_set_permset(entry, permset) == -1) + tst_brk(TBROK | TERRNO, "acl_set_permset failed"); + + break; + } +} + +/* + * Helper to add an ACL entry with optional qualifier. + * For ACL_USER and ACL_GROUP tags, qualifier must be provided. + * For other tags (ACL_USER_OBJ, ACL_GROUP_OBJ, ACL_OTHER, ACL_MASK), + * qualifier should be NULL. + */ +static inline void add_acl_entry_with_qualifier(acl_t acl, acl_tag_t tag, + const void *qualifier, + int perms) +{ + acl_entry_t entry; + acl_permset_t permset; + + if (acl_create_entry(&acl, &entry) == -1) + tst_brk(TBROK | TERRNO, "acl_create_entry failed"); + + if (acl_set_tag_type(entry, tag) == -1) + tst_brk(TBROK | TERRNO, "acl_set_tag_type failed"); + + /* Set qualifier for ACL_USER and ACL_GROUP entries */ + if (qualifier && acl_set_qualifier(entry, qualifier) == -1) + tst_brk(TBROK | TERRNO, "acl_set_qualifier failed"); + + if (acl_get_permset(entry, &permset) == -1) + tst_brk(TBROK | TERRNO, "acl_get_permset failed"); + + safe_fill_permset(permset, perms); + + if (acl_set_permset(entry, permset) == -1) + tst_brk(TBROK | TERRNO, "acl_set_permset failed"); +} + +/* Convenience wrapper for entries without qualifiers */ +static inline void add_acl_entry(acl_t acl, acl_tag_t tag, int perms) +{ + add_acl_entry_with_qualifier(acl, tag, NULL, perms); +} + +/* Convenience wrapper for named user entries */ +static inline void add_named_user_acl_entry(acl_t acl, uid_t uid, int perms) +{ + add_acl_entry_with_qualifier(acl, ACL_USER, &uid, perms); +} + +/* Convenience wrapper for named group entries */ +static inline void add_named_group_acl_entry(acl_t acl, gid_t gid, int perms) +{ + add_acl_entry_with_qualifier(acl, ACL_GROUP, &gid, perms); +} + +static inline void add_empty_acl_entry(acl_t acl, acl_tag_t tag) +{ + add_acl_entry(acl, tag, 0); +} + +static inline void set_acl_file(const char *path, acl_type_t type, acl_t acl) +{ + if (acl_valid(acl) == -1) + tst_brk(TBROK | TERRNO, "acl_valid failed"); + + if (acl_set_file(path, type, acl) == -1) { + if (errno == EOPNOTSUPP) + tst_brk(TCONF | TERRNO, "ACL not supported on %s", + path); + tst_brk(TBROK | TERRNO, "acl_set_file(%s) failed", path); + } +} +#endif + +static inline int create_file_as(uid_t uid, gid_t gid, mode_t mode, + int use_umask, mode_t mask) +{ + pid_t pid; + int status; + + pid = SAFE_FORK(); + if (!pid) { + int fd, err; + + if (setgroups(0, NULL) == -1) { + err = errno; + _exit(err); + } + + if (setgid(gid) == -1) { + err = errno; + _exit(err); + } + + if (setuid(uid) == -1) { + err = errno; + _exit(err); + } + + if (use_umask) + umask(mask); + + fd = open(TESTFILE, O_CREAT | O_WRONLY, mode); + if (fd >= 0) { + close(fd); + _exit(0); + } + + err = errno; + _exit(err); + } + + SAFE_WAITPID(pid, &status, 0); + + if (!WIFEXITED(status)) + tst_brk(TBROK, "Child terminated abnormally"); + + return WEXITSTATUS(status); +} + +static inline int try_create_as(uid_t uid, gid_t gid, mode_t mode) +{ + return create_file_as(uid, gid, mode, 0, 0); +} + +static inline int create_with_umask_as(uid_t uid, gid_t gid, mode_t mode, + mode_t mask) +{ + return create_file_as(uid, gid, mode, 1, mask); +} + +static inline void create_user_if_needed(const char *username, int flag) +{ + struct passwd *pw; + + errno = 0; + pw = getpwnam(username); + if (!pw && errno != 0) + tst_brk(TBROK | TERRNO, "getpwnam(%s) failed", username); + + if (!pw) { + if (tst_cmd((const char *[]){"useradd", "-M", "-U", username, + NULL}, NULL, NULL, TST_CMD_PASS_RETVAL) != 0) + tst_brk(TBROK, "Failed to create user %s", username); + users_created |= flag; + } +} + +static inline void create_test_users(void) +{ + create_user_if_needed(TEST_USER1, USER1_CREATED); + create_user_if_needed(TEST_USER2, USER2_CREATED); + create_user_if_needed(TEST_USER3, USER3_CREATED); +} + +static inline void init_test_users(void) +{ + struct passwd *pw; + + create_test_users(); + + pw = SAFE_GETPWNAM(TEST_USER1); + user1_uid = pw->pw_uid; + user1_gid = pw->pw_gid; + + pw = SAFE_GETPWNAM(TEST_USER2); + user2_uid = pw->pw_uid; + user2_gid = pw->pw_gid; + + pw = SAFE_GETPWNAM(TEST_USER3); + user3_uid = pw->pw_uid; + user3_gid = pw->pw_gid; +} + +static inline void cleanup_test_users(void) +{ + if (users_created & USER1_CREATED) + tst_cmd((const char *[]){"userdel", TEST_USER1, NULL}, + NULL, NULL, TST_CMD_PASS_RETVAL); + + if (users_created & USER2_CREATED) + tst_cmd((const char *[]){"userdel", TEST_USER2, NULL}, + NULL, NULL, TST_CMD_PASS_RETVAL); + + if (users_created & USER3_CREATED) + tst_cmd((const char *[]){"userdel", TEST_USER3, NULL}, + NULL, NULL, TST_CMD_PASS_RETVAL); +} + +static inline void cleanup_test_paths(void) +{ + if (unlink(TESTSYMLINK) == -1 && errno != ENOENT) + tst_res(TWARN | TERRNO, "unlink(%s) failed", TESTSYMLINK); + + if (unlink(TESTFILE) == -1 && errno != ENOENT) + tst_res(TWARN | TERRNO, "unlink(%s) failed", TESTFILE); + + if (unlink(XATTR_BACKUP_FILE) == -1 && errno != ENOENT) + tst_res(TWARN | TERRNO, "unlink(%s) failed", + XATTR_BACKUP_FILE); + + if (rmdir(TESTDIR) == -1 && errno != ENOENT) + tst_res(TWARN | TERRNO, "rmdir(%s) failed", TESTDIR); +} + +#endif /* ACL_LIB_H */ diff --git a/testcases/kernel/fs/acl/acl_user_obj01.c b/testcases/kernel/fs/acl/acl_user_obj01.c new file mode 100644 index 000000000..6b97e4ccc --- /dev/null +++ b/testcases/kernel/fs/acl/acl_user_obj01.c @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2026 IBM + * + * Original shell test by Kai Zhao ([email protected]) + * Converted to C by Sachin Sant <[email protected]> + */ + +/*\ + * Test ACL_USER_OBJ permissions. + * + * Verify that owner permissions (ACL_USER_OBJ) correctly control access + * to files and directories. The test validates that: + * - ACL_USER_OBJ permissions are applied directly as the owner bits + * - Setting ACL_USER_OBJ=rwx via acl_set_file() overrides a previous + * chmod restriction + * - Owner permissions work independently of group and other permissions + */ + +#include "acl_lib.h" + +uid_t user1_uid, user2_uid, user3_uid; +gid_t user1_gid, user2_gid, user3_gid; +int users_created = 0; + +#ifdef HAVE_LIBACL + +static void run(void) +{ + acl_t acl; + int err; + + tst_res(TINFO, "Testing ACL_USER_OBJ permissions"); + reset_test_path(); + + /* Test 1: Verify permission bits deny access */ + SAFE_CHMOD(TESTDIR, 0500); + + err = try_create_as(user1_uid, user1_gid, 0644); + if (!err) { + cleanup_testfile(); + tst_res(TFAIL, "Created file without write permission"); + return; + } + + if (err != EACCES) { + errno = err; + tst_res(TFAIL | TERRNO, "Expected EACCES from owner create"); + return; + } + + tst_res(TPASS, "File creation denied by permission bits"); + + /* Test 2: Verify ACL_USER_OBJ grants access */ + acl = acl_init(3); + if (!acl) + tst_brk(TBROK | TERRNO, "acl_init failed"); + + add_acl_entry(acl, ACL_USER_OBJ, + ACL_READ | ACL_WRITE | ACL_EXECUTE); + add_empty_acl_entry(acl, ACL_GROUP_OBJ); + add_empty_acl_entry(acl, ACL_OTHER); + + set_acl_file(TESTDIR, ACL_TYPE_ACCESS, acl); + safe_acl_free(acl); + + err = try_create_as(user1_uid, user1_gid, 0644); + if (err) { + errno = err; + tst_res(TFAIL | TERRNO, + "Failed to create file with ACL_USER_OBJ rwx"); + return; + } + + cleanup_testfile(); + tst_res(TPASS, "ACL_USER_OBJ permissions work correctly"); +} + +static void setup(void) +{ + init_test_users(); + reset_test_path(); +} + +static void cleanup(void) +{ + cleanup_test_paths(); + cleanup_test_users(); +} + +static struct tst_test test = { + .test_all = run, + .setup = setup, + .cleanup = cleanup, + .needs_root = 1, + .mount_device = 1, + .mntpoint = MNTPOINT, + .forks_child = 1, + .filesystems = (struct tst_fs[]) { + {.type = "ext2", .mnt_data = "acl"}, + {.type = "ext3", .mnt_data = "acl"}, + {.type = "ext4", .mnt_data = "acl"}, + {.type = "xfs"}, + {.type = "btrfs"}, + {} + }, + .needs_cmds = (struct tst_cmd[]) { + {.cmd = "useradd"}, + {.cmd = "userdel"}, + {} + } +}; + +#else +TST_TEST_TCONF("libacl or ACL headers are not available"); +#endif -- 2.39.1 -- Mailing list info: https://lists.linux.it/listinfo/ltp