All of lore.kernel.org
 help / color / mirror / Atom feed
From: Cyril Hrubis <chrubis@suse.cz>
To: Sachin Sant <sachinp@linux.ibm.com>
Cc: ltp@lists.linux.it
Subject: Re: [LTP] [PATCH v5 1/8] fs/acl: Add ACL_USER_OBJ permission test
Date: Mon, 8 Jun 2026 13:57:18 +0200	[thread overview]
Message-ID: <aiauHurwBjafsd4R@yuki.lan> (raw)
In-Reply-To: <20260608092200.92827-2-sachinp@linux.ibm.com>

Hi!
> Add acl_user_obj01 test to validate ACL_USER_OBJ permissions:
> - Owner permissions correctly control file/directory access
> - ACL_USER_OBJ=rwx via setxattr() overrides chmod restrictions
> - Owner permissions work independently of group/other permissions
> - Tests use arbitrary UIDs without requiring actual user creation
> 
> The patch also adds acl_lib.h containing shared helpers for ACL
> manipulation via xattr API, including:
> - ACL structure management (acl_init, acl_free, acl_add_entry)
> - ACL serialization/deserialization for kernel xattr format
> - ACL get/set operations using getxattr/setxattr
> - permission testing and file operations
> - Support for both ACCESS and DEFAULT ACL types
> 
> The implementation uses direct xattr API (getxattr/setxattr) to
> test kernel ACL behavior directly. Tests run on ext2/3/4,
> XFS, and Btrfs filesystems with ACL support.
> 
> Suggested-by: Cyril Hrubis <chrubis@suse.cz>
> Signed-off-by: Sachin Sant <sachinp@linux.ibm.com>
> ---
> V5 changes:
> - Switch to kernel only test validation to remove dependency on libacl
>   and useradd/del commands.
> - v4 link https://lore.kernel.org/ltp/20260604065417.25924-1-sachinp@linux.ibm.com/T/#t
> 
> 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/20260603140147.50738-1-sachinp@linux.ibm.com/T/#t
> 
> V3 changes:
> - Updated copyright header as per LTP format.
> - v2 link https://lore.kernel.org/ltp/20260603065744.47106-1-sachinp@linux.ibm.com/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/20260602121958.27494-1-sachinp@linux.ibm.com/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/477836fd-80c8-4168-bfe6-00b374bb2534@linux.ibm.com/T/#t
> 
> ---
>  runtest/fs                               |   3 +
>  testcases/kernel/fs/acl/.gitignore       |   1 +
>  testcases/kernel/fs/acl/Makefile         |   8 +
>  testcases/kernel/fs/acl/acl_lib.h        | 483 +++++++++++++++++++++++
>  testcases/kernel/fs/acl/acl_user_obj01.c | 154 ++++++++
>  5 files changed, 649 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..2d9cba46d
> --- /dev/null
> +++ b/testcases/kernel/fs/acl/Makefile
> @@ -0,0 +1,8 @@
> +# SPDX-License-Identifier: GPL-2.0-or-later
> +# Copyright (c) 2026 IBM
> +
> +top_srcdir		?= ../../../..
> +
> +include $(top_srcdir)/include/mk/testcases.mk
> +
> +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..717c9ff1e
> --- /dev/null
> +++ b/testcases/kernel/fs/acl/acl_lib.h
> @@ -0,0 +1,483 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */
> +/*
> + * Copyright (c) 2026 IBM
> + * Original shell test by Kai Zhao (ltcd3@cn.ibm.com)
> + * Converted to C by Sachin Sant <sachinp@linux.ibm.com>
> + *
> + * Common library for ACL and extended attribute tests using xattr API
> + */
> +
> +#ifndef ACL_LIB_H
> +#define ACL_LIB_H
> +
> +#include <pwd.h>
> +#include <grp.h>
> +#include <errno.h>
> +#include <fcntl.h>
> +#include <string.h>
> +#include <stdint.h>
> +#include <endian.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"
> +
> +#define MNTPOINT	"mntpoint"
> +#define TESTDIR		MNTPOINT "/testdir"
> +#define TESTFILE	TESTDIR "/testfile"
> +#define TESTSYMLINK	TESTDIR "/testsymlink"
> +#define XATTR_BACKUP_FILE MNTPOINT "/xattr_backup.txt"
> +
> +/* 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
> +
> +/*
> + * POSIX ACL xattr format definitions
> + * These match the kernel's internal representation
> + */
> +#define POSIX_ACL_XATTR_VERSION	0x0002
> +
> +/* ACL entry tag types */
> +#define ACL_UNDEFINED_TAG	0x00
> +#define ACL_USER_OBJ		0x01
> +#define ACL_USER		0x02
> +#define ACL_GROUP_OBJ		0x04
> +#define ACL_GROUP		0x08
> +#define ACL_MASK		0x10
> +#define ACL_OTHER		0x20
> +
> +/* ACL permissions */
> +#define ACL_READ		0x04
> +#define ACL_WRITE		0x02
> +#define ACL_EXECUTE		0x01
> +
> +/* ACL xattr names */
> +#define XATTR_NAME_POSIX_ACL_ACCESS	"system.posix_acl_access"
> +#define XATTR_NAME_POSIX_ACL_DEFAULT	"system.posix_acl_default"
> +
> +/* ACL type for set/get operations */
> +#define ACL_TYPE_ACCESS		1
> +#define ACL_TYPE_DEFAULT	2
> +
> +/* Convert host to little-endian */
> +#if __BYTE_ORDER == __LITTLE_ENDIAN
> +#define cpu_to_le16(x) (x)
> +#define cpu_to_le32(x) (x)
> +#define le16_to_cpu(x) (x)
> +#define le32_to_cpu(x) (x)
> +#else
> +#define cpu_to_le16(x) __builtin_bswap16(x)
> +#define cpu_to_le32(x) __builtin_bswap32(x)
> +#define le16_to_cpu(x) __builtin_bswap16(x)
> +#define le32_to_cpu(x) __builtin_bswap32(x)
> +#endif
> +
> +/*
> + * POSIX ACL xattr format as stored in kernel
> + * This is the on-disk/in-xattr representation
> + */
> +struct posix_acl_xattr_header {
> +	uint32_t a_version;
> +};
> +
> +struct posix_acl_xattr_entry {
> +	uint16_t e_tag;
> +	uint16_t e_perm;
> +	uint32_t e_id;
> +};
> +
> +/*
> + * In-memory ACL representation for building ACLs
> + */
> +#define MAX_ACL_ENTRIES 32
> +
> +struct acl_entry {
> +	uint16_t tag;
> +	uint16_t perm;
> +	uint32_t id;
> +};
> +
> +struct acl {
> +	int count;
> +	struct acl_entry entries[MAX_ACL_ENTRIES];
> +};
> +
> +/* 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();
> +}
> +
> +static inline void cleanup_testfile(void)
> +{
> +	if (unlink(TESTFILE) == -1 && errno != ENOENT)
> +		tst_res(TWARN | TERRNO, "unlink(%s) failed", TESTFILE);
> +}
> +
> +/*
> + * Initialize an empty ACL structure
> + */
> +static inline struct acl *acl_init(void)
> +{
> +	struct acl *acl = malloc(sizeof(struct acl));
> +
> +	if (!acl)
> +		return NULL;
> +
> +	acl->count = 0;
> +	return acl;
> +}
> +
> +/*
> + * Free an ACL structure
> + */
> +static inline void acl_free(struct acl *acl)
> +{
> +	free(acl);
> +}
> +
> +/*
> + * Add an ACL entry to the ACL structure
> + */
> +static inline int acl_add_entry(struct acl *acl, uint16_t tag, uint16_t perm,
> +				uint32_t id)
> +{
> +	if (acl->count >= MAX_ACL_ENTRIES) {
> +		errno = ENOMEM;
> +		return -1;
> +	}
> +
> +	acl->entries[acl->count].tag = tag;
> +	acl->entries[acl->count].perm = perm;
> +	acl->entries[acl->count].id = id;
> +	acl->count++;
> +
> +	return 0;
> +}
> +
> +/*
> + * Set ACL on a file using xattr.
> + *
> + * The kernel stores access ACLs only when they differ from the file mode.
> + * If the ACL is equivalent to st_mode, the xattr is removed and future
> + * getxattr() calls return ENODATA. Mirror libacl semantics by treating
> + * ENODATA as a valid minimal ACL derived from st_mode.
> + */
> +static inline int acl_set_file(const char *path, int type, struct acl *acl)
> +{
> +	const char *xattr_name;
> +	size_t size;
> +	char *buf;
> +	struct posix_acl_xattr_header *header;
> +	struct posix_acl_xattr_entry *entries;
> +	int i, ret;
> +
> +	if (type == ACL_TYPE_ACCESS)
> +		xattr_name = XATTR_NAME_POSIX_ACL_ACCESS;
> +	else if (type == ACL_TYPE_DEFAULT)
> +		xattr_name = XATTR_NAME_POSIX_ACL_DEFAULT;
> +	else {
> +		errno = EINVAL;
> +		return -1;
> +	}
> +
> +	size = sizeof(struct posix_acl_xattr_header) +
> +	       acl->count * sizeof(struct posix_acl_xattr_entry);
> +
> +	buf = malloc(size);
> +	if (!buf)
> +		return -1;
> +
> +	header = (struct posix_acl_xattr_header *)buf;
> +	header->a_version = cpu_to_le32(POSIX_ACL_XATTR_VERSION);
> +
> +	entries = (struct posix_acl_xattr_entry *)(buf + sizeof(*header));
> +
> +	for (i = 0; i < acl->count; i++) {
> +		entries[i].e_tag = cpu_to_le16(acl->entries[i].tag);
> +		entries[i].e_perm = cpu_to_le16(acl->entries[i].perm);
> +		entries[i].e_id = cpu_to_le32(acl->entries[i].id);
> +	}
> +
> +	ret = setxattr(path, xattr_name, buf, size, 0);
> +	free(buf);
> +
> +	return ret;
> +}
> +
> +static inline int acl_add_mode_entries(struct acl *acl, mode_t mode)
> +{
> +	if (acl_add_entry(acl, ACL_USER_OBJ, (mode >> 6) & 07, 0) < 0)
> +		return -1;
> +
> +	if (acl_add_entry(acl, ACL_GROUP_OBJ, (mode >> 3) & 07, 0) < 0)
> +		return -1;
> +
> +	if (acl_add_entry(acl, ACL_OTHER, mode & 07, 0) < 0)
> +		return -1;
> +
> +	return 0;
> +}
> +
> +/*
> + * Synthesize an ACL from file mode bits.
> + * Used when no xattr exists for an access ACL.
> + */
> +static inline struct acl *acl_from_mode(const char *path)
> +{
> +	struct acl *acl;
> +	struct stat st;
> +
> +	if (stat(path, &st) < 0)
> +		return NULL;
> +
> +	acl = acl_init();
> +	if (!acl)
> +		return NULL;
> +
> +	if (acl_add_mode_entries(acl, st.st_mode) < 0) {
> +		acl_free(acl);
> +		return NULL;
> +	}
> +
> +	return acl;
> +}
> +
> +/*
> + * Get ACL from a file using xattr.
> + *
> + * Access ACLs equivalent to file mode may not have a backing xattr at all.
> + * In that case synthesize the base ACL from st_mode so callers observe the
> + * same behavior as acl_get_file(3).
> + */
> +static inline struct acl *acl_get_file(const char *path, int type)
> +{
> +	const char *xattr_name;
> +	ssize_t size;
> +	char *buf;
> +	struct posix_acl_xattr_header *header;
> +	struct posix_acl_xattr_entry *entries;
> +	struct acl *acl;
> +	int i, count;
> +
> +	if (type == ACL_TYPE_ACCESS)
> +		xattr_name = XATTR_NAME_POSIX_ACL_ACCESS;
> +	else if (type == ACL_TYPE_DEFAULT)
> +		xattr_name = XATTR_NAME_POSIX_ACL_DEFAULT;
> +	else {
> +		errno = EINVAL;
> +		return NULL;
> +	}

We are using this in two places, maybe it's worth having an
acl_type_to_name() functions and use that instead.

> +	size = getxattr(path, xattr_name, NULL, 0);
> +	if (size < 0) {
> +		if (errno != ENODATA || type != ACL_TYPE_ACCESS)
> +			return NULL;
> +
> +		return acl_from_mode(path);
> +	}
> +
> +	/* Handle race: xattr removed between size check and actual read */
> +	if (size == 0)
> +		return acl_from_mode(path);
> +
> +	buf = malloc(size);
> +	if (!buf)
> +		return NULL;
> +
> +	size = getxattr(path, xattr_name, buf, size);
> +	if (size < 0) {
> +		free(buf);
> +		/* Handle race: xattr removed between size check and read */
> +		if (errno == ENODATA && type == ACL_TYPE_ACCESS)
> +			return acl_from_mode(path);
> +		return NULL;
> +	}
> +
> +	header = (struct posix_acl_xattr_header *)buf;
> +	if (le32_to_cpu(header->a_version) != POSIX_ACL_XATTR_VERSION) {
> +		free(buf);
> +		errno = EINVAL;
> +		return NULL;
> +	}
> +
> +	count = (size - sizeof(*header)) / sizeof(struct posix_acl_xattr_entry);
> +	entries = (struct posix_acl_xattr_entry *)(buf + sizeof(*header));

Maybe we can define a structure with both header and entries in order to
avoid some of the casts here. Maybe this would work:

struct acl_xattr {
	struct posix_acl_xattr_header header;
	struct posix_acl_xattr_entry entries[];
};

And then we could do:

	struct acl_xattr *ax = (void*)buf;

	if (le32_to_cpu(ax->header.a_version) == ACL_TYPE_ACCESS) {
		...
	}

	...

	for (i = 0; i < conut; i++) {
		uint16_t tag = le16_to_cpu(ax->entries[i].e_tag);
		...

	}


> +	acl = acl_init();
> +	if (!acl) {
> +		free(buf);
> +		return NULL;
> +	}
> +
> +	for (i = 0; i < count; i++) {
> +		uint16_t tag = le16_to_cpu(entries[i].e_tag);
> +		uint16_t perm = le16_to_cpu(entries[i].e_perm);
> +		uint32_t id = le32_to_cpu(entries[i].e_id);
> +
> +		if (acl_add_entry(acl, tag, perm, id) < 0) {
> +			acl_free(acl);
> +			free(buf);
> +			return NULL;
> +		}
> +	}
> +
> +	free(buf);
> +	return acl;
> +}
> +
> +/*
> + * Check if an ACL entry has a specific permission
> + */
> +static inline int acl_entry_has_perm(struct acl_entry *entry, uint16_t perm)
> +{
> +	return (entry->perm & perm) == perm;
> +}
> +
> +/*
> + * Check if an ACL entry has all rwx permissions
> + */
> +static inline int acl_entry_has_rwx(struct acl_entry *entry)
> +{
> +	return acl_entry_has_perm(entry, ACL_READ) &&
> +	       acl_entry_has_perm(entry, ACL_WRITE) &&
> +	       acl_entry_has_perm(entry, ACL_EXECUTE);

Can't we pass all the bits to a single call of the function like
acl_entry_has_perm(entry, ACL_READ | ACL_WRITE | ACL_EXECUTE) ?

> +}
> +
> +/*
> + * Find an ACL entry by tag type
> + */
> +static inline struct acl_entry *acl_find_entry(struct acl *acl, uint16_t tag,
> +					       uint32_t id)
> +{
> +	int i;
> +
> +	for (i = 0; i < acl->count; i++) {
> +		if (acl->entries[i].tag == tag) {
> +			if (tag == ACL_USER || tag == ACL_GROUP) {
> +				if (acl->entries[i].id == id)
> +					return &acl->entries[i];
> +			} else {
> +				return &acl->entries[i];
> +			}
> +		}
> +	}
> +
> +	return NULL;
> +}
> +
> +/*
> + * Update ACL mask permissions
> + */
> +static inline int acl_set_mask_perms(struct acl *acl, uint16_t perm)
> +{
> +	struct acl_entry *mask_entry = acl_find_entry(acl, ACL_MASK, 0);
> +
> +	if (!mask_entry) {
> +		errno = ENOENT;
> +		return -1;
> +	}
> +
> +	mask_entry->perm = perm;
> +	return 0;
> +}
> +
> +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);
> +		}

These should be SAFE_MACROS().

> +		if (use_umask)
> +			umask(mask);
> +
> +		fd = open(TESTFILE, O_CREAT | O_WRONLY, mode);
> +		if (fd >= 0) {
> +			close(fd);
> +			_exit(0);
> +		}

Generally in LTP we want to check the test conditions right after they
happen. So this function should get expected errno as last parameter and
we should check for the result right here with something as:

	if (errno)
		ST_EXP_FAIL()
	else
		TST_EXP_FD()

With that we do need to propagate the return value manually, which is
prone to errors, and do not need to process the child exit value
manually, we can just let the library collect the children with
tst_reap_children().

> +		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 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 (rmdir(TESTDIR) == -1 && errno != ENOENT)
> +		tst_res(TWARN | TERRNO, "rmdir(%s) failed", TESTDIR);

Maybe we need SAFE_TRY_UNLINK() and SAFE_TRY_RMDIR() that wouldn't TBROK
on ENOENT and then we could use it here.

> +}
> +
> +#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..01abfbf4e
> --- /dev/null
> +++ b/testcases/kernel/fs/acl/acl_user_obj01.c
> @@ -0,0 +1,154 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Copyright (c) 2026 IBM
> + *
> + * Original shell test by Kai Zhao (ltcd3@cn.ibm.com)
> + * Converted to C by Sachin Sant <sachinp@linux.ibm.com>
> + */
> +
> +/*\
> + * Test ACL_USER_OBJ permissions using direct xattr manipulation.
> + *
> + * 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 setxattr() overrides a previous
> + *   chmod restriction
> + * - Owner permissions work independently of group and other permissions
> + *
> + * This test uses arbitrary UIDs without creating actual users, testing
> + * only the kernel ACL implementation.
> + */
> +
> +#include "acl_lib.h"
> +
> +#define TEST_UID 1000
> +#define TEST_GID 1000
> +
> +/*
> + * Test permission bits deny access.
> + * Owner should be denied write access when mode is 0500.
> + */
> +static void test_deny_by_mode(void)
> +{
> +	int err;
> +
> +	tst_res(TINFO, "Testing permission bits deny access");
> +	reset_test_path();
> +
> +	SAFE_CHOWN(TESTDIR, TEST_UID, TEST_GID);
> +	SAFE_CHMOD(TESTDIR, 0500);
> +
> +	err = try_create_as(TEST_UID, TEST_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 ACL_USER_OBJ grants access.
> + * Setting ACL_USER_OBJ=rwx should override previous chmod restriction.
> + */
> +static void test_grant_by_acl(void)
> +{
> +	struct acl *acl = NULL;
> +	int err;
> +
> +	tst_res(TINFO, "Testing ACL_USER_OBJ grants access");
> +	reset_test_path();
> +
> +	SAFE_CHOWN(TESTDIR, TEST_UID, TEST_GID);
> +	SAFE_CHMOD(TESTDIR, 0500);
> +
> +	acl = acl_init();
> +	if (!acl)
> +		tst_brk(TBROK | TERRNO, "acl_init failed");
> +
> +	if (acl_add_entry(acl, ACL_USER_OBJ,
> +			  ACL_READ | ACL_WRITE | ACL_EXECUTE, 0) < 0)
> +		goto cleanup_acl;
> +
> +	if (acl_add_entry(acl, ACL_GROUP_OBJ, 0, 0) < 0)
> +		goto cleanup_acl;
> +
> +	if (acl_add_entry(acl, ACL_OTHER, 0, 0) < 0)
> +		goto cleanup_acl;

Maybe we can just call SAFE_MALLOC() and tst_brk() internally in these
functions so that we don't have to complicate the code here. We do call
tst_brk() in cleanup_acl: anyways.

> +	if (acl_set_file(TESTDIR, ACL_TYPE_ACCESS, acl) < 0) {
> +		if (errno == EOPNOTSUPP) {
> +			acl_free(acl);
> +			tst_brk(TCONF | TERRNO, "ACL not supported");
> +		}
> +		goto cleanup_acl;
> +	}
> +
> +	acl_free(acl);
> +	acl = NULL;
> +
> +	err = try_create_as(TEST_UID, TEST_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");
> +	return;
> +
> +cleanup_acl:
> +	acl_free(acl);
> +	tst_brk(TBROK | TERRNO, "ACL setup failed");
> +}
> +
> +static void run(unsigned int n)
> +{
> +	switch (n) {
> +	case 0:
> +		test_deny_by_mode();
> +		break;
> +	case 1:
> +		test_grant_by_acl();
> +		break;
> +	}
> +}
> +
> +static void setup(void)
> +{
> +	reset_test_path();
> +}
> +
> +static void cleanup(void)
> +{
> +	cleanup_test_paths();
> +}
> +
> +static struct tst_test test = {
> +	.test = run,
> +	.tcnt = 2,
> +	.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"},
> +		{}
> +	}
> +};
> -- 
> 2.39.1
> 

-- 
Cyril Hrubis
chrubis@suse.cz

-- 
Mailing list info: https://lists.linux.it/listinfo/ltp

  parent reply	other threads:[~2026-06-08 11:57 UTC|newest]

Thread overview: 13+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-06-08  9:21 [LTP] [PATCH v5 0/8] Convert shell-based ACL test (tacl_xattr.sh) to C Sachin Sant
2026-06-08  9:21 ` [LTP] [PATCH v5 1/8] fs/acl: Add ACL_USER_OBJ permission test Sachin Sant
2026-06-08 11:01   ` [LTP] " linuxtestproject.agent
2026-06-08 11:57   ` Cyril Hrubis [this message]
2026-06-08 13:40     ` [LTP] [PATCH v5 1/8] " Sachin Sant
2026-06-08 14:25       ` Cyril Hrubis
2026-06-08  9:21 ` [LTP] [PATCH v3 2/8] fs/acl: Add ACL mask interaction tests Sachin Sant
2026-06-08  9:21 ` [LTP] [PATCH v3 3/8] fs/acl: Add ACL_OTHER permissions test Sachin Sant
2026-06-08  9:21 ` [LTP] [PATCH v4 4/8] fs/acl: Add default ACL inheritance test Sachin Sant
2026-06-08  9:21 ` [LTP] [PATCH v3 5/8] fs/acl: Add chmod/chown ACL interaction tests Sachin Sant
2026-06-08  9:21 ` [LTP] [PATCH v5 6/8] fs/acl: Add symlink ACL operations test Sachin Sant
2026-06-08  9:21 ` [LTP] [PATCH v4 7/8] fs/acl: Add extended attributes test Sachin Sant
2026-06-08  9:22 ` [LTP] [PATCH v1 8/8] fs/acl: Remove old shell-based ACL test Sachin Sant

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=aiauHurwBjafsd4R@yuki.lan \
    --to=chrubis@suse.cz \
    --cc=ltp@lists.linux.it \
    --cc=sachinp@linux.ibm.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.