256 lines
5.7 KiB
C
256 lines
5.7 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Copyright (c) 2018 Matthew Bobrowski. All Rights Reserved.
|
|
*
|
|
* Started by Matthew Bobrowski <mbobrowski@mbobrowski.org>
|
|
*/
|
|
|
|
/*\
|
|
* [Description]
|
|
* This set of tests is to ensure that the unprivileged listener feature of
|
|
* fanotify is functioning as expected. The objective of this test file is
|
|
* to generate a sequence of events and ensure that the returned events
|
|
* contain the limited values that an unprivileged listener is expected
|
|
* to receive.
|
|
*/
|
|
|
|
#define _GNU_SOURCE
|
|
#include "config.h"
|
|
|
|
#include <pwd.h>
|
|
#include <stdio.h>
|
|
#include <errno.h>
|
|
#include <stdlib.h>
|
|
#include <sys/wait.h>
|
|
|
|
#include "tst_test.h"
|
|
|
|
#ifdef HAVE_SYS_FANOTIFY_H
|
|
#include "fanotify.h"
|
|
|
|
#define EVENT_MAX 1024
|
|
#define EVENT_SIZE (sizeof (struct fanotify_event_metadata))
|
|
#define EVENT_BUF_LEN (EVENT_MAX * EVENT_SIZE)
|
|
#define EVENT_SET_MAX 48
|
|
|
|
#define BUF_SIZE 256
|
|
|
|
#define MOUNT_PATH "fs_mnt"
|
|
#define TEST_FILE MOUNT_PATH "/testfile"
|
|
|
|
static int fd_notify;
|
|
static char buf[BUF_SIZE];
|
|
static struct fanotify_event_metadata event_buf[EVENT_BUF_LEN];
|
|
|
|
static struct test_case_t {
|
|
const char *name;
|
|
unsigned int fork;
|
|
unsigned int event_count;
|
|
unsigned long long event_set[EVENT_SET_MAX];
|
|
} test_cases[] = {
|
|
{
|
|
"unprivileged listener - events by self",
|
|
0,
|
|
4,
|
|
{
|
|
FAN_OPEN,
|
|
FAN_ACCESS,
|
|
FAN_MODIFY,
|
|
FAN_CLOSE,
|
|
}
|
|
},
|
|
{
|
|
"unprivileged lisneter - events by child",
|
|
1,
|
|
4,
|
|
{
|
|
FAN_OPEN,
|
|
FAN_ACCESS,
|
|
FAN_MODIFY,
|
|
FAN_CLOSE,
|
|
}
|
|
}
|
|
};
|
|
|
|
static void generate_events(void)
|
|
{
|
|
int fd;
|
|
|
|
/* FAN_OPEN */
|
|
fd = SAFE_OPEN(TEST_FILE, O_RDWR);
|
|
|
|
/* FAN_ACCESS */
|
|
SAFE_READ(0, fd, buf, BUF_SIZE);
|
|
|
|
/* FAN_MODIFY */
|
|
SAFE_WRITE(1, fd, TEST_FILE, 1);
|
|
|
|
/* FAN_CLOSE */
|
|
SAFE_CLOSE(fd);
|
|
}
|
|
|
|
static void do_fork(void)
|
|
{
|
|
int status;
|
|
pid_t child;
|
|
|
|
child = SAFE_FORK();
|
|
|
|
if (child == 0) {
|
|
SAFE_CLOSE(fd_notify);
|
|
generate_events();
|
|
exit(0);
|
|
}
|
|
|
|
SAFE_WAITPID(child, &status, 0);
|
|
|
|
if (WIFEXITED(child) && WEXITSTATUS(child) != 0)
|
|
tst_brk(TBROK, "Child process terminated incorrectly. Aborting");
|
|
}
|
|
|
|
static void test_fanotify(unsigned int n)
|
|
{
|
|
int len = 0;
|
|
pid_t pid = getpid();
|
|
unsigned int test_number = 0;
|
|
struct fanotify_event_metadata *event;
|
|
struct test_case_t *tc = &test_cases[n];
|
|
|
|
tst_res(TINFO, "Test #%d %s", n, tc->name);
|
|
|
|
/* Initialize fanotify */
|
|
fd_notify = fanotify_init(FANOTIFY_REQUIRED_USER_INIT_FLAGS, O_RDONLY);
|
|
|
|
if (fd_notify < 0) {
|
|
if (errno == EPERM || errno == EINVAL) {
|
|
tst_res(TCONF,
|
|
"unprivileged fanotify not supported by kernel?");
|
|
return;
|
|
} else {
|
|
tst_brk(TBROK | TERRNO,
|
|
"fanotify_init(FAN_CLASS_NOTIF, O_RDONLY) failed");
|
|
}
|
|
}
|
|
|
|
/* Place mark on object */
|
|
if (fanotify_mark(fd_notify, FAN_MARK_ADD, FAN_ALL_EVENTS,
|
|
AT_FDCWD, TEST_FILE) < 0) {
|
|
tst_brk(TBROK | TERRNO,
|
|
"fanotify_mark(%d, FAN_MARK_ADD, %d, "
|
|
"AT_FDCWD, %s) failed",
|
|
fd_notify,
|
|
FAN_ALL_EVENTS,
|
|
TEST_FILE);
|
|
}
|
|
|
|
/* Generate events in either child or listening process */
|
|
if (tc->fork)
|
|
do_fork();
|
|
else
|
|
generate_events();
|
|
|
|
/* Read events from queue */
|
|
len = SAFE_READ(0, fd_notify, event_buf + len, EVENT_BUF_LEN - len);
|
|
|
|
event = event_buf;
|
|
|
|
/* Iterate over and validate events against expected result set */
|
|
while (FAN_EVENT_OK(event, len) && test_number < tc->event_count) {
|
|
if (!(event->mask & tc->event_set[test_number])) {
|
|
tst_res(TFAIL,
|
|
"Received unexpected event mask: mask=%llx "
|
|
"pid=%u fd=%d",
|
|
(unsigned long long) event->mask,
|
|
(unsigned) event->pid,
|
|
event->fd);
|
|
} else if ((!tc->fork && event->pid != pid) ||
|
|
(tc->fork && event->pid != 0)) {
|
|
tst_res(TFAIL,
|
|
"Received unexpected pid in event: "
|
|
"mask=%llx pid=%u (expected %u) fd=%d",
|
|
(unsigned long long) event->mask,
|
|
(unsigned) event->pid,
|
|
(tc->fork ? 0 : pid),
|
|
event->fd);
|
|
} else if (event->fd != FAN_NOFD) {
|
|
tst_res(TFAIL,
|
|
"Received unexpected file descriptor: "
|
|
"mask=%llx pid=%u fd=%d (expected %d)",
|
|
(unsigned long long) event->pid,
|
|
(unsigned) event->pid,
|
|
event->fd,
|
|
FAN_NOFD);
|
|
SAFE_CLOSE(event->fd);
|
|
} else {
|
|
tst_res(TPASS,
|
|
"Received event: mask=%llx, pid=%u fd=%d",
|
|
(unsigned long long) event->mask,
|
|
(unsigned) event->pid,
|
|
event->fd);
|
|
}
|
|
|
|
/* Non-permission events can be merged into a single event. */
|
|
event->mask &= ~tc->event_set[test_number];
|
|
|
|
if (event->mask == 0)
|
|
event = FAN_EVENT_NEXT(event, len);
|
|
test_number++;
|
|
}
|
|
|
|
/*
|
|
* Determine whether there is still unprocessed events remaining in the
|
|
* buffer. This is to cover the basis whereby the event processing loop
|
|
* terminates prematurely. In that case, we need to ensure that any
|
|
* event file descriptor that is open is closed so that the temporary
|
|
* filesystem can be unmounted.
|
|
*/
|
|
if (FAN_EVENT_OK(event, len)) {
|
|
tst_res(TFAIL,
|
|
"Event processing loop exited prematurely. Did NOT "
|
|
"finish processing events in buffer. Cleaning up.");
|
|
while (FAN_EVENT_OK(event, len)) {
|
|
if (event->fd != FAN_NOFD)
|
|
SAFE_CLOSE(event->fd);
|
|
event = FAN_EVENT_NEXT(event, len);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void setup(void)
|
|
{
|
|
SAFE_FILE_PRINTF(TEST_FILE, "1");
|
|
SAFE_CHMOD(TEST_FILE, 0666);
|
|
|
|
/* Check for kernel fanotify support */
|
|
REQUIRE_FANOTIFY_INIT_FLAGS_SUPPORTED_ON_FS(FAN_REPORT_FID, TEST_FILE);
|
|
|
|
/* Relinquish privileged user */
|
|
if (geteuid() == 0) {
|
|
tst_res(TINFO,
|
|
"Running as privileged user, revoking.");
|
|
struct passwd *nobody = SAFE_GETPWNAM("nobody");
|
|
SAFE_SETUID(nobody->pw_uid);
|
|
}
|
|
}
|
|
|
|
static void cleanup(void)
|
|
{
|
|
if (fd_notify > 0)
|
|
SAFE_CLOSE(fd_notify);
|
|
}
|
|
|
|
static struct tst_test test = {
|
|
.test = test_fanotify,
|
|
.tcnt = ARRAY_SIZE(test_cases),
|
|
.setup = setup,
|
|
.cleanup = cleanup,
|
|
.forks_child = 1,
|
|
.needs_root = 1,
|
|
.mount_device = 1,
|
|
.mntpoint = MOUNT_PATH,
|
|
};
|
|
|
|
#else
|
|
TST_TEST_TCONF("system doesn't have required fanotify support");
|
|
#endif
|