diff --git a/fs/notify/fanotify/af_fanotify.c b/fs/notify/fanotify/af_fanotify.c
index ac6aee1..cefd108 100644
--- a/fs/notify/fanotify/af_fanotify.c
+++ b/fs/notify/fanotify/af_fanotify.c
@@ -2,6 +2,7 @@
#include <linux/fdtable.h>
#include <linux/file.h>
#include <linux/fsnotify_backend.h>
+#include <linux/ima.h> /* ima_path_check */
#include <linux/init.h>
#include <linux/kernel.h> /* UINT_MAX */
#include <linux/mount.h> /* mntget() */
@@ -16,6 +17,8 @@
#include "fanotify.h"
#include "af_fanotify.h"
+#include <asm/ioctls.h>
+
static const struct proto_ops fanotify_proto_ops;
static struct kmem_cache *fanotify_mark_cache __read_mostly;
@@ -114,6 +117,36 @@ static int fan_bind(struct socket *sock, struct sockaddr *addr, int addr_len)
return 0;
}
+static int fan_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
+{
+ struct fanotify_sock *fan_sock;
+ struct fsnotify_group *group;
+ struct fsnotify_event_holder *holder;
+ void __user *p;
+ int ret = -ENOTTY;
+ size_t send_len = 0;
+
+ if (sock->state != SS_CONNECTED)
+ return -EBADF;
+
+ fan_sock = fan_sk(sock->sk);
+ group = fan_sock->group;
+
+ p = (void __user *) arg;
+
+ switch (cmd) {
+ case FIONREAD:
+ mutex_lock(&group->notification_mutex);
+ list_for_each_entry(holder, &group->notification_list, event_list)
+ send_len += FAN_EVENT_METADATA_LEN;
+ mutex_unlock(&group->notification_mutex);
+ ret = put_user(send_len, (int __user *) p);
+ break;
+ }
+
+ return ret;
+}
+
static void fanotify_free_mark(struct fsnotify_mark_entry *entry)
{
kmem_cache_free(fanotify_mark_cache, entry);@@ -278,6 +311,199 @@ static int fan_setsockopt(struct socket *sock, int level, int optname,
return ret;
}
+/*
+ * Get an fsnotify notification event if one exists and is small
+ * enough to fit in "count". Return an error pointer if the count
+ * is not large enough.
+ *
+ * Called with the group->notification_mutex held.
+ */
+static struct fsnotify_event *get_one_event(struct fsnotify_group *group,
+ size_t count)
+{
+ BUG_ON(!mutex_is_locked(&group->notification_mutex));
+
+ if (fsnotify_notify_queue_is_empty(group))
+ return NULL;
+
+ if (FAN_EVENT_METADATA_LEN > count)
+ return ERR_PTR(-EINVAL);
+
+ /* held the notification_mutex the whole time, so this is the
+ * same event we peeked above */
+ return fsnotify_remove_notify_event(group);
+}
+
+static int create_and_fill_fd(struct fsnotify_group *group,
+ struct fanotify_event_metadata *metadata,
+ struct fsnotify_event *event)
+{
+ int client_fd, err;
+ struct dentry *dentry;
+ struct vfsmount *mnt;
+ struct file *new_file;
+
+ client_fd = get_unused_fd();
+ if (client_fd < 0)
+ return client_fd;
+
+ if (event->data_type != FSNOTIFY_EVENT_PATH) {
+ 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);
+ /* 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) {
+ err = ima_path_check(&event->path, MAY_READ, IMA_COUNT_UPDATE);
+ if (err)
+ new_file = ERR_PTR(err);
+ else {
+ current->flags |= PF_NONOTIFY;
+ new_file = dentry_open(dentry, mnt, O_RDONLY | O_LARGEFILE,
+ current_cred());
+ current->flags &= ~PF_NONOTIFY;
+ }
+ } else
+ new_file = ERR_PTR(-EOVERFLOW);
+ if (IS_ERR(new_file)) {
+ /*
+ * we still send an event even if we can't open the file. this
+ * can happen when say tasks are gone and we try to open their
+ * /proc entries or we try to open a WRONLY file like in sysfs
+ * we just send the errno to userspace since there isn't much
+ * else we can do.
+ */
+ put_unused_fd(client_fd);
+ client_fd = PTR_ERR(new_file);
+ } else {
+ new_file->f_mode |= FMODE_NONOTIFY;
+ fd_install(client_fd, new_file);
+ }
+
+ metadata->fd = client_fd;
+
+ return 0;
+}
+
+static ssize_t fill_event_metadata(struct fsnotify_group *group,
+ struct fanotify_event_metadata *metadata,
+ struct fsnotify_event *event)
+{
+ pr_debug("%s: \n", __func__);
+
+ metadata->event_len = FAN_EVENT_METADATA_LEN;
+ metadata->mask = fanotify_outgoing_mask(event->mask);
+
+ return create_and_fill_fd(group, metadata, event);
+
+}
+
+static ssize_t copy_event_to_iov(struct fsnotify_group *group,
+ struct fsnotify_event *event,
+ struct iovec *iov)
+{
+ struct fanotify_event_metadata fanotify_event_metadata;
+ int ret;
+
+ pr_debug("%s: \n", __func__);
+
+ ret = fill_event_metadata(group, &fanotify_event_metadata, event);
+ if (ret)
+ return ret;
+
+ /* send the main event */
+ ret = memcpy_toiovec(iov, (unsigned char *)&fanotify_event_metadata,
+ FAN_EVENT_METADATA_LEN);
+ if (ret < 0)
+ return ret;
+
+ return FAN_EVENT_METADATA_LEN;
+}
+
+static ssize_t fan_recv_events(struct fsnotify_group *group, struct msghdr *msg,
+ int count, int nonblock)
+{
+ struct fsnotify_event *event;
+ int ret, len_sent = 0;
+ DEFINE_WAIT(wait);
+
+ pr_debug("%s: \n", __func__);
+
+ while (1) {
+ prepare_to_wait(&group->notification_waitq, &wait, TASK_INTERRUPTIBLE);
+
+ mutex_lock(&group->notification_mutex);
+ event = get_one_event(group, count);
+ mutex_unlock(&group->notification_mutex);
+
+ if (event) {
+ ret = PTR_ERR(event);
+ if (IS_ERR(event))
+ break;
+
+ ret = copy_event_to_iov(group, event, msg->msg_iov);
+ fsnotify_put_event(event);
+ if (ret < 0)
+ break;
+ len_sent += ret;
+ count -= ret;
+ continue;
+ }
+
+ ret = -EAGAIN;
+ if (nonblock)
+ break;
+ ret = -EINTR;
+ if (signal_pending(current))
+ break;
+
+ if (len_sent)
+ break;
+
+ schedule();
+ }
+
+ finish_wait(&group->notification_waitq, &wait);
+ if (len_sent && ret != -EFAULT)
+ ret = len_sent;
+ return ret;
+}
+
+static int fan_recvmsg(struct kiocb *iocb, struct socket *sock,
+ struct msghdr *msg, size_t size, int flags)
+{
+ struct fanotify_sock *fan_sock;
+ struct fsnotify_group *group;
+ int nonblock;
+
+ pr_debug("%s: \n", __func__);
+
+ if (sock->state != SS_CONNECTED)
+ return -EBADF;
+
+ if (size < FAN_EVENT_METADATA_LEN)
+ return -ENOMEM;
+
+ fan_sock = fan_sk(sock->sk);
+ group = fan_sock->group;
+
+ /* hey, nonblock no matter how they ask */
+ nonblock = !!(sock->file->f_flags & O_NONBLOCK);
+ nonblock |= !!(flags & MSG_DONTWAIT);
+
+ size = fan_recv_events(group, msg, size, nonblock);
+
+ return size;
+}
+
static const struct net_proto_family fanotify_family_ops = {
.family = PF_FANOTIFY,
.create = fan_sock_create,@@ -294,13 +520,13 @@ static const struct proto_ops fanotify_proto_ops = {
.accept = sock_no_accept,
.getname = sock_no_getname,
.poll = sock_no_poll,
- .ioctl = sock_no_ioctl,
+ .ioctl = fan_ioctl,
.listen = sock_no_listen,
.shutdown = sock_no_shutdown,
.setsockopt = fan_setsockopt,
.getsockopt = sock_no_getsockopt,
.sendmsg = sock_no_sendmsg,
- .recvmsg = sock_no_recvmsg,
+ .recvmsg = fan_recvmsg,
.mmap = sock_no_mmap,
.sendpage = sock_no_sendpage,
};diff --git a/fs/notify/fanotify/fanotify.h b/fs/notify/fanotify/fanotify.h
index 6c7bf06..4a5c785 100644
--- a/fs/notify/fanotify/fanotify.h
+++ b/fs/notify/fanotify/fanotify.h
@@ -12,3 +12,8 @@ static inline bool fanotify_is_mask_valid(__u32 mask)
return false;
return true;
}
+
+static inline __u32 fanotify_outgoing_mask(__u32 mask)
+{
+ return mask & FAN_ALL_OUTGOING_EVENTS;
+}diff --git a/include/linux/fanotify.h b/include/linux/fanotify.h
index db96dd8..f44a668 100644
--- a/include/linux/fanotify.h
+++ b/include/linux/fanotify.h
@@ -35,6 +35,10 @@
*/
#define FAN_ALL_INCOMING_EVENTS (FAN_ALL_EVENTS |\
FAN_EVENT_ON_CHILD)
+
+#define FAN_ALL_OUTGOING_EVENTS (FAN_ALL_EVENTS |\
+ FAN_Q_OVERFLOW)
+
#ifndef SOL_FANOTIFY
#define SOL_FANOTIFY 278
#endif
@@ -63,6 +67,24 @@ struct fanotify_so_inode_mark {
#define FANOTIFY_SET_MARK 1
#define FANOTIFY_REMOVE_MARK 2
+struct fanotify_event_metadata {
+ __u32 event_len;
+ __s32 fd;
+ __u32 mask;
+} __attribute__((packed));
+
+
+/* Helper functions to deal with fanotify_event_metadata buffers */
+#define FAN_EVENT_METADATA_LEN (sizeof(struct fanotify_event_metadata))
+
+#define FAN_EVENT_NEXT(meta, len) ((len) -= (meta)->event_len, \
+ (struct fanotify_event_metadata*)(((char *)(meta)) + \
+ (meta)->event_len))
+
+#define FAN_EVENT_OK(meta, len) ((long)(len) >= (long)FAN_EVENT_METADATA_LEN && \
+ (long)(meta)->event_len >= (long)FAN_EVENT_METADATA_LEN && \
+ (long)(meta)->event_len <= (long)(len))
+
#ifdef __KERNEL__
#endif /* __KERNEL__ */