* Copyright (c) 2018 Matthew Bobrowski. All Rights Reserved.
* Copyright (c) Linux Test Project, 2020-2022
*
* Started by Matthew Bobrowski <mbobrowski@mbobrowski.org>
*/
* This test file has been designed to ensure that the fanotify
* system calls fanotify_init(2) and fanotify_mark(2) return the
* correct error code to the calling process when an invalid flag or
* mask value has been specified in conjunction with FAN_REPORT_FID.
*/
* The ENOTDIR test cases are regression tests for commits:
*
* ceaf69f8eadc fanotify: do not allow setting dirent events in mask of non-dir
* 8698e3bab4dd fanotify: refine the validation checks on non-dir inode mask
*
* The pipes test cases are regression tests for commit:
* 69562eb0bd3e fanotify: disallow mount/sb marks on kernel internal pseudo fs
*/
#define _GNU_SOURCE
#include "tst_test.h"
#include <errno.h>
#ifdef HAVE_SYS_FANOTIFY_H
#include "fanotify.h"
#define MNTPOINT "mntpoint"
#define FILE1 MNTPOINT"/file1"
* List of inode events that are only available when notification group is
* set to report fid.
*/
#define INODE_EVENTS (FAN_ATTRIB | FAN_CREATE | FAN_DELETE | FAN_MOVE | \
FAN_DELETE_SELF | FAN_MOVE_SELF)
#define FLAGS_DESC(flags) {(flags), (#flags)}
static int pipes[2] = {-1, -1};
static int fanotify_fd;
static int ignore_mark_unsupported;
static int filesystem_mark_unsupported;
static int se_enforcing;
static unsigned int supported_init_flags;
struct test_case_flags_t {
unsigned long long flags;
const char *desc;
};
* Each test case has been designed in a manner whereby the values defined
* within should result in the interface to return an error to the calling
* process.
*/
static struct test_case_t {
struct test_case_flags_t init;
struct test_case_flags_t mark;
struct test_case_flags_t mask;
int expected_errno;
int *pfd;
} test_cases[] = {
{
.init = FLAGS_DESC(FAN_CLASS_CONTENT | FAN_REPORT_FID),
.expected_errno = EINVAL,
},
{
.init = FLAGS_DESC(FAN_CLASS_PRE_CONTENT | FAN_REPORT_FID),
.expected_errno = EINVAL,
},
{
.init = FLAGS_DESC(FAN_CLASS_NOTIF),
.mark = FLAGS_DESC(FAN_MARK_INODE),
.mask = FLAGS_DESC(INODE_EVENTS),
.expected_errno = EINVAL,
},
{
.init = FLAGS_DESC(FAN_CLASS_NOTIF | FAN_REPORT_FID),
.mark = FLAGS_DESC(FAN_MARK_MOUNT),
.mask = FLAGS_DESC(INODE_EVENTS),
.expected_errno = EINVAL,
},
{
.init = FLAGS_DESC(FAN_CLASS_NOTIF | FAN_REPORT_NAME),
.expected_errno = EINVAL,
},
{
.init = FLAGS_DESC(FAN_CLASS_NOTIF | FAN_REPORT_FID | FAN_REPORT_NAME),
.expected_errno = EINVAL,
},
{
.init = FLAGS_DESC(FAN_CLASS_NOTIF | FAN_REPORT_TARGET_FID | FAN_REPORT_DFID_NAME),
.expected_errno = EINVAL,
},
{
.init = FLAGS_DESC(FAN_CLASS_NOTIF | FAN_REPORT_TARGET_FID | FAN_REPORT_DFID_FID),
.expected_errno = EINVAL,
},
{
.init = FLAGS_DESC(FAN_CLASS_NOTIF | FAN_REPORT_DFID_FID),
.mark = FLAGS_DESC(FAN_MARK_INODE),
.mask = FLAGS_DESC(FAN_RENAME),
.expected_errno = EINVAL,
},
{
.init = FLAGS_DESC(FAN_CLASS_NOTIF),
.mark = FLAGS_DESC(FAN_MARK_ONLYDIR),
.mask = FLAGS_DESC(FAN_OPEN),
.expected_errno = ENOTDIR,
},
{
.init = FLAGS_DESC(FAN_CLASS_NOTIF | FAN_REPORT_DFID_NAME_TARGET),
.mark = FLAGS_DESC(FAN_MARK_INODE),
.mask = FLAGS_DESC(FAN_DELETE),
.expected_errno = ENOTDIR,
},
{
.init = FLAGS_DESC(FAN_CLASS_NOTIF | FAN_REPORT_DFID_NAME_TARGET),
.mark = FLAGS_DESC(FAN_MARK_INODE),
.mask = FLAGS_DESC(FAN_RENAME),
.expected_errno = ENOTDIR,
},
{
.init = FLAGS_DESC(FAN_CLASS_NOTIF | FAN_REPORT_DFID_NAME_TARGET),
.mark = FLAGS_DESC(FAN_MARK_INODE),
.mask = FLAGS_DESC(FAN_OPEN | FAN_ONDIR),
.expected_errno = ENOTDIR,
},
{
.init = FLAGS_DESC(FAN_CLASS_NOTIF | FAN_REPORT_DFID_NAME_TARGET),
.mark = FLAGS_DESC(FAN_MARK_INODE),
.mask = FLAGS_DESC(FAN_OPEN | FAN_EVENT_ON_CHILD),
.expected_errno = ENOTDIR,
},
{
.init = FLAGS_DESC(FAN_CLASS_NOTIF | FAN_REPORT_DFID_NAME),
.mark = FLAGS_DESC(FAN_MARK_IGNORE_SURV),
.mask = FLAGS_DESC(FAN_DELETE),
.expected_errno = ENOTDIR,
},
{
.init = FLAGS_DESC(FAN_CLASS_NOTIF | FAN_REPORT_DFID_NAME),
.mark = FLAGS_DESC(FAN_MARK_IGNORE_SURV),
.mask = FLAGS_DESC(FAN_RENAME),
.expected_errno = ENOTDIR,
},
{
.init = FLAGS_DESC(FAN_CLASS_NOTIF | FAN_REPORT_DFID_NAME),
.mark = FLAGS_DESC(FAN_MARK_IGNORE_SURV),
.mask = FLAGS_DESC(FAN_OPEN | FAN_ONDIR),
.expected_errno = ENOTDIR,
},
{
.init = FLAGS_DESC(FAN_CLASS_NOTIF | FAN_REPORT_DFID_NAME),
.mark = FLAGS_DESC(FAN_MARK_IGNORE_SURV),
.mask = FLAGS_DESC(FAN_OPEN | FAN_EVENT_ON_CHILD),
.expected_errno = ENOTDIR,
},
{
.init = FLAGS_DESC(FAN_CLASS_NOTIF),
.mark = FLAGS_DESC(FAN_MARK_IGNORE),
.mask = FLAGS_DESC(FAN_OPEN),
.expected_errno = EISDIR,
},
{
.init = FLAGS_DESC(FAN_CLASS_NOTIF),
.mark = FLAGS_DESC(FAN_MARK_MOUNT | FAN_MARK_IGNORE),
.mask = FLAGS_DESC(FAN_OPEN),
.expected_errno = EINVAL,
},
{
.init = FLAGS_DESC(FAN_CLASS_NOTIF),
.mark = FLAGS_DESC(FAN_MARK_FILESYSTEM | FAN_MARK_IGNORE),
.mask = FLAGS_DESC(FAN_OPEN),
.expected_errno = EINVAL,
},
{
.init = FLAGS_DESC(FAN_CLASS_NOTIF),
.mark = FLAGS_DESC(FAN_MARK_MOUNT),
.mask = { FAN_ACCESS, "anonymous pipe"},
.pfd = pipes,
.expected_errno = EINVAL,
},
{
.init = FLAGS_DESC(FAN_CLASS_NOTIF),
.mark = FLAGS_DESC(FAN_MARK_FILESYSTEM),
.mask = { FAN_ACCESS, "anonymous pipe"},
.pfd = pipes,
.expected_errno = EINVAL,
},
{
.init = FLAGS_DESC(FAN_CLASS_NOTIF),
.mark = FLAGS_DESC(FAN_MARK_INODE),
.mask = FLAGS_DESC(LTP_ALL_PERM_EVENTS),
.expected_errno = EINVAL,
},
{
.init = FLAGS_DESC(FAN_CLASS_NOTIF),
.mark = FLAGS_DESC(FAN_MARK_INODE),
.mask = FLAGS_DESC(LTP_PRE_CONTENT_EVENTS),
.expected_errno = EINVAL,
},
{
.init = FLAGS_DESC(FAN_CLASS_CONTENT),
.mark = FLAGS_DESC(FAN_MARK_INODE),
.mask = FLAGS_DESC(LTP_PRE_CONTENT_EVENTS),
.expected_errno = EINVAL,
},
};
static void do_test(unsigned int number)
{
struct test_case_t *tc = &test_cases[number];
tst_res(TINFO, "Test case %d: fanotify_init(%s, O_RDONLY)", number,
tc->init.desc);
if (tc->init.flags & ~supported_init_flags) {
tst_res(TCONF, "Unsupported init flags");
return;
}
if (ignore_mark_unsupported && tc->mark.flags & FAN_MARK_IGNORE) {
tst_res(TCONF, "FAN_MARK_IGNORE not supported in kernel?");
return;
}
if (!tc->mask.flags && tc->expected_errno) {
TST_EXP_FAIL(fanotify_init(tc->init.flags, O_RDONLY),
tc->expected_errno);
} else {
TST_EXP_FD(fanotify_init(tc->init.flags, O_RDONLY));
}
fanotify_fd = TST_RET;
if (fanotify_fd < 0)
return;
if (!tc->mask.flags)
goto out;
const char *path = tc->expected_errno == ENOTDIR ? FILE1 : MNTPOINT;
const int exp_errs[] = {tc->expected_errno, EACCES};
int dirfd = AT_FDCWD;
if (tc->pfd) {
dirfd = tc->pfd[0];
path = NULL;
}
tst_res(TINFO, "Testing %s with %s",
tc->mark.desc, tc->mask.desc);
TST_EXP_FAIL_ARR(fanotify_mark(fanotify_fd, FAN_MARK_ADD | tc->mark.flags,
tc->mask.flags, dirfd, path), exp_errs, se_enforcing ? 2 : 1);
* ENOTDIR are errors for events/flags not allowed on a non-dir inode.
* Try to set an inode mark on a directory and it should succeed.
* Try to set directory events in filesystem mark mask on non-dir
* and it should succeed.
*/
if (TST_PASS && tc->expected_errno == ENOTDIR) {
SAFE_FANOTIFY_MARK(fanotify_fd, FAN_MARK_ADD | tc->mark.flags,
tc->mask.flags, AT_FDCWD, MNTPOINT);
tst_res(TPASS,
"Adding an inode mark on directory did not fail with "
"ENOTDIR error as on non-dir inode");
if (!(tc->mark.flags & FAN_MARK_ONLYDIR) && !filesystem_mark_unsupported) {
SAFE_FANOTIFY_MARK(fanotify_fd, FAN_MARK_ADD | tc->mark.flags |
FAN_MARK_FILESYSTEM, tc->mask.flags,
AT_FDCWD, FILE1);
tst_res(TPASS,
"Adding a filesystem mark on non-dir did not fail with "
"ENOTDIR error as with an inode mark");
}
}
out:
if (fanotify_fd > 0)
SAFE_CLOSE(fanotify_fd);
}
static void do_setup(void)
{
unsigned int all_init_flags = FAN_REPORT_DFID_NAME_TARGET |
FAN_CLASS_NOTIF | FAN_CLASS_CONTENT | FAN_CLASS_PRE_CONTENT;
REQUIRE_FANOTIFY_INIT_FLAGS_SUPPORTED_ON_FS(FAN_REPORT_FID, MNTPOINT);
supported_init_flags = fanotify_get_supported_init_flags(all_init_flags, MNTPOINT);
ignore_mark_unsupported = fanotify_mark_supported_on_fs(FAN_MARK_IGNORE_SURV,
MNTPOINT);
filesystem_mark_unsupported =
fanotify_flags_supported_on_fs(FAN_REPORT_FID, FAN_MARK_FILESYSTEM, FAN_OPEN,
MNTPOINT);
SAFE_FILE_PRINTF(FILE1, "0");
SAFE_PIPE2(pipes, O_CLOEXEC);
se_enforcing = tst_selinux_enforcing();
}
static void do_cleanup(void)
{
if (fanotify_fd > 0)
SAFE_CLOSE(fanotify_fd);
if (pipes[0] != -1)
SAFE_CLOSE(pipes[0]);
if (pipes[1] != -1)
SAFE_CLOSE(pipes[1]);
}
static struct tst_test test = {
.needs_root = 1,
.test = do_test,
.tcnt = ARRAY_SIZE(test_cases),
.setup = do_setup,
.cleanup = do_cleanup,
.mount_device = 1,
.mntpoint = MNTPOINT,
.all_filesystems = 1,
.tags = (const struct tst_tag[]) {
{"linux-git", "ceaf69f8eadc"},
{"linux-git", "8698e3bab4dd"},
{"linux-git", "69562eb0bd3e"},
{}
}
};
#else
TST_TEST_TCONF("System does not have required fanotify support");
#endif