|
@@ -16,6 +16,10 @@
|
|
|
|
|
|
#include <asm/ioctls.h>
|
|
#include <asm/ioctls.h>
|
|
|
|
|
|
|
|
+#define FANOTIFY_DEFAULT_MAX_EVENTS 16384
|
|
|
|
+#define FANOTIFY_DEFAULT_MAX_MARKS 8192
|
|
|
|
+#define FANOTIFY_DEFAULT_MAX_LISTENERS 128
|
|
|
|
+
|
|
extern const struct fsnotify_ops fanotify_fsnotify_ops;
|
|
extern const struct fsnotify_ops fanotify_fsnotify_ops;
|
|
|
|
|
|
static struct kmem_cache *fanotify_mark_cache __read_mostly;
|
|
static struct kmem_cache *fanotify_mark_cache __read_mostly;
|
|
@@ -326,7 +330,7 @@ static ssize_t fanotify_read(struct file *file, char __user *buf,
|
|
ret = -EAGAIN;
|
|
ret = -EAGAIN;
|
|
if (file->f_flags & O_NONBLOCK)
|
|
if (file->f_flags & O_NONBLOCK)
|
|
break;
|
|
break;
|
|
- ret = -EINTR;
|
|
|
|
|
|
+ ret = -ERESTARTSYS;
|
|
if (signal_pending(current))
|
|
if (signal_pending(current))
|
|
break;
|
|
break;
|
|
|
|
|
|
@@ -372,11 +376,10 @@ static ssize_t fanotify_write(struct file *file, const char __user *buf, size_t
|
|
static int fanotify_release(struct inode *ignored, struct file *file)
|
|
static int fanotify_release(struct inode *ignored, struct file *file)
|
|
{
|
|
{
|
|
struct fsnotify_group *group = file->private_data;
|
|
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
|
|
#ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS
|
|
|
|
+ struct fanotify_response_event *re, *lre;
|
|
|
|
+
|
|
mutex_lock(&group->fanotify_data.access_mutex);
|
|
mutex_lock(&group->fanotify_data.access_mutex);
|
|
|
|
|
|
group->fanotify_data.bypass_perm = true;
|
|
group->fanotify_data.bypass_perm = true;
|
|
@@ -554,18 +557,24 @@ static __u32 fanotify_mark_add_to_mask(struct fsnotify_mark *fsn_mark,
|
|
__u32 mask,
|
|
__u32 mask,
|
|
unsigned int flags)
|
|
unsigned int flags)
|
|
{
|
|
{
|
|
- __u32 oldmask;
|
|
|
|
|
|
+ __u32 oldmask = -1;
|
|
|
|
|
|
spin_lock(&fsn_mark->lock);
|
|
spin_lock(&fsn_mark->lock);
|
|
if (!(flags & FAN_MARK_IGNORED_MASK)) {
|
|
if (!(flags & FAN_MARK_IGNORED_MASK)) {
|
|
oldmask = fsn_mark->mask;
|
|
oldmask = fsn_mark->mask;
|
|
fsnotify_set_mark_mask_locked(fsn_mark, (oldmask | mask));
|
|
fsnotify_set_mark_mask_locked(fsn_mark, (oldmask | mask));
|
|
} else {
|
|
} else {
|
|
- oldmask = fsn_mark->ignored_mask;
|
|
|
|
- fsnotify_set_mark_ignored_mask_locked(fsn_mark, (oldmask | mask));
|
|
|
|
|
|
+ __u32 tmask = fsn_mark->ignored_mask | mask;
|
|
|
|
+ fsnotify_set_mark_ignored_mask_locked(fsn_mark, tmask);
|
|
if (flags & FAN_MARK_IGNORED_SURV_MODIFY)
|
|
if (flags & FAN_MARK_IGNORED_SURV_MODIFY)
|
|
fsn_mark->flags |= FSNOTIFY_MARK_FLAG_IGNORED_SURV_MODIFY;
|
|
fsn_mark->flags |= FSNOTIFY_MARK_FLAG_IGNORED_SURV_MODIFY;
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ if (!(flags & FAN_MARK_ONDIR)) {
|
|
|
|
+ __u32 tmask = fsn_mark->ignored_mask | FAN_ONDIR;
|
|
|
|
+ fsnotify_set_mark_ignored_mask_locked(fsn_mark, tmask);
|
|
|
|
+ }
|
|
|
|
+
|
|
spin_unlock(&fsn_mark->lock);
|
|
spin_unlock(&fsn_mark->lock);
|
|
|
|
|
|
return mask & ~oldmask;
|
|
return mask & ~oldmask;
|
|
@@ -582,6 +591,9 @@ static int fanotify_add_vfsmount_mark(struct fsnotify_group *group,
|
|
if (!fsn_mark) {
|
|
if (!fsn_mark) {
|
|
int ret;
|
|
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);
|
|
fsn_mark = kmem_cache_alloc(fanotify_mark_cache, GFP_KERNEL);
|
|
if (!fsn_mark)
|
|
if (!fsn_mark)
|
|
return -ENOMEM;
|
|
return -ENOMEM;
|
|
@@ -610,10 +622,23 @@ static int fanotify_add_inode_mark(struct fsnotify_group *group,
|
|
|
|
|
|
pr_debug("%s: group=%p inode=%p\n", __func__, group, inode);
|
|
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);
|
|
fsn_mark = fsnotify_find_inode_mark(group, inode);
|
|
if (!fsn_mark) {
|
|
if (!fsn_mark) {
|
|
int ret;
|
|
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);
|
|
fsn_mark = kmem_cache_alloc(fanotify_mark_cache, GFP_KERNEL);
|
|
if (!fsn_mark)
|
|
if (!fsn_mark)
|
|
return -ENOMEM;
|
|
return -ENOMEM;
|
|
@@ -637,6 +662,7 @@ SYSCALL_DEFINE2(fanotify_init, unsigned int, flags, unsigned int, event_f_flags)
|
|
{
|
|
{
|
|
struct fsnotify_group *group;
|
|
struct fsnotify_group *group;
|
|
int f_flags, fd;
|
|
int f_flags, fd;
|
|
|
|
+ struct user_struct *user;
|
|
|
|
|
|
pr_debug("%s: flags=%d event_f_flags=%d\n",
|
|
pr_debug("%s: flags=%d event_f_flags=%d\n",
|
|
__func__, flags, event_f_flags);
|
|
__func__, flags, event_f_flags);
|
|
@@ -647,6 +673,12 @@ SYSCALL_DEFINE2(fanotify_init, unsigned int, flags, unsigned int, event_f_flags)
|
|
if (flags & ~FAN_ALL_INIT_FLAGS)
|
|
if (flags & ~FAN_ALL_INIT_FLAGS)
|
|
return -EINVAL;
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
+ user = get_current_user();
|
|
|
|
+ if (atomic_read(&user->fanotify_listeners) > FANOTIFY_DEFAULT_MAX_LISTENERS) {
|
|
|
|
+ free_uid(user);
|
|
|
|
+ return -EMFILE;
|
|
|
|
+ }
|
|
|
|
+
|
|
f_flags = O_RDWR | FMODE_NONOTIFY;
|
|
f_flags = O_RDWR | FMODE_NONOTIFY;
|
|
if (flags & FAN_CLOEXEC)
|
|
if (flags & FAN_CLOEXEC)
|
|
f_flags |= O_CLOEXEC;
|
|
f_flags |= O_CLOEXEC;
|
|
@@ -658,12 +690,47 @@ SYSCALL_DEFINE2(fanotify_init, unsigned int, flags, unsigned int, event_f_flags)
|
|
if (IS_ERR(group))
|
|
if (IS_ERR(group))
|
|
return PTR_ERR(group);
|
|
return PTR_ERR(group);
|
|
|
|
|
|
|
|
+ group->fanotify_data.user = user;
|
|
|
|
+ atomic_inc(&user->fanotify_listeners);
|
|
|
|
+
|
|
group->fanotify_data.f_flags = event_f_flags;
|
|
group->fanotify_data.f_flags = event_f_flags;
|
|
#ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS
|
|
#ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS
|
|
mutex_init(&group->fanotify_data.access_mutex);
|
|
mutex_init(&group->fanotify_data.access_mutex);
|
|
init_waitqueue_head(&group->fanotify_data.access_waitq);
|
|
init_waitqueue_head(&group->fanotify_data.access_waitq);
|
|
INIT_LIST_HEAD(&group->fanotify_data.access_list);
|
|
INIT_LIST_HEAD(&group->fanotify_data.access_list);
|
|
#endif
|
|
#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;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (flags & FAN_UNLIMITED_MARKS) {
|
|
|
|
+ fd = -EPERM;
|
|
|
|
+ if (!capable(CAP_SYS_ADMIN))
|
|
|
|
+ goto out_put_group;
|
|
|
|
+ group->fanotify_data.max_marks = UINT_MAX;
|
|
|
|
+ } else {
|
|
|
|
+ group->fanotify_data.max_marks = FANOTIFY_DEFAULT_MAX_MARKS;
|
|
|
|
+ }
|
|
|
|
|
|
fd = anon_inode_getfd("[fanotify]", &fanotify_fops, group, f_flags);
|
|
fd = anon_inode_getfd("[fanotify]", &fanotify_fops, group, f_flags);
|
|
if (fd < 0)
|
|
if (fd < 0)
|
|
@@ -704,6 +771,12 @@ SYSCALL_DEFINE(fanotify_mark)(int fanotify_fd, unsigned int flags,
|
|
default:
|
|
default:
|
|
return -EINVAL;
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ if (mask & FAN_ONDIR) {
|
|
|
|
+ flags |= FAN_MARK_ONDIR;
|
|
|
|
+ mask &= ~FAN_ONDIR;
|
|
|
|
+ }
|
|
|
|
+
|
|
#ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS
|
|
#ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS
|
|
if (mask & ~(FAN_ALL_EVENTS | FAN_ALL_PERM_EVENTS | FAN_EVENT_ON_CHILD))
|
|
if (mask & ~(FAN_ALL_EVENTS | FAN_ALL_PERM_EVENTS | FAN_EVENT_ON_CHILD))
|
|
#else
|
|
#else
|
|
@@ -719,6 +792,16 @@ SYSCALL_DEFINE(fanotify_mark)(int fanotify_fd, unsigned int flags,
|
|
ret = -EINVAL;
|
|
ret = -EINVAL;
|
|
if (unlikely(filp->f_op != &fanotify_fops))
|
|
if (unlikely(filp->f_op != &fanotify_fops))
|
|
goto fput_and_out;
|
|
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);
|
|
ret = fanotify_find_path(dfd, pathname, &path, flags);
|
|
if (ret)
|
|
if (ret)
|
|
@@ -729,7 +812,6 @@ SYSCALL_DEFINE(fanotify_mark)(int fanotify_fd, unsigned int flags,
|
|
inode = path.dentry->d_inode;
|
|
inode = path.dentry->d_inode;
|
|
else
|
|
else
|
|
mnt = path.mnt;
|
|
mnt = path.mnt;
|
|
- group = filp->private_data;
|
|
|
|
|
|
|
|
/* create/update an inode mark */
|
|
/* create/update an inode mark */
|
|
switch (flags & (FAN_MARK_ADD | FAN_MARK_REMOVE | FAN_MARK_FLUSH)) {
|
|
switch (flags & (FAN_MARK_ADD | FAN_MARK_REMOVE | FAN_MARK_FLUSH)) {
|