]>
Commit | Line | Data |
---|---|---|
11637e4b | 1 | #include <linux/fcntl.h> |
2a3edf86 | 2 | #include <linux/file.h> |
11637e4b | 3 | #include <linux/fs.h> |
52c923dd | 4 | #include <linux/anon_inodes.h> |
11637e4b | 5 | #include <linux/fsnotify_backend.h> |
2a3edf86 EP |
6 | #include <linux/init.h> |
7 | #include <linux/namei.h> | |
11637e4b EP |
8 | #include <linux/security.h> |
9 | #include <linux/syscalls.h> | |
2a3edf86 | 10 | #include <linux/types.h> |
11637e4b EP |
11 | |
12 | #include "fanotify.h" | |
13 | ||
2a3edf86 EP |
14 | static struct kmem_cache *fanotify_mark_cache __read_mostly; |
15 | ||
52c923dd EP |
16 | static int fanotify_release(struct inode *ignored, struct file *file) |
17 | { | |
18 | struct fsnotify_group *group = file->private_data; | |
19 | ||
20 | pr_debug("%s: file=%p group=%p\n", __func__, file, group); | |
21 | ||
22 | /* matches the fanotify_init->fsnotify_alloc_group */ | |
23 | fsnotify_put_group(group); | |
24 | ||
25 | return 0; | |
26 | } | |
27 | ||
28 | static const struct file_operations fanotify_fops = { | |
29 | .poll = NULL, | |
30 | .read = NULL, | |
31 | .fasync = NULL, | |
32 | .release = fanotify_release, | |
33 | .unlocked_ioctl = NULL, | |
34 | .compat_ioctl = NULL, | |
35 | }; | |
36 | ||
2a3edf86 EP |
37 | static void fanotify_free_mark(struct fsnotify_mark *fsn_mark) |
38 | { | |
39 | kmem_cache_free(fanotify_mark_cache, fsn_mark); | |
40 | } | |
41 | ||
42 | static int fanotify_find_path(int dfd, const char __user *filename, | |
43 | struct path *path, unsigned int flags) | |
44 | { | |
45 | int ret; | |
46 | ||
47 | pr_debug("%s: dfd=%d filename=%p flags=%x\n", __func__, | |
48 | dfd, filename, flags); | |
49 | ||
50 | if (filename == NULL) { | |
51 | struct file *file; | |
52 | int fput_needed; | |
53 | ||
54 | ret = -EBADF; | |
55 | file = fget_light(dfd, &fput_needed); | |
56 | if (!file) | |
57 | goto out; | |
58 | ||
59 | ret = -ENOTDIR; | |
60 | if ((flags & FAN_MARK_ONLYDIR) && | |
61 | !(S_ISDIR(file->f_path.dentry->d_inode->i_mode))) { | |
62 | fput_light(file, fput_needed); | |
63 | goto out; | |
64 | } | |
65 | ||
66 | *path = file->f_path; | |
67 | path_get(path); | |
68 | fput_light(file, fput_needed); | |
69 | } else { | |
70 | unsigned int lookup_flags = 0; | |
71 | ||
72 | if (!(flags & FAN_MARK_DONT_FOLLOW)) | |
73 | lookup_flags |= LOOKUP_FOLLOW; | |
74 | if (flags & FAN_MARK_ONLYDIR) | |
75 | lookup_flags |= LOOKUP_DIRECTORY; | |
76 | ||
77 | ret = user_path_at(dfd, filename, lookup_flags, path); | |
78 | if (ret) | |
79 | goto out; | |
80 | } | |
81 | ||
82 | /* you can only watch an inode if you have read permissions on it */ | |
83 | ret = inode_permission(path->dentry->d_inode, MAY_READ); | |
84 | if (ret) | |
85 | path_put(path); | |
86 | out: | |
87 | return ret; | |
88 | } | |
89 | ||
90 | static int fanotify_remove_mark(struct fsnotify_group *group, | |
91 | struct inode *inode, | |
92 | __u32 mask) | |
93 | { | |
94 | struct fsnotify_mark *fsn_mark; | |
95 | __u32 new_mask; | |
96 | ||
97 | pr_debug("%s: group=%p inode=%p mask=%x\n", __func__, | |
98 | group, inode, mask); | |
99 | ||
100 | fsn_mark = fsnotify_find_mark(group, inode); | |
101 | if (!fsn_mark) | |
102 | return -ENOENT; | |
103 | ||
104 | spin_lock(&fsn_mark->lock); | |
105 | fsn_mark->mask &= ~mask; | |
106 | new_mask = fsn_mark->mask; | |
107 | spin_unlock(&fsn_mark->lock); | |
108 | ||
109 | if (!new_mask) | |
110 | fsnotify_destroy_mark(fsn_mark); | |
111 | else | |
112 | fsnotify_recalc_inode_mask(inode); | |
113 | ||
114 | fsnotify_recalc_group_mask(group); | |
115 | ||
116 | /* matches the fsnotify_find_mark() */ | |
117 | fsnotify_put_mark(fsn_mark); | |
118 | ||
119 | return 0; | |
120 | } | |
121 | ||
122 | static int fanotify_add_mark(struct fsnotify_group *group, | |
123 | struct inode *inode, | |
124 | __u32 mask) | |
125 | { | |
126 | struct fsnotify_mark *fsn_mark; | |
127 | __u32 old_mask, new_mask; | |
128 | int ret; | |
129 | ||
130 | pr_debug("%s: group=%p inode=%p mask=%x\n", __func__, | |
131 | group, inode, mask); | |
132 | ||
133 | fsn_mark = fsnotify_find_mark(group, inode); | |
134 | if (!fsn_mark) { | |
135 | struct fsnotify_mark *new_fsn_mark; | |
136 | ||
137 | ret = -ENOMEM; | |
138 | new_fsn_mark = kmem_cache_alloc(fanotify_mark_cache, GFP_KERNEL); | |
139 | if (!new_fsn_mark) | |
140 | goto out; | |
141 | ||
142 | fsnotify_init_mark(new_fsn_mark, fanotify_free_mark); | |
143 | ret = fsnotify_add_mark(new_fsn_mark, group, inode, 0); | |
144 | if (ret) { | |
145 | fanotify_free_mark(new_fsn_mark); | |
146 | goto out; | |
147 | } | |
148 | ||
149 | fsn_mark = new_fsn_mark; | |
150 | } | |
151 | ||
152 | ret = 0; | |
153 | ||
154 | spin_lock(&fsn_mark->lock); | |
155 | old_mask = fsn_mark->mask; | |
156 | fsn_mark->mask |= mask; | |
157 | new_mask = fsn_mark->mask; | |
158 | spin_unlock(&fsn_mark->lock); | |
159 | ||
160 | /* we made changes to a mask, update the group mask and the inode mask | |
161 | * so things happen quickly. */ | |
162 | if (old_mask != new_mask) { | |
163 | /* more bits in old than in new? */ | |
164 | int dropped = (old_mask & ~new_mask); | |
165 | /* more bits in this mark than the inode's mask? */ | |
166 | int do_inode = (new_mask & ~inode->i_fsnotify_mask); | |
167 | /* more bits in this mark than the group? */ | |
168 | int do_group = (new_mask & ~group->mask); | |
169 | ||
170 | /* update the inode with this new mark */ | |
171 | if (dropped || do_inode) | |
172 | fsnotify_recalc_inode_mask(inode); | |
173 | ||
174 | /* update the group mask with the new mask */ | |
175 | if (dropped || do_group) | |
176 | fsnotify_recalc_group_mask(group); | |
177 | } | |
178 | ||
179 | /* match the init or the find.... */ | |
180 | fsnotify_put_mark(fsn_mark); | |
181 | out: | |
182 | return ret; | |
183 | } | |
184 | ||
185 | static int fanotify_update_mark(struct fsnotify_group *group, | |
186 | struct inode *inode, int flags, | |
187 | __u32 mask) | |
188 | { | |
189 | pr_debug("%s: group=%p inode=%p flags=%x mask=%x\n", __func__, | |
190 | group, inode, flags, mask); | |
191 | ||
192 | if (flags & FAN_MARK_ADD) | |
193 | fanotify_add_mark(group, inode, mask); | |
194 | else if (flags & FAN_MARK_REMOVE) | |
195 | fanotify_remove_mark(group, inode, mask); | |
196 | else | |
197 | BUG(); | |
198 | ||
199 | return 0; | |
200 | } | |
201 | ||
202 | static bool fanotify_mark_validate_input(int flags, | |
203 | __u32 mask) | |
204 | { | |
205 | pr_debug("%s: flags=%x mask=%x\n", __func__, flags, mask); | |
206 | ||
207 | /* are flags valid of this operation? */ | |
208 | if (!fanotify_mark_flags_valid(flags)) | |
209 | return false; | |
210 | /* is the mask valid? */ | |
211 | if (!fanotify_mask_valid(mask)) | |
212 | return false; | |
213 | return true; | |
214 | } | |
215 | ||
52c923dd | 216 | /* fanotify syscalls */ |
11637e4b EP |
217 | SYSCALL_DEFINE3(fanotify_init, unsigned int, flags, unsigned int, event_f_flags, |
218 | unsigned int, priority) | |
219 | { | |
52c923dd EP |
220 | struct fsnotify_group *group; |
221 | int f_flags, fd; | |
222 | ||
223 | pr_debug("%s: flags=%d event_f_flags=%d priority=%d\n", | |
224 | __func__, flags, event_f_flags, priority); | |
225 | ||
226 | if (event_f_flags) | |
227 | return -EINVAL; | |
228 | if (priority) | |
229 | return -EINVAL; | |
230 | ||
231 | if (!capable(CAP_SYS_ADMIN)) | |
232 | return -EACCES; | |
233 | ||
234 | if (flags & ~FAN_ALL_INIT_FLAGS) | |
235 | return -EINVAL; | |
236 | ||
237 | f_flags = (O_RDONLY | FMODE_NONOTIFY); | |
238 | if (flags & FAN_CLOEXEC) | |
239 | f_flags |= O_CLOEXEC; | |
240 | if (flags & FAN_NONBLOCK) | |
241 | f_flags |= O_NONBLOCK; | |
242 | ||
243 | /* fsnotify_alloc_group takes a ref. Dropped in fanotify_release */ | |
244 | group = fsnotify_alloc_group(&fanotify_fsnotify_ops); | |
245 | if (IS_ERR(group)) | |
246 | return PTR_ERR(group); | |
247 | ||
248 | fd = anon_inode_getfd("[fanotify]", &fanotify_fops, group, f_flags); | |
249 | if (fd < 0) | |
250 | goto out_put_group; | |
251 | ||
252 | return fd; | |
253 | ||
254 | out_put_group: | |
255 | fsnotify_put_group(group); | |
256 | return fd; | |
11637e4b | 257 | } |
bbaa4168 EP |
258 | |
259 | SYSCALL_DEFINE5(fanotify_mark, int, fanotify_fd, unsigned int, flags, | |
260 | __u64, mask, int, dfd, const char __user *, pathname) | |
261 | { | |
2a3edf86 EP |
262 | struct inode *inode; |
263 | struct fsnotify_group *group; | |
264 | struct file *filp; | |
265 | struct path path; | |
266 | int ret, fput_needed; | |
267 | ||
268 | pr_debug("%s: fanotify_fd=%d flags=%x dfd=%d pathname=%p mask=%llx\n", | |
269 | __func__, fanotify_fd, flags, dfd, pathname, mask); | |
270 | ||
271 | /* we only use the lower 32 bits as of right now. */ | |
272 | if (mask & ((__u64)0xffffffff << 32)) | |
273 | return -EINVAL; | |
274 | ||
275 | if (!fanotify_mark_validate_input(flags, mask)) | |
276 | return -EINVAL; | |
277 | ||
278 | filp = fget_light(fanotify_fd, &fput_needed); | |
279 | if (unlikely(!filp)) | |
280 | return -EBADF; | |
281 | ||
282 | /* verify that this is indeed an fanotify instance */ | |
283 | ret = -EINVAL; | |
284 | if (unlikely(filp->f_op != &fanotify_fops)) | |
285 | goto fput_and_out; | |
286 | ||
287 | ret = fanotify_find_path(dfd, pathname, &path, flags); | |
288 | if (ret) | |
289 | goto fput_and_out; | |
290 | ||
291 | /* inode held in place by reference to path; group by fget on fd */ | |
292 | inode = path.dentry->d_inode; | |
293 | group = filp->private_data; | |
294 | ||
295 | /* create/update an inode mark */ | |
296 | ret = fanotify_update_mark(group, inode, flags, mask); | |
297 | ||
298 | path_put(&path); | |
299 | fput_and_out: | |
300 | fput_light(filp, fput_needed); | |
301 | return ret; | |
302 | } | |
303 | ||
304 | /* | |
305 | * fanotify_user_setup - Our initialization function. Note that we cannnot return | |
306 | * error because we have compiled-in VFS hooks. So an (unlikely) failure here | |
307 | * must result in panic(). | |
308 | */ | |
309 | static int __init fanotify_user_setup(void) | |
310 | { | |
311 | fanotify_mark_cache = KMEM_CACHE(fsnotify_mark, SLAB_PANIC); | |
312 | ||
313 | return 0; | |
bbaa4168 | 314 | } |
2a3edf86 | 315 | device_initcall(fanotify_user_setup); |