summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--fs/notify/fsnotify.c47
-rw-r--r--include/linux/fsnotify.h19
-rw-r--r--include/linux/fsnotify_backend.h15
-rw-r--r--include/linux/security.h1
-rw-r--r--security/security.c16
5 files changed, 68 insertions, 30 deletions
diff --git a/fs/notify/fsnotify.c b/fs/notify/fsnotify.c
index c5adf833bf6a..668268627894 100644
--- a/fs/notify/fsnotify.c
+++ b/fs/notify/fsnotify.c
@@ -169,27 +169,22 @@ void __fsnotify_flush_ignored_mask(struct inode *inode, void *data, int data_is)
}
}
-static void send_to_group(struct fsnotify_group *group, struct inode *to_tell,
- struct vfsmount *mnt, __u32 mask, void *data,
- int data_is, u32 cookie, const unsigned char *file_name,
- struct fsnotify_event **event)
+static int send_to_group(struct fsnotify_group *group, struct inode *to_tell,
+ struct vfsmount *mnt, __u32 mask, void *data,
+ int data_is, u32 cookie, const unsigned char *file_name,
+ struct fsnotify_event **event)
{
if (!group->ops->should_send_event(group, to_tell, mnt, mask,
data, data_is))
- return;
+ return 0;
if (!*event) {
*event = fsnotify_create_event(to_tell, mask, data,
data_is, file_name,
cookie, GFP_KERNEL);
- /*
- * shit, we OOM'd and now we can't tell, maybe
- * someday someone else will want to do something
- * here
- */
if (!*event)
- return;
+ return -ENOMEM;
}
- group->ops->handle_event(group, *event);
+ return group->ops->handle_event(group, *event);
}
static bool needed_by_vfsmount(__u32 test_mask, struct vfsmount *mnt)
@@ -206,20 +201,20 @@ static bool needed_by_vfsmount(__u32 test_mask, struct vfsmount *mnt)
* out to all of the registered fsnotify_group. Those groups can then use the
* notification event in whatever means they feel necessary.
*/
-void fsnotify(struct inode *to_tell, __u32 mask, void *data, int data_is,
- const unsigned char *file_name, u32 cookie)
+int fsnotify(struct inode *to_tell, __u32 mask, void *data, int data_is,
+ const unsigned char *file_name, u32 cookie)
{
struct fsnotify_group *group;
struct fsnotify_event *event = NULL;
struct vfsmount *mnt = NULL;
- int idx;
+ int idx, ret = 0;
/* global tests shouldn't care about events on child only the specific event */
__u32 test_mask = (mask & ~FS_EVENT_ON_CHILD);
/* if no fsnotify listeners, nothing to do */
if (list_empty(&fsnotify_inode_groups) &&
list_empty(&fsnotify_vfsmount_groups))
- return;
+ return 0;
if (mask & FS_MODIFY)
__fsnotify_flush_ignored_mask(to_tell, data, data_is);
@@ -227,7 +222,7 @@ void fsnotify(struct inode *to_tell, __u32 mask, void *data, int data_is,
/* if none of the directed listeners or vfsmount listeners care */
if (!(test_mask & fsnotify_inode_mask) &&
!(test_mask & fsnotify_vfsmount_mask))
- return;
+ return 0;
if (data_is == FSNOTIFY_EVENT_PATH)
mnt = ((struct path *)data)->mnt;
@@ -236,7 +231,7 @@ void fsnotify(struct inode *to_tell, __u32 mask, void *data, int data_is,
* listeners list cares, nothing to do */
if (!(test_mask & to_tell->i_fsnotify_mask) &&
!needed_by_vfsmount(test_mask, mnt))
- return;
+ return 0;
/*
* SRCU!! the groups list is very very much read only and the path is
@@ -248,20 +243,24 @@ void fsnotify(struct inode *to_tell, __u32 mask, void *data, int data_is,
if (test_mask & to_tell->i_fsnotify_mask) {
list_for_each_entry_rcu(group, &fsnotify_inode_groups, inode_group_list) {
if (test_mask & group->mask) {
- send_to_group(group, to_tell, NULL, mask, data, data_is,
- cookie, file_name, &event);
+ ret = send_to_group(group, to_tell, NULL, mask, data, data_is,
+ cookie, file_name, &event);
+ if (ret)
+ goto out;
}
}
}
if (needed_by_vfsmount(test_mask, mnt)) {
list_for_each_entry_rcu(group, &fsnotify_vfsmount_groups, vfsmount_group_list) {
if (test_mask & group->mask) {
- send_to_group(group, to_tell, mnt, mask, data, data_is,
- cookie, file_name, &event);
+ ret = send_to_group(group, to_tell, mnt, mask, data, data_is,
+ cookie, file_name, &event);
+ if (ret)
+ goto out;
}
}
}
-
+out:
srcu_read_unlock(&fsnotify_grp_srcu, idx);
/*
* fsnotify_create_event() took a reference so the event can't be cleaned
@@ -269,6 +268,8 @@ void fsnotify(struct inode *to_tell, __u32 mask, void *data, int data_is,
*/
if (event)
fsnotify_put_event(event);
+
+ return 0;
}
EXPORT_SYMBOL_GPL(fsnotify);
diff --git a/include/linux/fsnotify.h b/include/linux/fsnotify.h
index b8cf161f5a6d..64efda9aae62 100644
--- a/include/linux/fsnotify.h
+++ b/include/linux/fsnotify.h
@@ -34,6 +34,25 @@ static inline void fsnotify_parent(struct path *path, struct dentry *dentry, __u
__fsnotify_parent(path, dentry, mask);
}
+/* simple call site for access decisions */
+static inline int fsnotify_perm(struct file *file, int mask)
+{
+ struct path *path = &file->f_path;
+ struct inode *inode = path->dentry->d_inode;
+ __u32 fsnotify_mask;
+
+ if (file->f_mode & FMODE_NONOTIFY)
+ return 0;
+ if (!(mask & (MAY_READ | MAY_OPEN)))
+ return 0;
+ if (mask & MAY_READ)
+ fsnotify_mask = FS_ACCESS_PERM;
+ if (mask & MAY_OPEN)
+ fsnotify_mask = FS_OPEN_PERM;
+
+ return fsnotify(inode, fsnotify_mask, path, FSNOTIFY_EVENT_PATH, NULL, 0);
+}
+
/*
* fsnotify_d_move - dentry has been moved
* Called with dcache_lock and dentry->d_lock held.
diff --git a/include/linux/fsnotify_backend.h b/include/linux/fsnotify_backend.h
index efe9ba321cf2..c34728e7d8cb 100644
--- a/include/linux/fsnotify_backend.h
+++ b/include/linux/fsnotify_backend.h
@@ -41,6 +41,9 @@
#define FS_Q_OVERFLOW 0x00004000 /* Event queued overflowed */
#define FS_IN_IGNORED 0x00008000 /* last inotify event here */
+#define FS_OPEN_PERM 0x00010000 /* open event in an permission hook */
+#define FS_ACCESS_PERM 0x00020000 /* access event in a permissions hook */
+
#define FS_IN_ISDIR 0x40000000 /* event occurred against dir */
#define FS_IN_ONESHOT 0x80000000 /* only send event once */
@@ -282,8 +285,8 @@ struct fsnotify_mark {
/* called from the vfs helpers */
/* main fsnotify call to send events */
-extern void fsnotify(struct inode *to_tell, __u32 mask, void *data, int data_is,
- const unsigned char *name, u32 cookie);
+extern int fsnotify(struct inode *to_tell, __u32 mask, void *data, int data_is,
+ const unsigned char *name, u32 cookie);
extern void __fsnotify_parent(struct path *path, struct dentry *dentry, __u32 mask);
extern void __fsnotify_inode_delete(struct inode *inode);
extern void __fsnotify_vfsmount_delete(struct vfsmount *mnt);
@@ -413,9 +416,11 @@ extern int fsnotify_replace_event(struct fsnotify_event_holder *old_holder,
#else
-static inline void fsnotify(struct inode *to_tell, __u32 mask, void *data, int data_is,
- const unsigned char *name, u32 cookie)
-{}
+static inline int fsnotify(struct inode *to_tell, __u32 mask, void *data, int data_is,
+ const unsigned char *name, u32 cookie)
+{
+ return 0;
+}
static inline void __fsnotify_parent(struct path *path, struct dentry *dentry, __u32 mask)
{}
diff --git a/include/linux/security.h b/include/linux/security.h
index 0c8819170463..24fc29540aa3 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -23,6 +23,7 @@
#define __LINUX_SECURITY_H
#include <linux/fs.h>
+#include <linux/fsnotify.h>
#include <linux/binfmts.h>
#include <linux/signal.h>
#include <linux/resource.h>
diff --git a/security/security.c b/security/security.c
index 351942a4ca0e..f6ac27cd3452 100644
--- a/security/security.c
+++ b/security/security.c
@@ -620,7 +620,13 @@ void security_inode_getsecid(const struct inode *inode, u32 *secid)
int security_file_permission(struct file *file, int mask)
{
- return security_ops->file_permission(file, mask);
+ int ret;
+
+ ret = security_ops->file_permission(file, mask);
+ if (ret)
+ return ret;
+
+ return fsnotify_perm(file, mask);
}
int security_file_alloc(struct file *file)
@@ -684,7 +690,13 @@ int security_file_receive(struct file *file)
int security_dentry_open(struct file *file, const struct cred *cred)
{
- return security_ops->dentry_open(file, cred);
+ int ret;
+
+ ret = security_ops->dentry_open(file, cred);
+ if (ret)
+ return ret;
+
+ return fsnotify_perm(file, MAY_OPEN);
}
int security_task_create(unsigned long clone_flags)