2009-12-17 21:24:29 -05:00
# include <linux/fanotify.h>
2009-12-17 21:24:25 -05:00
# include <linux/fdtable.h>
# include <linux/fsnotify_backend.h>
# include <linux/init.h>
2009-12-17 21:24:34 -05:00
# include <linux/jiffies.h>
2009-12-17 21:24:25 -05:00
# include <linux/kernel.h> /* UINT_MAX */
2009-12-17 21:24:28 -05:00
# include <linux/mount.h>
2009-12-17 21:24:34 -05:00
# include <linux/sched.h>
2017-02-02 17:54:15 +01:00
# include <linux/sched/user.h>
2009-12-17 21:24:25 -05:00
# include <linux/types.h>
2009-12-17 21:24:34 -05:00
# include <linux/wait.h>
2009-12-17 21:24:25 -05:00
2014-01-21 15:48:14 -08:00
# include "fanotify.h"
static bool should_merge ( struct fsnotify_event * old_fsn ,
struct fsnotify_event * new_fsn )
2009-12-17 21:24:25 -05:00
{
2014-01-21 15:48:14 -08:00
struct fanotify_event_info * old , * new ;
2009-12-17 21:24:25 -05:00
2014-01-21 15:48:14 -08:00
pr_debug ( " %s: old=%p new=%p \n " , __func__ , old_fsn , new_fsn ) ;
old = FANOTIFY_E ( old_fsn ) ;
new = FANOTIFY_E ( new_fsn ) ;
if ( old_fsn - > inode = = new_fsn - > inode & & old - > tgid = = new - > tgid & &
old - > path . mnt = = new - > path . mnt & &
old - > path . dentry = = new - > path . dentry )
return true ;
2009-12-17 21:24:25 -05:00
return false ;
}
2010-07-28 10:18:37 -04:00
/* and the list better be locked by something too! */
2014-01-28 18:53:22 +01:00
static int fanotify_merge ( struct list_head * list , struct fsnotify_event * event )
2009-12-17 21:24:25 -05:00
{
2014-01-21 15:48:14 -08:00
struct fsnotify_event * test_event ;
2009-12-17 21:24:25 -05:00
pr_debug ( " %s: list=%p event=%p \n " , __func__ , list , event ) ;
2014-01-28 18:29:24 +01:00
# ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS
/*
* Don ' t merge a permission event with any other event so that we know
* the event structure we have created in fanotify_handle_event ( ) is the
* one we should check for permission response .
*/
if ( event - > mask & FAN_ALL_PERM_EVENTS )
2014-01-28 18:53:22 +01:00
return 0 ;
2014-01-28 18:29:24 +01:00
# endif
2014-01-21 15:48:14 -08:00
list_for_each_entry_reverse ( test_event , list , list ) {
if ( should_merge ( test_event , event ) ) {
2017-02-09 20:45:22 +08:00
test_event - > mask | = event - > mask ;
return 1 ;
2009-12-17 21:24:25 -05:00
}
}
2010-07-28 10:18:37 -04:00
2017-02-09 20:45:22 +08:00
return 0 ;
2009-12-17 21:24:25 -05:00
}
2009-12-17 21:24:34 -05:00
# ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS
2014-04-03 14:46:33 -07:00
static int fanotify_get_response ( struct fsnotify_group * group ,
2016-11-10 17:45:16 +01:00
struct fanotify_perm_event_info * event ,
struct fsnotify_iter_info * iter_info )
2009-12-17 21:24:34 -05:00
{
int ret ;
pr_debug ( " %s: group=%p event=%p \n " , __func__ , group , event ) ;
2016-11-10 17:45:16 +01:00
/*
* fsnotify_prepare_user_wait ( ) fails if we race with mark deletion .
* Just let the operation pass in that case .
*/
if ( ! fsnotify_prepare_user_wait ( iter_info ) ) {
event - > response = FAN_ALLOW ;
goto out ;
}
2016-09-19 14:44:30 -07:00
wait_event ( group - > fanotify_data . access_waitq , event - > response ) ;
2009-12-17 21:24:34 -05:00
2016-11-10 17:45:16 +01:00
fsnotify_finish_user_wait ( iter_info ) ;
out :
2009-12-17 21:24:34 -05:00
/* userspace responded, convert to something usable */
switch ( event - > response ) {
case FAN_ALLOW :
ret = 0 ;
break ;
case FAN_DENY :
default :
ret = - EPERM ;
}
event - > response = 0 ;
2009-12-17 21:24:34 -05:00
pr_debug ( " %s: group=%p event=%p about to return ret=%d \n " , __func__ ,
group , event , ret ) ;
2009-12-17 21:24:34 -05:00
return ret ;
}
# endif
2014-01-21 15:48:15 -08:00
static bool fanotify_should_send_event ( struct fsnotify_mark * inode_mark ,
2010-07-28 10:18:39 -04:00
struct fsnotify_mark * vfsmnt_mark ,
2014-01-21 15:48:15 -08:00
u32 event_mask ,
2016-11-20 20:19:09 -05:00
const void * data , int data_type )
2009-12-17 21:24:28 -05:00
{
2010-07-28 10:18:39 -04:00
__u32 marks_mask , marks_ignored_mask ;
2016-11-20 20:19:09 -05:00
const struct path * path = data ;
2010-07-28 10:18:39 -04:00
2014-01-21 15:48:15 -08:00
pr_debug ( " %s: inode_mark=%p vfsmnt_mark=%p mask=%x data=%p "
" data_type=%d \n " , __func__ , inode_mark , vfsmnt_mark ,
event_mask , data , data_type ) ;
2010-07-28 10:18:39 -04:00
2009-12-17 21:24:28 -05:00
/* if we don't have enough info to send an event to userspace say no */
2010-08-12 14:23:04 -07:00
if ( data_type ! = FSNOTIFY_EVENT_PATH )
2009-12-17 21:24:28 -05:00
return false ;
2010-10-28 17:21:58 -04:00
/* sorry, fanotify only gives a damn about files and dirs */
VFS: (Scripted) Convert S_ISLNK/DIR/REG(dentry->d_inode) to d_is_*(dentry)
Convert the following where appropriate:
(1) S_ISLNK(dentry->d_inode) to d_is_symlink(dentry).
(2) S_ISREG(dentry->d_inode) to d_is_reg(dentry).
(3) S_ISDIR(dentry->d_inode) to d_is_dir(dentry). This is actually more
complicated than it appears as some calls should be converted to
d_can_lookup() instead. The difference is whether the directory in
question is a real dir with a ->lookup op or whether it's a fake dir with
a ->d_automount op.
In some circumstances, we can subsume checks for dentry->d_inode not being
NULL into this, provided we the code isn't in a filesystem that expects
d_inode to be NULL if the dirent really *is* negative (ie. if we're going to
use d_inode() rather than d_backing_inode() to get the inode pointer).
Note that the dentry type field may be set to something other than
DCACHE_MISS_TYPE when d_inode is NULL in the case of unionmount, where the VFS
manages the fall-through from a negative dentry to a lower layer. In such a
case, the dentry type of the negative union dentry is set to the same as the
type of the lower dentry.
However, if you know d_inode is not NULL at the call site, then you can use
the d_is_xxx() functions even in a filesystem.
There is one further complication: a 0,0 chardev dentry may be labelled
DCACHE_WHITEOUT_TYPE rather than DCACHE_SPECIAL_TYPE. Strictly, this was
intended for special directory entry types that don't have attached inodes.
The following perl+coccinelle script was used:
use strict;
my @callers;
open($fd, 'git grep -l \'S_IS[A-Z].*->d_inode\' |') ||
die "Can't grep for S_ISDIR and co. callers";
@callers = <$fd>;
close($fd);
unless (@callers) {
print "No matches\n";
exit(0);
}
my @cocci = (
'@@',
'expression E;',
'@@',
'',
'- S_ISLNK(E->d_inode->i_mode)',
'+ d_is_symlink(E)',
'',
'@@',
'expression E;',
'@@',
'',
'- S_ISDIR(E->d_inode->i_mode)',
'+ d_is_dir(E)',
'',
'@@',
'expression E;',
'@@',
'',
'- S_ISREG(E->d_inode->i_mode)',
'+ d_is_reg(E)' );
my $coccifile = "tmp.sp.cocci";
open($fd, ">$coccifile") || die $coccifile;
print($fd "$_\n") || die $coccifile foreach (@cocci);
close($fd);
foreach my $file (@callers) {
chomp $file;
print "Processing ", $file, "\n";
system("spatch", "--sp-file", $coccifile, $file, "--in-place", "--no-show-diff") == 0 ||
die "spatch failed";
}
[AV: overlayfs parts skipped]
Signed-off-by: David Howells <dhowells@redhat.com>
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
2015-01-29 12:02:35 +00:00
if ( ! d_is_reg ( path - > dentry ) & &
2015-01-29 12:02:36 +00:00
! d_can_lookup ( path - > dentry ) )
2010-10-28 17:21:58 -04:00
return false ;
2010-07-28 10:18:39 -04:00
if ( inode_mark & & vfsmnt_mark ) {
marks_mask = ( vfsmnt_mark - > mask | inode_mark - > mask ) ;
marks_ignored_mask = ( vfsmnt_mark - > ignored_mask | inode_mark - > ignored_mask ) ;
} else if ( inode_mark ) {
/*
* if the event is for a child and this inode doesn ' t care about
* events on the child , don ' t send it !
*/
if ( ( event_mask & FS_EVENT_ON_CHILD ) & &
! ( inode_mark - > mask & FS_EVENT_ON_CHILD ) )
return false ;
marks_mask = inode_mark - > mask ;
marks_ignored_mask = inode_mark - > ignored_mask ;
} else if ( vfsmnt_mark ) {
marks_mask = vfsmnt_mark - > mask ;
marks_ignored_mask = vfsmnt_mark - > ignored_mask ;
} else {
BUG ( ) ;
}
VFS: (Scripted) Convert S_ISLNK/DIR/REG(dentry->d_inode) to d_is_*(dentry)
Convert the following where appropriate:
(1) S_ISLNK(dentry->d_inode) to d_is_symlink(dentry).
(2) S_ISREG(dentry->d_inode) to d_is_reg(dentry).
(3) S_ISDIR(dentry->d_inode) to d_is_dir(dentry). This is actually more
complicated than it appears as some calls should be converted to
d_can_lookup() instead. The difference is whether the directory in
question is a real dir with a ->lookup op or whether it's a fake dir with
a ->d_automount op.
In some circumstances, we can subsume checks for dentry->d_inode not being
NULL into this, provided we the code isn't in a filesystem that expects
d_inode to be NULL if the dirent really *is* negative (ie. if we're going to
use d_inode() rather than d_backing_inode() to get the inode pointer).
Note that the dentry type field may be set to something other than
DCACHE_MISS_TYPE when d_inode is NULL in the case of unionmount, where the VFS
manages the fall-through from a negative dentry to a lower layer. In such a
case, the dentry type of the negative union dentry is set to the same as the
type of the lower dentry.
However, if you know d_inode is not NULL at the call site, then you can use
the d_is_xxx() functions even in a filesystem.
There is one further complication: a 0,0 chardev dentry may be labelled
DCACHE_WHITEOUT_TYPE rather than DCACHE_SPECIAL_TYPE. Strictly, this was
intended for special directory entry types that don't have attached inodes.
The following perl+coccinelle script was used:
use strict;
my @callers;
open($fd, 'git grep -l \'S_IS[A-Z].*->d_inode\' |') ||
die "Can't grep for S_ISDIR and co. callers";
@callers = <$fd>;
close($fd);
unless (@callers) {
print "No matches\n";
exit(0);
}
my @cocci = (
'@@',
'expression E;',
'@@',
'',
'- S_ISLNK(E->d_inode->i_mode)',
'+ d_is_symlink(E)',
'',
'@@',
'expression E;',
'@@',
'',
'- S_ISDIR(E->d_inode->i_mode)',
'+ d_is_dir(E)',
'',
'@@',
'expression E;',
'@@',
'',
'- S_ISREG(E->d_inode->i_mode)',
'+ d_is_reg(E)' );
my $coccifile = "tmp.sp.cocci";
open($fd, ">$coccifile") || die $coccifile;
print($fd "$_\n") || die $coccifile foreach (@cocci);
close($fd);
foreach my $file (@callers) {
chomp $file;
print "Processing ", $file, "\n";
system("spatch", "--sp-file", $coccifile, $file, "--in-place", "--no-show-diff") == 0 ||
die "spatch failed";
}
[AV: overlayfs parts skipped]
Signed-off-by: David Howells <dhowells@redhat.com>
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
2015-01-29 12:02:35 +00:00
if ( d_is_dir ( path - > dentry ) & &
2015-02-10 14:08:27 -08:00
! ( marks_mask & FS_ISDIR & ~ marks_ignored_mask ) )
2010-10-28 17:21:59 -04:00
return false ;
fanotify: fix event filtering with FAN_ONDIR set
With FAN_ONDIR set, the user can end up getting events, which it hasn't
marked. This was revealed with fanotify04 testcase failure on
Linux-4.0-rc1, and is a regression from 3.19, revealed with 66ba93c0d7fe6
("fanotify: don't set FAN_ONDIR implicitly on a marks ignored mask").
# /opt/ltp/testcases/bin/fanotify04
[ ... ]
fanotify04 7 TPASS : event generated properly for type 100000
fanotify04 8 TFAIL : fanotify04.c:147: got unexpected event 30
fanotify04 9 TPASS : No event as expected
The testcase sets the adds the following marks : FAN_OPEN | FAN_ONDIR for
a fanotify on a dir. Then does an open(), followed by close() of the
directory and expects to see an event FAN_OPEN(0x20). However, the
fanotify returns (FAN_OPEN|FAN_CLOSE_NOWRITE(0x10)). This happens due to
the flaw in the check for event_mask in fanotify_should_send_event() which
does:
if (event_mask & marks_mask & ~marks_ignored_mask)
return true;
where, event_mask == (FAN_ONDIR | FAN_CLOSE_NOWRITE),
marks_mask == (FAN_ONDIR | FAN_OPEN),
marks_ignored_mask == 0
Fix this by masking the outgoing events to the user, as we already take
care of FAN_ONDIR and FAN_EVENT_ON_CHILD.
Signed-off-by: Suzuki K. Poulose <suzuki.poulose@arm.com>
Tested-by: Lino Sanfilippo <LinoSanfilippo@gmx.de>
Reviewed-by: Jan Kara <jack@suse.cz>
Cc: Eric Paris <eparis@redhat.com>
Cc: Will Deacon <will.deacon@arm.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2015-03-12 16:26:08 -07:00
if ( event_mask & FAN_ALL_OUTGOING_EVENTS & marks_mask &
~ marks_ignored_mask )
2010-07-28 10:18:39 -04:00
return true ;
return false ;
2009-12-17 21:24:28 -05:00
}
2014-04-03 14:46:33 -07:00
struct fanotify_event_info * fanotify_alloc_event ( struct inode * inode , u32 mask ,
2016-11-20 20:19:09 -05:00
const struct path * path )
2014-04-03 14:46:33 -07:00
{
struct fanotify_event_info * event ;
# ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS
if ( mask & FAN_ALL_PERM_EVENTS ) {
struct fanotify_perm_event_info * pevent ;
pevent = kmem_cache_alloc ( fanotify_perm_event_cachep ,
GFP_KERNEL ) ;
if ( ! pevent )
return NULL ;
event = & pevent - > fae ;
pevent - > response = 0 ;
goto init ;
}
# endif
event = kmem_cache_alloc ( fanotify_event_cachep , GFP_KERNEL ) ;
if ( ! event )
return NULL ;
init : __maybe_unused
fsnotify_init_event ( & event - > fse , inode , mask ) ;
event - > tgid = get_pid ( task_tgid ( current ) ) ;
if ( path ) {
event - > path = * path ;
path_get ( & event - > path ) ;
} else {
event - > path . mnt = NULL ;
event - > path . dentry = NULL ;
}
return event ;
}
2014-01-21 15:48:14 -08:00
static int fanotify_handle_event ( struct fsnotify_group * group ,
struct inode * inode ,
struct fsnotify_mark * inode_mark ,
struct fsnotify_mark * fanotify_mark ,
2016-11-20 20:19:09 -05:00
u32 mask , const void * data , int data_type ,
2016-11-10 17:51:50 +01:00
const unsigned char * file_name , u32 cookie ,
struct fsnotify_iter_info * iter_info )
2014-01-21 15:48:14 -08:00
{
int ret = 0 ;
struct fanotify_event_info * event ;
struct fsnotify_event * fsn_event ;
BUILD_BUG_ON ( FAN_ACCESS ! = FS_ACCESS ) ;
BUILD_BUG_ON ( FAN_MODIFY ! = FS_MODIFY ) ;
BUILD_BUG_ON ( FAN_CLOSE_NOWRITE ! = FS_CLOSE_NOWRITE ) ;
BUILD_BUG_ON ( FAN_CLOSE_WRITE ! = FS_CLOSE_WRITE ) ;
BUILD_BUG_ON ( FAN_OPEN ! = FS_OPEN ) ;
BUILD_BUG_ON ( FAN_EVENT_ON_CHILD ! = FS_EVENT_ON_CHILD ) ;
BUILD_BUG_ON ( FAN_Q_OVERFLOW ! = FS_Q_OVERFLOW ) ;
BUILD_BUG_ON ( FAN_OPEN_PERM ! = FS_OPEN_PERM ) ;
BUILD_BUG_ON ( FAN_ACCESS_PERM ! = FS_ACCESS_PERM ) ;
BUILD_BUG_ON ( FAN_ONDIR ! = FS_ISDIR ) ;
2014-01-21 15:48:15 -08:00
if ( ! fanotify_should_send_event ( inode_mark , fanotify_mark , mask , data ,
data_type ) )
return 0 ;
2014-01-21 15:48:14 -08:00
pr_debug ( " %s: group=%p inode=%p mask=%x \n " , __func__ , group , inode ,
mask ) ;
2014-04-03 14:46:33 -07:00
event = fanotify_alloc_event ( inode , mask , data ) ;
2014-01-21 15:48:14 -08:00
if ( unlikely ( ! event ) )
return - ENOMEM ;
fsn_event = & event - > fse ;
2014-08-06 16:03:26 -07:00
ret = fsnotify_add_event ( group , fsn_event , fanotify_merge ) ;
2014-01-28 18:53:22 +01:00
if ( ret ) {
2014-02-21 19:07:54 +01:00
/* Permission events shouldn't be merged */
BUG_ON ( ret = = 1 & & mask & FAN_ALL_PERM_EVENTS ) ;
2014-01-21 15:48:14 -08:00
/* Our event wasn't used in the end. Free it. */
fsnotify_destroy_event ( group , fsn_event ) ;
2014-02-21 19:07:54 +01:00
return 0 ;
2014-01-21 15:48:14 -08:00
}
# ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS
2014-01-28 21:38:06 +01:00
if ( mask & FAN_ALL_PERM_EVENTS ) {
2016-11-10 17:45:16 +01:00
ret = fanotify_get_response ( group , FANOTIFY_PE ( fsn_event ) ,
iter_info ) ;
2014-01-28 21:38:06 +01:00
fsnotify_destroy_event ( group , fsn_event ) ;
}
2014-01-21 15:48:14 -08:00
# endif
return ret ;
}
2010-10-28 17:21:58 -04:00
static void fanotify_free_group_priv ( struct fsnotify_group * group )
{
struct user_struct * user ;
user = group - > fanotify_data . user ;
atomic_dec ( & user - > fanotify_listeners ) ;
free_uid ( user ) ;
}
2014-01-21 15:48:14 -08:00
static void fanotify_free_event ( struct fsnotify_event * fsn_event )
{
struct fanotify_event_info * event ;
event = FANOTIFY_E ( fsn_event ) ;
path_put ( & event - > path ) ;
put_pid ( event - > tgid ) ;
2014-04-03 14:46:33 -07:00
# ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS
if ( fsn_event - > mask & FAN_ALL_PERM_EVENTS ) {
kmem_cache_free ( fanotify_perm_event_cachep ,
FANOTIFY_PE ( fsn_event ) ) ;
return ;
}
# endif
2014-01-21 15:48:14 -08:00
kmem_cache_free ( fanotify_event_cachep , event ) ;
}
2009-12-17 21:24:25 -05:00
const struct fsnotify_ops fanotify_fsnotify_ops = {
. handle_event = fanotify_handle_event ,
2010-10-28 17:21:58 -04:00
. free_group_priv = fanotify_free_group_priv ,
2014-01-21 15:48:14 -08:00
. free_event = fanotify_free_event ,
2009-12-17 21:24:25 -05:00
} ;