2009-12-18 05:24:29 +03:00
# include <linux/fanotify.h>
2009-12-18 05:24:25 +03:00
# include <linux/fdtable.h>
# include <linux/fsnotify_backend.h>
# include <linux/init.h>
2009-12-18 05:24:34 +03:00
# include <linux/jiffies.h>
2009-12-18 05:24:25 +03:00
# include <linux/kernel.h> /* UINT_MAX */
2009-12-18 05:24:28 +03:00
# include <linux/mount.h>
2009-12-18 05:24:34 +03:00
# include <linux/sched.h>
2009-12-18 05:24:25 +03:00
# include <linux/types.h>
2009-12-18 05:24:34 +03:00
# include <linux/wait.h>
2009-12-18 05:24:25 +03:00
2014-01-22 03:48:14 +04:00
# include "fanotify.h"
static bool should_merge ( struct fsnotify_event * old_fsn ,
struct fsnotify_event * new_fsn )
2009-12-18 05:24:25 +03:00
{
2014-01-22 03:48:14 +04:00
struct fanotify_event_info * old , * new ;
2009-12-18 05:24:25 +03:00
2014-01-22 03:48:14 +04: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-18 05:24:25 +03:00
return false ;
}
2010-07-28 18:18:37 +04:00
/* and the list better be locked by something too! */
2014-01-28 21:53:22 +04:00
static int fanotify_merge ( struct list_head * list , struct fsnotify_event * event )
2009-12-18 05:24:25 +03:00
{
2014-01-22 03:48:14 +04:00
struct fsnotify_event * test_event ;
bool do_merge = false ;
2009-12-18 05:24:25 +03:00
pr_debug ( " %s: list=%p event=%p \n " , __func__ , list , event ) ;
2014-01-28 21:29:24 +04: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 21:53:22 +04:00
return 0 ;
2014-01-28 21:29:24 +04:00
# endif
2014-01-22 03:48:14 +04:00
list_for_each_entry_reverse ( test_event , list , list ) {
if ( should_merge ( test_event , event ) ) {
do_merge = true ;
2009-12-18 05:24:25 +03:00
break ;
}
}
2010-07-28 18:18:37 +04:00
2014-01-22 03:48:14 +04:00
if ( ! do_merge )
2014-01-28 21:53:22 +04:00
return 0 ;
2010-07-28 18:18:37 +04:00
2014-01-22 03:48:14 +04:00
test_event - > mask | = event - > mask ;
2014-01-28 21:53:22 +04:00
return 1 ;
2009-12-18 05:24:25 +03:00
}
2009-12-18 05:24:34 +03:00
# ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS
2014-04-04 01:46:33 +04:00
static int fanotify_get_response ( struct fsnotify_group * group ,
struct fanotify_perm_event_info * event )
2009-12-18 05:24:34 +03:00
{
int ret ;
pr_debug ( " %s: group=%p event=%p \n " , __func__ , group , event ) ;
2010-11-19 12:58:07 +03:00
wait_event ( group - > fanotify_data . access_waitq , event - > response | |
atomic_read ( & group - > fanotify_data . bypass_perm ) ) ;
2014-08-07 03:03:28 +04:00
if ( ! event - > response ) { /* bypass_perm set */
/*
* Event was canceled because group is being destroyed . Remove
* it from group ' s event list because we are responsible for
* freeing the permission event .
*/
fsnotify_remove_event ( group , & event - > fae . fse ) ;
2010-11-19 12:58:07 +03:00
return 0 ;
2014-08-07 03:03:28 +04:00
}
2009-12-18 05:24:34 +03: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-18 05:24:34 +03:00
pr_debug ( " %s: group=%p event=%p about to return ret=%d \n " , __func__ ,
group , event , ret ) ;
2009-12-18 05:24:34 +03:00
return ret ;
}
# endif
2014-01-22 03:48:15 +04:00
static bool fanotify_should_send_event ( struct fsnotify_mark * inode_mark ,
2010-07-28 18:18:39 +04:00
struct fsnotify_mark * vfsmnt_mark ,
2014-01-22 03:48:15 +04:00
u32 event_mask ,
void * data , int data_type )
2009-12-18 05:24:28 +03:00
{
2010-07-28 18:18:39 +04:00
__u32 marks_mask , marks_ignored_mask ;
2010-10-29 01:21:58 +04:00
struct path * path = data ;
2010-07-28 18:18:39 +04:00
2014-01-22 03:48:15 +04: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 18:18:39 +04:00
2009-12-18 05:24:28 +03:00
/* if we don't have enough info to send an event to userspace say no */
2010-08-13 01:23:04 +04:00
if ( data_type ! = FSNOTIFY_EVENT_PATH )
2009-12-18 05:24:28 +03:00
return false ;
2010-10-29 01: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 15:02:35 +03:00
if ( ! d_is_reg ( path - > dentry ) & &
2015-01-29 15:02:36 +03:00
! d_can_lookup ( path - > dentry ) )
2010-10-29 01:21:58 +04:00
return false ;
2010-07-28 18: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 15:02:35 +03:00
if ( d_is_dir ( path - > dentry ) & &
2015-02-11 01:08:27 +03:00
! ( marks_mask & FS_ISDIR & ~ marks_ignored_mask ) )
2010-10-29 01: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-13 02:26:08 +03:00
if ( event_mask & FAN_ALL_OUTGOING_EVENTS & marks_mask &
~ marks_ignored_mask )
2010-07-28 18:18:39 +04:00
return true ;
return false ;
2009-12-18 05:24:28 +03:00
}
2014-04-04 01:46:33 +04:00
struct fanotify_event_info * fanotify_alloc_event ( struct inode * inode , u32 mask ,
struct path * path )
{
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-22 03:48:14 +04:00
static int fanotify_handle_event ( struct fsnotify_group * group ,
struct inode * inode ,
struct fsnotify_mark * inode_mark ,
struct fsnotify_mark * fanotify_mark ,
u32 mask , void * data , int data_type ,
2014-02-17 16:09:50 +04:00
const unsigned char * file_name , u32 cookie )
2014-01-22 03:48:14 +04: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-22 03:48:15 +04:00
if ( ! fanotify_should_send_event ( inode_mark , fanotify_mark , mask , data ,
data_type ) )
return 0 ;
2014-01-22 03:48:14 +04:00
pr_debug ( " %s: group=%p inode=%p mask=%x \n " , __func__ , group , inode ,
mask ) ;
2014-04-04 01:46:33 +04:00
event = fanotify_alloc_event ( inode , mask , data ) ;
2014-01-22 03:48:14 +04:00
if ( unlikely ( ! event ) )
return - ENOMEM ;
fsn_event = & event - > fse ;
2014-08-07 03:03:26 +04:00
ret = fsnotify_add_event ( group , fsn_event , fanotify_merge ) ;
2014-01-28 21:53:22 +04:00
if ( ret ) {
2014-02-21 22:07:54 +04:00
/* Permission events shouldn't be merged */
BUG_ON ( ret = = 1 & & mask & FAN_ALL_PERM_EVENTS ) ;
2014-01-22 03:48:14 +04:00
/* Our event wasn't used in the end. Free it. */
fsnotify_destroy_event ( group , fsn_event ) ;
2014-02-21 22:07:54 +04:00
return 0 ;
2014-01-22 03:48:14 +04:00
}
# ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS
2014-01-29 00:38:06 +04:00
if ( mask & FAN_ALL_PERM_EVENTS ) {
2014-04-04 01:46:33 +04:00
ret = fanotify_get_response ( group , FANOTIFY_PE ( fsn_event ) ) ;
2014-01-29 00:38:06 +04:00
fsnotify_destroy_event ( group , fsn_event ) ;
}
2014-01-22 03:48:14 +04:00
# endif
return ret ;
}
2010-10-29 01: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-22 03:48:14 +04: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-04 01:46:33 +04: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-22 03:48:14 +04:00
kmem_cache_free ( fanotify_event_cachep , event ) ;
}
2009-12-18 05:24:25 +03:00
const struct fsnotify_ops fanotify_fsnotify_ops = {
. handle_event = fanotify_handle_event ,
2010-10-29 01:21:58 +04:00
. free_group_priv = fanotify_free_group_priv ,
2014-01-22 03:48:14 +04:00
. free_event = fanotify_free_event ,
2009-12-18 05:24:25 +03:00
} ;