Skip to content

Commit a94204f

Browse files
amir73iljankara
authored andcommitted
fsnotify: opt-in for permission events at file open time
Legacy inotify/fanotify listeners can add watches for events on inode, parent or mount and expect to get events (e.g. FS_MODIFY) on files that were already open at the time of setting up the watches. fanotify permission events are typically used by Anti-malware sofware, that is watching the entire mount and it is not common to have more that one Anti-malware engine installed on a system. To reduce the overhead of the fsnotify_file_perm() hooks on every file access, relax the semantics of the legacy FAN_ACCESS_PERM event to generate events only if there were *any* permission event listeners on the filesystem at the time that the file was opened. The new semantic is implemented by extending the FMODE_NONOTIFY bit into two FMODE_NONOTIFY_* bits, that are used to store a mode for which of the events types to report. This is going to apply to the new fanotify pre-content events in order to reduce the cost of the new pre-content event vfs hooks. [Thanks to Bert Karwatzki <[email protected]> for reporting a bug in this code with CONFIG_FANOTIFY_ACCESS_PERMISSIONS disabled] Suggested-by: Linus Torvalds <[email protected]> Link: https://lore.kernel.org/linux-fsdevel/CAHk-=wj8L=mtcRTi=NECHMGfZQgXOp_uix1YVh04fEmrKaMnXA@mail.gmail.com/ Signed-off-by: Amir Goldstein <[email protected]> Signed-off-by: Jan Kara <[email protected]> Link: https://patch.msgid.link/5ea5f8e283d1edb55aa79c35187bfe344056af14.1731684329.git.josef@toxicpanda.com
1 parent ebe5596 commit a94204f

File tree

4 files changed

+106
-22
lines changed

4 files changed

+106
-22
lines changed

fs/notify/fsnotify.c

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -623,6 +623,44 @@ int fsnotify(__u32 mask, const void *data, int data_type, struct inode *dir,
623623
}
624624
EXPORT_SYMBOL_GPL(fsnotify);
625625

626+
#ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS
627+
/*
628+
* At open time we check fsnotify_sb_has_priority_watchers() and set the
629+
* FMODE_NONOTIFY_ mode bits accordignly.
630+
* Later, fsnotify permission hooks do not check if there are permission event
631+
* watches, but that there were permission event watches at open time.
632+
*/
633+
void file_set_fsnotify_mode(struct file *file)
634+
{
635+
struct super_block *sb = file->f_path.dentry->d_sb;
636+
637+
/* Is it a file opened by fanotify? */
638+
if (FMODE_FSNOTIFY_NONE(file->f_mode))
639+
return;
640+
641+
/*
642+
* Permission events is a super set of pre-content events, so if there
643+
* are no permission event watchers, there are also no pre-content event
644+
* watchers and this is implied from the single FMODE_NONOTIFY_PERM bit.
645+
*/
646+
if (likely(!fsnotify_sb_has_priority_watchers(sb,
647+
FSNOTIFY_PRIO_CONTENT))) {
648+
file->f_mode |= FMODE_NONOTIFY_PERM;
649+
return;
650+
}
651+
652+
/*
653+
* If there are permission event watchers but no pre-content event
654+
* watchers, set FMODE_NONOTIFY | FMODE_NONOTIFY_PERM to indicate that.
655+
*/
656+
if (likely(!fsnotify_sb_has_priority_watchers(sb,
657+
FSNOTIFY_PRIO_PRE_CONTENT))) {
658+
file->f_mode |= FMODE_NONOTIFY | FMODE_NONOTIFY_PERM;
659+
return;
660+
}
661+
}
662+
#endif
663+
626664
static __init int fsnotify_init(void)
627665
{
628666
int ret;

fs/open.c

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -901,7 +901,7 @@ static int do_dentry_open(struct file *f,
901901
f->f_sb_err = file_sample_sb_err(f);
902902

903903
if (unlikely(f->f_flags & O_PATH)) {
904-
f->f_mode = FMODE_PATH | FMODE_OPENED;
904+
f->f_mode = FMODE_PATH | FMODE_OPENED | FMODE_NONOTIFY;
905905
f->f_op = &empty_fops;
906906
return 0;
907907
}
@@ -929,6 +929,12 @@ static int do_dentry_open(struct file *f,
929929
if (error)
930930
goto cleanup_all;
931931

932+
/*
933+
* Set FMODE_NONOTIFY_* bits according to existing permission watches.
934+
* If FMODE_NONOTIFY was already set for an fanotify fd, this doesn't
935+
* change anything.
936+
*/
937+
file_set_fsnotify_mode(f);
932938
error = fsnotify_open_perm(f);
933939
if (error)
934940
goto cleanup_all;

include/linux/fs.h

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -173,13 +173,20 @@ typedef int (dio_iodone_t)(struct kiocb *iocb, loff_t offset,
173173

174174
#define FMODE_NOREUSE ((__force fmode_t)(1 << 23))
175175

176-
/* FMODE_* bit 24 */
177-
178176
/* File is embedded in backing_file object */
179-
#define FMODE_BACKING ((__force fmode_t)(1 << 25))
177+
#define FMODE_BACKING ((__force fmode_t)(1 << 24))
178+
179+
/*
180+
* Together with FMODE_NONOTIFY_PERM defines which fsnotify events shouldn't be
181+
* generated (see below)
182+
*/
183+
#define FMODE_NONOTIFY ((__force fmode_t)(1 << 25))
180184

181-
/* File was opened by fanotify and shouldn't generate fanotify events */
182-
#define FMODE_NONOTIFY ((__force fmode_t)(1 << 26))
185+
/*
186+
* Together with FMODE_NONOTIFY defines which fsnotify events shouldn't be
187+
* generated (see below)
188+
*/
189+
#define FMODE_NONOTIFY_PERM ((__force fmode_t)(1 << 26))
183190

184191
/* File is capable of returning -EAGAIN if I/O will block */
185192
#define FMODE_NOWAIT ((__force fmode_t)(1 << 27))
@@ -190,6 +197,32 @@ typedef int (dio_iodone_t)(struct kiocb *iocb, loff_t offset,
190197
/* File does not contribute to nr_files count */
191198
#define FMODE_NOACCOUNT ((__force fmode_t)(1 << 29))
192199

200+
/*
201+
* The two FMODE_NONOTIFY* define which fsnotify events should not be generated
202+
* for a file. These are the possible values of (f->f_mode &
203+
* FMODE_FSNOTIFY_MASK) and their meaning:
204+
*
205+
* FMODE_NONOTIFY - suppress all (incl. non-permission) events.
206+
* FMODE_NONOTIFY_PERM - suppress permission (incl. pre-content) events.
207+
* FMODE_NONOTIFY | FMODE_NONOTIFY_PERM - suppress only pre-content events.
208+
*/
209+
#define FMODE_FSNOTIFY_MASK \
210+
(FMODE_NONOTIFY | FMODE_NONOTIFY_PERM)
211+
212+
#define FMODE_FSNOTIFY_NONE(mode) \
213+
((mode & FMODE_FSNOTIFY_MASK) == FMODE_NONOTIFY)
214+
#ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS
215+
#define FMODE_FSNOTIFY_PERM(mode) \
216+
((mode & FMODE_FSNOTIFY_MASK) == 0 || \
217+
(mode & FMODE_FSNOTIFY_MASK) == (FMODE_NONOTIFY | FMODE_NONOTIFY_PERM))
218+
#define FMODE_FSNOTIFY_HSM(mode) \
219+
((mode & FMODE_FSNOTIFY_MASK) == 0)
220+
#else
221+
#define FMODE_FSNOTIFY_PERM(mode) 0
222+
#define FMODE_FSNOTIFY_HSM(mode) 0
223+
#endif
224+
225+
193226
/*
194227
* Attribute flags. These should be or-ed together to figure out what
195228
* has been changed!

include/linux/fsnotify.h

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -108,38 +108,35 @@ static inline void fsnotify_dentry(struct dentry *dentry, __u32 mask)
108108
fsnotify_parent(dentry, mask, dentry, FSNOTIFY_EVENT_DENTRY);
109109
}
110110

111-
static inline int fsnotify_file(struct file *file, __u32 mask)
111+
static inline int fsnotify_path(const struct path *path, __u32 mask)
112112
{
113-
const struct path *path;
113+
return fsnotify_parent(path->dentry, mask, path, FSNOTIFY_EVENT_PATH);
114+
}
114115

116+
static inline int fsnotify_file(struct file *file, __u32 mask)
117+
{
115118
/*
116119
* FMODE_NONOTIFY are fds generated by fanotify itself which should not
117120
* generate new events. We also don't want to generate events for
118121
* FMODE_PATH fds (involves open & close events) as they are just
119122
* handle creation / destruction events and not "real" file events.
120123
*/
121-
if (file->f_mode & (FMODE_NONOTIFY | FMODE_PATH))
124+
if (FMODE_FSNOTIFY_NONE(file->f_mode))
122125
return 0;
123126

124-
path = &file->f_path;
125-
/* Permission events require group prio >= FSNOTIFY_PRIO_CONTENT */
126-
if (mask & ALL_FSNOTIFY_PERM_EVENTS &&
127-
!fsnotify_sb_has_priority_watchers(path->dentry->d_sb,
128-
FSNOTIFY_PRIO_CONTENT))
129-
return 0;
130-
131-
return fsnotify_parent(path->dentry, mask, path, FSNOTIFY_EVENT_PATH);
127+
return fsnotify_path(&file->f_path, mask);
132128
}
133129

134130
#ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS
131+
132+
void file_set_fsnotify_mode(struct file *file);
133+
135134
/*
136135
* fsnotify_file_area_perm - permission hook before access to file range
137136
*/
138137
static inline int fsnotify_file_area_perm(struct file *file, int perm_mask,
139138
const loff_t *ppos, size_t count)
140139
{
141-
__u32 fsnotify_mask = FS_ACCESS_PERM;
142-
143140
/*
144141
* filesystem may be modified in the context of permission events
145142
* (e.g. by HSM filling a file on access), so sb freeze protection
@@ -150,7 +147,10 @@ static inline int fsnotify_file_area_perm(struct file *file, int perm_mask,
150147
if (!(perm_mask & MAY_READ))
151148
return 0;
152149

153-
return fsnotify_file(file, fsnotify_mask);
150+
if (likely(!FMODE_FSNOTIFY_PERM(file->f_mode)))
151+
return 0;
152+
153+
return fsnotify_path(&file->f_path, FS_ACCESS_PERM);
154154
}
155155

156156
/*
@@ -168,16 +168,23 @@ static inline int fsnotify_open_perm(struct file *file)
168168
{
169169
int ret;
170170

171+
if (likely(!FMODE_FSNOTIFY_PERM(file->f_mode)))
172+
return 0;
173+
171174
if (file->f_flags & __FMODE_EXEC) {
172-
ret = fsnotify_file(file, FS_OPEN_EXEC_PERM);
175+
ret = fsnotify_path(&file->f_path, FS_OPEN_EXEC_PERM);
173176
if (ret)
174177
return ret;
175178
}
176179

177-
return fsnotify_file(file, FS_OPEN_PERM);
180+
return fsnotify_path(&file->f_path, FS_OPEN_PERM);
178181
}
179182

180183
#else
184+
static inline void file_set_fsnotify_mode(struct file *file)
185+
{
186+
}
187+
181188
static inline int fsnotify_file_area_perm(struct file *file, int perm_mask,
182189
const loff_t *ppos, size_t count)
183190
{

0 commit comments

Comments
 (0)