#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>
+#define FANOTIFY_DEFAULT_MAX_EVENTS 16384
+#define FANOTIFY_DEFAULT_MAX_MARKS 8192
+
extern const struct fsnotify_ops fanotify_fsnotify_ops;
static struct kmem_cache *fanotify_mark_cache __read_mostly;
* 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);
re->fd = fd;
mutex_lock(&group->fanotify_data.access_mutex);
+
+ if (group->fanotify_data.bypass_perm) {
+ mutex_unlock(&group->fanotify_data.access_mutex);
+ kmem_cache_free(fanotify_response_event_cache, re);
+ event->response = FAN_ALLOW;
+ return 0;
+ }
+
list_add_tail(&re->list, &group->fanotify_data.access_list);
mutex_unlock(&group->fanotify_data.access_mutex);
static int fanotify_release(struct inode *ignored, struct file *file)
{
struct fsnotify_group *group = file->private_data;
+ struct fanotify_response_event *re, *lre;
pr_debug("%s: file=%p group=%p\n", __func__, file, group);
+#ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS
+ mutex_lock(&group->fanotify_data.access_mutex);
+
+ group->fanotify_data.bypass_perm = true;
+
+ list_for_each_entry_safe(re, lre, &group->fanotify_data.access_list, list) {
+ pr_debug("%s: found group=%p re=%p event=%p\n", __func__, group,
+ re, re->event);
+
+ list_del_init(&re->list);
+ re->event->response = FAN_ALLOW;
+
+ kmem_cache_free(fanotify_response_event_cache, re);
+ }
+ mutex_unlock(&group->fanotify_data.access_mutex);
+
+ wake_up(&group->fanotify_data.access_waitq);
+#endif
/* matches the fanotify_init->fsnotify_alloc_group */
fsnotify_put_group(group);
.release = fanotify_release,
.unlocked_ioctl = fanotify_ioctl,
.compat_ioctl = fanotify_ioctl,
+ .llseek = noop_llseek,
};
static void fanotify_free_mark(struct fsnotify_mark *fsn_mark)
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);
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 (removed & inode->i_fsnotify_mask)
fsnotify_recalc_inode_mask(inode);
if (!fsn_mark) {
int ret;
+ if (atomic_read(&group->num_marks) > group->fanotify_data.max_marks)
+ return -ENOSPC;
+
fsn_mark = kmem_cache_alloc(fanotify_mark_cache, GFP_KERNEL);
if (!fsn_mark)
return -ENOMEM;
}
added = fanotify_mark_add_to_mask(fsn_mark, mask, flags);
fsnotify_put_mark(fsn_mark);
- if (added) {
- if (added & ~group->mask)
- fsnotify_recalc_group_mask(group);
- if (added & ~mnt->mnt_fsnotify_mask)
- fsnotify_recalc_vfsmount_mask(mnt);
- }
+ if (added & ~mnt->mnt_fsnotify_mask)
+ fsnotify_recalc_vfsmount_mask(mnt);
+
return 0;
}
pr_debug("%s: group=%p inode=%p\n", __func__, group, inode);
+ /*
+ * If some other task has this inode open for write we should not add
+ * an ignored mark, unless that ignored mark is supposed to survive
+ * modification changes anyway.
+ */
+ if ((flags & FAN_MARK_IGNORED_MASK) &&
+ !(flags & FAN_MARK_IGNORED_SURV_MODIFY) &&
+ (atomic_read(&inode->i_writecount) > 0))
+ return 0;
+
fsn_mark = fsnotify_find_inode_mark(group, inode);
if (!fsn_mark) {
int ret;
+ if (atomic_read(&group->num_marks) > group->fanotify_data.max_marks)
+ return -ENOSPC;
+
fsn_mark = kmem_cache_alloc(fanotify_mark_cache, GFP_KERNEL);
if (!fsn_mark)
return -ENOMEM;
}
added = fanotify_mark_add_to_mask(fsn_mark, mask, flags);
fsnotify_put_mark(fsn_mark);
- if (added) {
- if (added & ~group->mask)
- fsnotify_recalc_group_mask(group);
- if (added & ~inode->i_fsnotify_mask)
- fsnotify_recalc_inode_mask(inode);
- }
+ 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;
+ pr_debug("%s: flags=%d event_f_flags=%d\n",
+ __func__, flags, event_f_flags);
if (!capable(CAP_SYS_ADMIN))
- return -EACCES;
+ return -EPERM;
if (flags & ~FAN_ALL_INIT_FLAGS)
return -EINVAL;
if (IS_ERR(group))
return PTR_ERR(group);
- group->priority = priority;
+ 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
+ switch (flags & FAN_ALL_CLASS_BITS) {
+ case FAN_CLASS_NOTIF:
+ group->priority = FS_PRIO_0;
+ break;
+ case FAN_CLASS_CONTENT:
+ group->priority = FS_PRIO_1;
+ break;
+ case FAN_CLASS_PRE_CONTENT:
+ group->priority = FS_PRIO_2;
+ break;
+ default:
+ fd = -EINVAL;
+ goto out_put_group;
+ }
+
+ if (flags & FAN_UNLIMITED_QUEUE) {
+ fd = -EPERM;
+ if (!capable(CAP_SYS_ADMIN))
+ goto out_put_group;
+ group->max_events = UINT_MAX;
+ } else {
+ group->max_events = FANOTIFY_DEFAULT_MAX_EVENTS;
+ }
+
+ group->fanotify_data.max_marks = FANOTIFY_DEFAULT_MAX_MARKS;
fd = anon_inode_getfd("[fanotify]", &fanotify_fops, group, f_flags);
if (fd < 0)
ret = -EINVAL;
if (unlikely(filp->f_op != &fanotify_fops))
goto fput_and_out;
+ group = filp->private_data;
+
+ /*
+ * group->priority == FS_PRIO_0 == FAN_CLASS_NOTIF. These are not
+ * allowed to set permissions events.
+ */
+ ret = -EINVAL;
+ if (mask & FAN_ALL_PERM_EVENTS &&
+ group->priority == FS_PRIO_0)
+ goto fput_and_out;
ret = fanotify_find_path(dfd, pathname, &path, flags);
if (ret)
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 | FAN_MARK_FLUSH)) {
fsnotify_clear_vfsmount_marks_by_group(group);
else
fsnotify_clear_inode_marks_by_group(group);
- fsnotify_recalc_group_mask(group);
break;
default:
ret = -EINVAL;