+#include <linux/fanotify.h>
#include <linux/fcntl.h>
#include <linux/file.h>
#include <linux/fs.h>
#include <linux/poll.h>
#include <linux/security.h>
#include <linux/syscalls.h>
+#include <linux/slab.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include <asm/ioctls.h>
-#include "fanotify.h"
+extern const struct fsnotify_ops fanotify_fsnotify_ops;
static struct kmem_cache *fanotify_mark_cache __read_mostly;
+static struct kmem_cache *fanotify_response_event_cache __read_mostly;
+
+struct fanotify_response_event {
+ struct list_head list;
+ __s32 fd;
+ struct fsnotify_event *event;
+};
/*
* Get an fsnotify notification event if one exists and is small
if (client_fd < 0)
return client_fd;
- if (event->data_type != FSNOTIFY_EVENT_PATH) {
+ if (event->data_type != FSNOTIFY_EVENT_FILE) {
WARN_ON(1);
put_unused_fd(client_fd);
return -EINVAL;
* we need a new file handle for the userspace program so it can read even if it was
* originally opened O_WRONLY.
*/
- dentry = dget(event->path.dentry);
- mnt = mntget(event->path.mnt);
+ dentry = dget(event->file->f_path.dentry);
+ mnt = mntget(event->file->f_path.mnt);
/* it's possible this event was an overflow event. in that case dentry and mnt
* are NULL; That's fine, just don't call dentry open */
if (dentry && mnt)
new_file = dentry_open(dentry, mnt,
- O_RDONLY | O_LARGEFILE | FMODE_NONOTIFY,
+ group->fanotify_data.f_flags | FMODE_NONOTIFY,
current_cred());
else
new_file = ERR_PTR(-EOVERFLOW);
metadata->event_len = FAN_EVENT_METADATA_LEN;
metadata->vers = FANOTIFY_METADATA_VERSION;
- metadata->mask = fanotify_outgoing_mask(event->mask);
+ metadata->mask = event->mask & FAN_ALL_OUTGOING_EVENTS;
metadata->pid = pid_vnr(event->tgid);
metadata->fd = create_fd(group, event);
return metadata->fd;
}
+#ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS
+static struct fanotify_response_event *dequeue_re(struct fsnotify_group *group,
+ __s32 fd)
+{
+ struct fanotify_response_event *re, *return_re = NULL;
+
+ mutex_lock(&group->fanotify_data.access_mutex);
+ list_for_each_entry(re, &group->fanotify_data.access_list, list) {
+ if (re->fd != fd)
+ continue;
+
+ list_del_init(&re->list);
+ return_re = re;
+ break;
+ }
+ mutex_unlock(&group->fanotify_data.access_mutex);
+
+ pr_debug("%s: found return_re=%p\n", __func__, return_re);
+
+ return return_re;
+}
+
+static int process_access_response(struct fsnotify_group *group,
+ struct fanotify_response *response_struct)
+{
+ struct fanotify_response_event *re;
+ __s32 fd = response_struct->fd;
+ __u32 response = response_struct->response;
+
+ pr_debug("%s: group=%p fd=%d response=%d\n", __func__, group,
+ fd, response);
+ /*
+ * make sure the response is valid, if invalid we do nothing and either
+ * userspace can send a valid responce or we will clean it up after the
+ * timeout
+ */
+ switch (response) {
+ case FAN_ALLOW:
+ case FAN_DENY:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (fd < 0)
+ return -EINVAL;
+
+ re = dequeue_re(group, fd);
+ if (!re)
+ return -ENOENT;
+
+ re->event->response = response;
+
+ wake_up(&group->fanotify_data.access_waitq);
+
+ kmem_cache_free(fanotify_response_event_cache, re);
+
+ return 0;
+}
+
+static int prepare_for_access_response(struct fsnotify_group *group,
+ struct fsnotify_event *event,
+ __s32 fd)
+{
+ struct fanotify_response_event *re;
+
+ if (!(event->mask & FAN_ALL_PERM_EVENTS))
+ return 0;
+
+ re = kmem_cache_alloc(fanotify_response_event_cache, GFP_KERNEL);
+ if (!re)
+ return -ENOMEM;
+
+ re->event = event;
+ re->fd = fd;
+
+ mutex_lock(&group->fanotify_data.access_mutex);
+ list_add_tail(&re->list, &group->fanotify_data.access_list);
+ mutex_unlock(&group->fanotify_data.access_mutex);
+
+ return 0;
+}
+
+static void remove_access_response(struct fsnotify_group *group,
+ struct fsnotify_event *event,
+ __s32 fd)
+{
+ struct fanotify_response_event *re;
+
+ if (!(event->mask & FAN_ALL_PERM_EVENTS))
+ return;
+
+ re = dequeue_re(group, fd);
+ if (!re)
+ return;
+
+ BUG_ON(re->event != event);
+
+ kmem_cache_free(fanotify_response_event_cache, re);
+
+ return;
+}
+#else
+static int prepare_for_access_response(struct fsnotify_group *group,
+ struct fsnotify_event *event,
+ __s32 fd)
+{
+ return 0;
+}
+
+static void remove_access_response(struct fsnotify_group *group,
+ struct fsnotify_event *event,
+ __s32 fd)
+{
+ return;
+}
+#endif
+
static ssize_t copy_event_to_user(struct fsnotify_group *group,
struct fsnotify_event *event,
char __user *buf)
{
struct fanotify_event_metadata fanotify_event_metadata;
- int ret;
+ int fd, ret;
pr_debug("%s: group=%p event=%p\n", __func__, group, event);
- ret = fill_event_metadata(group, &fanotify_event_metadata, event);
- if (ret < 0)
- return ret;
+ fd = fill_event_metadata(group, &fanotify_event_metadata, event);
+ if (fd < 0)
+ return fd;
+ ret = prepare_for_access_response(group, event, fd);
+ if (ret)
+ goto out_close_fd;
+
+ ret = -EFAULT;
if (copy_to_user(buf, &fanotify_event_metadata, FAN_EVENT_METADATA_LEN))
- return -EFAULT;
+ goto out_kill_access_response;
return FAN_EVENT_METADATA_LEN;
+
+out_kill_access_response:
+ remove_access_response(group, event, fd);
+out_close_fd:
+ sys_close(fd);
+ return ret;
}
/* intofiy userspace file descriptor functions */
return ret;
}
+static ssize_t fanotify_write(struct file *file, const char __user *buf, size_t count, loff_t *pos)
+{
+#ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS
+ struct fanotify_response response = { .fd = -1, .response = -1 };
+ struct fsnotify_group *group;
+ int ret;
+
+ group = file->private_data;
+
+ if (count > sizeof(response))
+ count = sizeof(response);
+
+ pr_debug("%s: group=%p count=%zu\n", __func__, group, count);
+
+ if (copy_from_user(&response, buf, count))
+ return -EFAULT;
+
+ ret = process_access_response(group, &response);
+ if (ret < 0)
+ count = ret;
+
+ return count;
+#else
+ return -EINVAL;
+#endif
+}
+
static int fanotify_release(struct inode *ignored, struct file *file)
{
struct fsnotify_group *group = file->private_data;
static const struct file_operations fanotify_fops = {
.poll = fanotify_poll,
.read = fanotify_read,
+ .write = fanotify_write,
.fasync = NULL,
.release = fanotify_release,
.unlocked_ioctl = fanotify_ioctl,
return ret;
}
-static __u32 fanotify_mark_remove_from_mask(struct fsnotify_mark *fsn_mark, __u32 mask)
+static __u32 fanotify_mark_remove_from_mask(struct fsnotify_mark *fsn_mark,
+ __u32 mask,
+ unsigned int flags)
{
__u32 oldmask;
spin_lock(&fsn_mark->lock);
- oldmask = fsn_mark->mask;
- fsn_mark->mask = oldmask & ~mask;
+ if (!(flags & FAN_MARK_IGNORED_MASK)) {
+ oldmask = fsn_mark->mask;
+ fsnotify_set_mark_mask_locked(fsn_mark, (oldmask & ~mask));
+ } else {
+ oldmask = fsn_mark->ignored_mask;
+ fsnotify_set_mark_ignored_mask_locked(fsn_mark, (oldmask & ~mask));
+ }
spin_unlock(&fsn_mark->lock);
if (!(oldmask & ~mask))
return mask & oldmask;
}
-static int fanotify_remove_mark(struct fsnotify_group *group, struct inode *inode,
- struct vfsmount *mnt, __u32 mask)
+static int fanotify_remove_vfsmount_mark(struct fsnotify_group *group,
+ struct vfsmount *mnt, __u32 mask,
+ unsigned int flags)
{
struct fsnotify_mark *fsn_mark = NULL;
__u32 removed;
- BUG_ON(inode && mnt);
- BUG_ON(!inode && !mnt);
+ fsn_mark = fsnotify_find_vfsmount_mark(group, mnt);
+ if (!fsn_mark)
+ return -ENOENT;
- if (inode)
- fsn_mark = fsnotify_find_inode_mark(group, inode);
- else if (mnt)
- fsn_mark = fsnotify_find_vfsmount_mark(group, mnt);
- else
- BUG();
+ removed = fanotify_mark_remove_from_mask(fsn_mark, mask, flags);
+ fsnotify_put_mark(fsn_mark);
+ if (removed & group->mask)
+ fsnotify_recalc_group_mask(group);
+ if (removed & mnt->mnt_fsnotify_mask)
+ fsnotify_recalc_vfsmount_mask(mnt);
+ return 0;
+}
+
+static int fanotify_remove_inode_mark(struct fsnotify_group *group,
+ struct inode *inode, __u32 mask,
+ unsigned int flags)
+{
+ struct fsnotify_mark *fsn_mark = NULL;
+ __u32 removed;
+
+ fsn_mark = fsnotify_find_inode_mark(group, inode);
if (!fsn_mark)
return -ENOENT;
- removed = fanotify_mark_remove_from_mask(fsn_mark, mask);
+ removed = fanotify_mark_remove_from_mask(fsn_mark, mask, flags);
/* matches the fsnotify_find_inode_mark() */
fsnotify_put_mark(fsn_mark);
if (removed & group->mask)
fsnotify_recalc_group_mask(group);
- if (inode) {
- if (removed & inode->i_fsnotify_mask)
- fsnotify_recalc_inode_mask(inode);
- } else if (mnt) {
- if (removed & mnt->mnt_fsnotify_mask)
- fsnotify_recalc_vfsmount_mask(mnt);
- }
+ if (removed & inode->i_fsnotify_mask)
+ fsnotify_recalc_inode_mask(inode);
return 0;
}
-static __u32 fanotify_mark_add_to_mask(struct fsnotify_mark *fsn_mark, __u32 mask)
+static __u32 fanotify_mark_add_to_mask(struct fsnotify_mark *fsn_mark,
+ __u32 mask,
+ unsigned int flags)
{
__u32 oldmask;
spin_lock(&fsn_mark->lock);
- oldmask = fsn_mark->mask;
- fsn_mark->mask = oldmask | mask;
+ if (!(flags & FAN_MARK_IGNORED_MASK)) {
+ oldmask = fsn_mark->mask;
+ fsnotify_set_mark_mask_locked(fsn_mark, (oldmask | mask));
+ } else {
+ oldmask = fsn_mark->ignored_mask;
+ fsnotify_set_mark_ignored_mask_locked(fsn_mark, (oldmask | mask));
+ if (flags & FAN_MARK_IGNORED_SURV_MODIFY)
+ fsn_mark->flags |= FSNOTIFY_MARK_FLAG_IGNORED_SURV_MODIFY;
+ }
spin_unlock(&fsn_mark->lock);
return mask & ~oldmask;
}
static int fanotify_add_vfsmount_mark(struct fsnotify_group *group,
- struct vfsmount *mnt, __u32 mask)
+ struct vfsmount *mnt, __u32 mask,
+ unsigned int flags)
{
struct fsnotify_mark *fsn_mark;
__u32 added;
return ret;
}
}
- added = fanotify_mark_add_to_mask(fsn_mark, mask);
+ added = fanotify_mark_add_to_mask(fsn_mark, mask, flags);
fsnotify_put_mark(fsn_mark);
if (added) {
if (added & ~group->mask)
}
static int fanotify_add_inode_mark(struct fsnotify_group *group,
- struct inode *inode, __u32 mask)
+ struct inode *inode, __u32 mask,
+ unsigned int flags)
{
struct fsnotify_mark *fsn_mark;
__u32 added;
return ret;
}
}
- added = fanotify_mark_add_to_mask(fsn_mark, mask);
+ added = fanotify_mark_add_to_mask(fsn_mark, mask, flags);
fsnotify_put_mark(fsn_mark);
if (added) {
if (added & ~group->mask)
return 0;
}
-static bool fanotify_mark_validate_input(int flags,
- __u32 mask)
-{
- pr_debug("%s: flags=%x mask=%x\n", __func__, flags, mask);
-
- /* are flags valid of this operation? */
- if (!fanotify_mark_flags_valid(flags))
- return false;
- /* is the mask valid? */
- if (!fanotify_mask_valid(mask))
- return false;
- return true;
-}
-
/* fanotify syscalls */
-SYSCALL_DEFINE3(fanotify_init, unsigned int, flags, unsigned int, event_f_flags,
- unsigned int, priority)
+SYSCALL_DEFINE2(fanotify_init, unsigned int, flags, unsigned int, event_f_flags)
{
struct fsnotify_group *group;
int f_flags, fd;
- pr_debug("%s: flags=%d event_f_flags=%d priority=%d\n",
- __func__, flags, event_f_flags, priority);
-
- if (event_f_flags)
- return -EINVAL;
- if (priority)
- return -EINVAL;
+ pr_debug("%s: flags=%d event_f_flags=%d\n",
+ __func__, flags, event_f_flags);
if (!capable(CAP_SYS_ADMIN))
return -EACCES;
if (flags & ~FAN_ALL_INIT_FLAGS)
return -EINVAL;
- f_flags = (O_RDONLY | FMODE_NONOTIFY);
+ f_flags = O_RDWR | FMODE_NONOTIFY;
if (flags & FAN_CLOEXEC)
f_flags |= O_CLOEXEC;
if (flags & FAN_NONBLOCK)
if (IS_ERR(group))
return PTR_ERR(group);
+ group->fanotify_data.f_flags = event_f_flags;
+#ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS
+ mutex_init(&group->fanotify_data.access_mutex);
+ init_waitqueue_head(&group->fanotify_data.access_waitq);
+ INIT_LIST_HEAD(&group->fanotify_data.access_list);
+#endif
+
fd = anon_inode_getfd("[fanotify]", &fanotify_fops, group, f_flags);
if (fd < 0)
goto out_put_group;
if (mask & ((__u64)0xffffffff << 32))
return -EINVAL;
- if (!fanotify_mark_validate_input(flags, mask))
+ if (flags & ~FAN_ALL_MARK_FLAGS)
+ return -EINVAL;
+ switch (flags & (FAN_MARK_ADD | FAN_MARK_REMOVE | FAN_MARK_FLUSH)) {
+ case FAN_MARK_ADD:
+ case FAN_MARK_REMOVE:
+ case FAN_MARK_FLUSH:
+ break;
+ default:
+ return -EINVAL;
+ }
+#ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS
+ if (mask & ~(FAN_ALL_EVENTS | FAN_ALL_PERM_EVENTS | FAN_EVENT_ON_CHILD))
+#else
+ if (mask & ~(FAN_ALL_EVENTS | FAN_EVENT_ON_CHILD))
+#endif
return -EINVAL;
filp = fget_light(fanotify_fd, &fput_needed);
group = filp->private_data;
/* create/update an inode mark */
- switch (flags & (FAN_MARK_ADD | FAN_MARK_REMOVE)) {
+ switch (flags & (FAN_MARK_ADD | FAN_MARK_REMOVE | FAN_MARK_FLUSH)) {
case FAN_MARK_ADD:
if (flags & FAN_MARK_MOUNT)
- ret = fanotify_add_vfsmount_mark(group, mnt, mask);
+ ret = fanotify_add_vfsmount_mark(group, mnt, mask, flags);
else
- ret = fanotify_add_inode_mark(group, inode, mask);
+ ret = fanotify_add_inode_mark(group, inode, mask, flags);
break;
case FAN_MARK_REMOVE:
- ret = fanotify_remove_mark(group, inode, mnt, mask);
+ if (flags & FAN_MARK_MOUNT)
+ ret = fanotify_remove_vfsmount_mark(group, mnt, mask, flags);
+ else
+ ret = fanotify_remove_inode_mark(group, inode, mask, flags);
+ break;
+ case FAN_MARK_FLUSH:
+ if (flags & FAN_MARK_MOUNT)
+ fsnotify_clear_vfsmount_marks_by_group(group);
+ else
+ fsnotify_clear_inode_marks_by_group(group);
+ fsnotify_recalc_group_mask(group);
break;
default:
ret = -EINVAL;
static int __init fanotify_user_setup(void)
{
fanotify_mark_cache = KMEM_CACHE(fsnotify_mark, SLAB_PANIC);
+ fanotify_response_event_cache = KMEM_CACHE(fanotify_response_event,
+ SLAB_PANIC);
return 0;
}