+#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
* 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 void fanotify_update_object_mask(struct fsnotify_group *group,
- struct inode *inode,
- struct vfsmount *mnt,
- struct fsnotify_mark *fsn_mark,
- unsigned int flags,
- __u32 mask)
+static __u32 fanotify_mark_remove_from_mask(struct fsnotify_mark *fsn_mark,
+ __u32 mask,
+ unsigned int flags)
{
- __u32 old_mask, new_mask;
-
- pr_debug("%s: group=%p inode=%p mnt=%p fsn_mark=%p flags=%x mask=%x\n",
- __func__, group, inode, mnt, fsn_mark, flags, mask);
+ __u32 oldmask;
spin_lock(&fsn_mark->lock);
- old_mask = fsn_mark->mask;
- if (flags & FAN_MARK_ADD)
- fsn_mark->mask |= mask;
- else if (flags & FAN_MARK_REMOVE)
- fsn_mark->mask &= ~mask;
- else
- BUG();
- new_mask = fsn_mark->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 (!new_mask)
+ if (!(oldmask & ~mask))
fsnotify_destroy_mark(fsn_mark);
- /* we made changes to a mask, update the group mask and the object mask
- * so things happen quickly. */
- if (old_mask != new_mask) {
- __u32 dropped, do_object, do_group;
-
- /* more bits in old than in new? */
- dropped = (old_mask & ~new_mask);
- /* more bits in this fsn_mark than the group? */
- do_group = (new_mask & ~group->mask);
-
- if (inode) {
- /* more bits in this fsn_mark than the object's mask? */
- do_object = (new_mask & ~inode->i_fsnotify_mask);
- /* update the object with this new fsn_mark */
- if (dropped || do_object)
- fsnotify_recalc_inode_mask(inode);
- } else if (mnt) {
- /* more bits in this fsn_mark than the object's mask? */
- do_object = (new_mask & ~mnt->mnt_fsnotify_mask);
- /* update the object with this new fsn_mark */
- if (dropped || do_object)
- fsnotify_recalc_vfsmount_mask(mnt);
- } else {
- BUG();
- }
-
- /* update the group mask with the new mask */
- if (dropped || do_group)
- fsnotify_recalc_group_mask(group);
- }
+ return mask & oldmask;
}
-static int fanotify_remove_mark(struct fsnotify_group *group, struct inode *inode,
- struct vfsmount *mnt, unsigned int flags, __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 & 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;
- fanotify_update_object_mask(group, inode, mnt, fsn_mark, flags, mask);
-
+ removed = fanotify_mark_remove_from_mask(fsn_mark, mask, flags);
/* matches the fsnotify_find_inode_mark() */
fsnotify_put_mark(fsn_mark);
+ if (removed & inode->i_fsnotify_mask)
+ fsnotify_recalc_inode_mask(inode);
return 0;
}
-static struct fsnotify_mark *fanotify_add_vfsmount_mark(struct fsnotify_group *group,
- struct vfsmount *mnt)
+static __u32 fanotify_mark_add_to_mask(struct fsnotify_mark *fsn_mark,
+ __u32 mask,
+ unsigned int flags)
+{
+ __u32 oldmask;
+
+ spin_lock(&fsn_mark->lock);
+ 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,
+ unsigned int flags)
{
struct fsnotify_mark *fsn_mark;
+ __u32 added;
fsn_mark = fsnotify_find_vfsmount_mark(group, mnt);
if (!fsn_mark) {
- struct fsnotify_mark *new_fsn_mark;
int ret;
- fsn_mark = ERR_PTR(-ENOMEM);
- new_fsn_mark = kmem_cache_alloc(fanotify_mark_cache, GFP_KERNEL);
- if (!new_fsn_mark)
- goto out;
+ fsn_mark = kmem_cache_alloc(fanotify_mark_cache, GFP_KERNEL);
+ if (!fsn_mark)
+ return -ENOMEM;
- fsnotify_init_mark(new_fsn_mark, fanotify_free_mark);
- ret = fsnotify_add_mark(new_fsn_mark, group, NULL, mnt, 0);
+ fsnotify_init_mark(fsn_mark, fanotify_free_mark);
+ ret = fsnotify_add_mark(fsn_mark, group, NULL, mnt, 0);
if (ret) {
- fsn_mark = ERR_PTR(ret);
- fanotify_free_mark(new_fsn_mark);
- goto out;
+ fanotify_free_mark(fsn_mark);
+ return ret;
}
-
- fsn_mark = new_fsn_mark;
}
-out:
- return fsn_mark;
+ added = fanotify_mark_add_to_mask(fsn_mark, mask, flags);
+ fsnotify_put_mark(fsn_mark);
+ if (added & ~mnt->mnt_fsnotify_mask)
+ fsnotify_recalc_vfsmount_mask(mnt);
+
+ return 0;
}
-static struct fsnotify_mark *fanotify_add_inode_mark(struct fsnotify_group *group,
- struct inode *inode)
+static int fanotify_add_inode_mark(struct fsnotify_group *group,
+ struct inode *inode, __u32 mask,
+ unsigned int flags)
{
struct fsnotify_mark *fsn_mark;
+ __u32 added;
pr_debug("%s: group=%p inode=%p\n", __func__, group, inode);
fsn_mark = fsnotify_find_inode_mark(group, inode);
if (!fsn_mark) {
- struct fsnotify_mark *new_fsn_mark;
int ret;
- fsn_mark = ERR_PTR(-ENOMEM);
- new_fsn_mark = kmem_cache_alloc(fanotify_mark_cache, GFP_KERNEL);
- if (!new_fsn_mark)
- goto out;
+ fsn_mark = kmem_cache_alloc(fanotify_mark_cache, GFP_KERNEL);
+ if (!fsn_mark)
+ return -ENOMEM;
- fsnotify_init_mark(new_fsn_mark, fanotify_free_mark);
- ret = fsnotify_add_mark(new_fsn_mark, group, inode, NULL, 0);
+ fsnotify_init_mark(fsn_mark, fanotify_free_mark);
+ ret = fsnotify_add_mark(fsn_mark, group, inode, NULL, 0);
if (ret) {
- fsn_mark = ERR_PTR(ret);
- fanotify_free_mark(new_fsn_mark);
- goto out;
+ fanotify_free_mark(fsn_mark);
+ return ret;
}
-
- fsn_mark = new_fsn_mark;
}
-out:
- return fsn_mark;
-}
-
-static int fanotify_add_mark(struct fsnotify_group *group, struct inode *inode,
- struct vfsmount *mnt, unsigned int flags, __u32 mask)
-{
- struct fsnotify_mark *fsn_mark;
-
- pr_debug("%s: group=%p inode=%p mnt=%p flags=%x mask=%x\n",
- __func__, group, inode, mnt, flags, mask);
-
- BUG_ON(inode && mnt);
- BUG_ON(!inode && !mnt);
-
- if (inode)
- fsn_mark = fanotify_add_inode_mark(group, inode);
- else if (mnt)
- fsn_mark = fanotify_add_vfsmount_mark(group, mnt);
- else
- BUG();
-
- if (IS_ERR(fsn_mark))
- goto out;
-
- fanotify_update_object_mask(group, inode, mnt, fsn_mark, flags, mask);
-
- /* match the init or the find.... */
+ added = fanotify_mark_add_to_mask(fsn_mark, mask, flags);
fsnotify_put_mark(fsn_mark);
-out:
- return PTR_ERR(fsn_mark);
-}
-
-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;
+ if (added & ~inode->i_fsnotify_mask)
+ fsnotify_recalc_inode_mask(inode);
+ return 0;
}
/* 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;
__u64 mask, int dfd,
const char __user * pathname)
{
- struct inode *inode;
+ struct inode *inode = NULL;
+ struct vfsmount *mnt = NULL;
struct fsnotify_group *group;
struct file *filp;
struct path path;
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);
goto fput_and_out;
/* inode held in place by reference to path; group by fget on fd */
- inode = path.dentry->d_inode;
+ if (!(flags & FAN_MARK_MOUNT))
+ inode = path.dentry->d_inode;
+ else
+ mnt = path.mnt;
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:
- ret = fanotify_add_mark(group, inode, NULL, flags, mask);
+ if (flags & FAN_MARK_MOUNT)
+ ret = fanotify_add_vfsmount_mark(group, mnt, mask, flags);
+ else
+ ret = fanotify_add_inode_mark(group, inode, mask, flags);
break;
case FAN_MARK_REMOVE:
- ret = fanotify_remove_mark(group, inode, NULL, flags, 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);
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;
}