// SPDX-License-Identifier: GPL-2.0
#include <linux/fanotify.h>
#include <linux/fdtable.h>
#include <linux/fsnotify_backend.h>
#include <linux/init.h>
#include <linux/jiffies.h>
#include <linux/kernel.h> /* UINT_MAX */
#include <linux/mount.h>
#include <linux/sched.h>
#include <linux/sched/user.h>
#include <linux/sched/signal.h>
#include <linux/types.h>
#include <linux/wait.h>
#include <linux/audit.h>
#include <linux/sched/mm.h>
#include <linux/statfs.h>
#include <linux/stringhash.h>
#include "fanotify.h"
static bool fanotify_path_equal(const struct path *p1, const struct path *p2)
{
return p1->mnt == p2->mnt && p1->dentry == p2->dentry;
}
static unsigned int fanotify_hash_path(const struct path *path)
{
return hash_ptr(path->dentry, FANOTIFY_EVENT_HASH_BITS) ^
hash_ptr(path->mnt, FANOTIFY_EVENT_HASH_BITS);
}
static unsigned int fanotify_hash_fsid(__kernel_fsid_t *fsid)
{
return hash_32(fsid->val[0], FANOTIFY_EVENT_HASH_BITS) ^
hash_32(fsid->val[1], FANOTIFY_EVENT_HASH_BITS);
}
static bool fanotify_fh_equal(struct fanotify_fh *fh1,
struct fanotify_fh *fh2)
{
if (fh1->type != fh2->type || fh1->len != fh2->len)
return false;
return !fh1->len ||
!memcmp(fanotify_fh_buf(fh1), fanotify_fh_buf(fh2), fh1->len);
}
static unsigned int fanotify_hash_fh(struct fanotify_fh *fh)
{
long salt = (long)fh->type | (long)fh->len << 8;
/*
* full_name_hash() works long by long, so it handles fh buf optimally.
*/
return full_name_hash((void *)salt, fanotify_fh_buf(fh), fh->len);
}
static bool fanotify_fid_event_equal(struct fanotify_fid_event *ffe1,
struct fanotify_fid_event *ffe2)
{
/* Do not merge fid events without object fh */
if (!ffe1->object_fh.len)
return false;
return fanotify_fsid_equal(&ffe1->fsid, &ffe2->fsid) &&
fanotify_fh_equal(&ffe1->object_fh, &ffe2->object_fh);
}
static bool fanotify_info_equal(struct fanotify_info *info1,
struct fanotify_info *info2)
{
if (info1->dir_fh_totlen != info2->dir_fh_totlen ||
info1->dir2_fh_totlen != info2->dir2_fh_totlen ||
info1->file_fh_totlen != info2->file_fh_totlen ||
info1->name_len != info2->name_len ||
info1->name2_len != info2->name2_len)
return false;
if (info1->dir_fh_totlen &&
!fanotify_fh_equal(fanotify_info_dir_fh(info1),
fanotify_info_dir_fh(info2)))
return false;
if (info1->dir2_fh_totlen &&
!fanotify_fh_equal(fanotify_info_dir2_fh(info1),
fanotify_info_dir2_fh(info2)))
return false;
if (info1->file_fh_totlen &&
!fanotify_fh_equal(fanotify_info_file_fh(info1),
fanotify_info_file_fh(info2)))
return false;
if (info1->name_len &&
memcmp(fanotify_info_name(info1), fanotify_info_name(info2),
info1->name_len))
return false;
return !info1->name2_len ||
!memcmp(fanotify_info_name2(info1), fanotify_info_name2(info2),
info1->name2_len);
}
static bool fanotify_name_event_equal(struct fanotify_name_event *fne1,
struct fanotify_name_event *fne2)
{
struct fanotify_info *info1 = &fne1->info;
struct fanotify_info *info2 = &fne2->info;
/* Do not merge name events without dir fh */
if (!info1->dir_fh_totlen)
return false;
if (!fanotify_fsid_equal(&fne1->fsid, &fne2->fsid))
return false;
return fanotify_info_equal(info1, info2);
}
static bool fanotify_error_event_equal(struct fanotify_error_event *fee1,
struct fanotify_error_event *fee2)
{
/* Error events against the same file system are always merged. */
if (!fanotify_fsid_equal(&fee1->fsid, &fee2->fsid))
return false;
return true;
}
static bool fanotify_should_merge(struct fanotify_event *old,
struct fanotify_event *new)
{
pr_debug("%s: old=%p new=%p\n", __func__, old, new);
if (old->hash != new->hash ||
old->type != new->type || old->pid != new->pid)
return false;
/*
* We want to merge many dirent events in the same dir (i.e.
* creates/unlinks/renames), but we do not want to merge dirent
* events referring to subdirs with dirent events referring to
* non subdirs, otherwise, user won't be able to tell from a
* mask FAN_CREATE|FAN_DELETE|FAN_ONDIR if it describes mkdir+
* unlink pair or rmdir+create pair of events.
*/
if ((old->mask & FS_ISDIR) != (new->mask & FS_ISDIR))
return false;
/*
* FAN_RENAME event is reported with special info record types,
* so we cannot merge it with other events.
*/
if ((old->mask & FAN_RENAME) != (