aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2014-01-29 19:09:34 -0500
committerLinus Torvalds <torvalds@linux-foundation.org>2014-01-29 19:09:34 -0500
commit1ecd7450c0503d99675109a4cd43ecd735b9d876 (patch)
treee76671c1920f7e7ccccd405c376628ba7682f10a
parent72466d0b92e04a7e0e5abf74c86eb352225346e4 (diff)
parent85816794240b9659e66e4d9b0df7c6e814e5f603 (diff)
Merge branch 'for_linus' of git://git.kernel.org/pub/scm/linux/kernel/git/jack/linux-fs
Pull fanotify use-after-free fixes from Jan Kara: "Three fixes for the fanotify use after free problems guys were reporting. I have ended up with different lifetime rules for struct fanotify_event_info depending on whether it is for permission event or normal event which isn't ideal. My plan is to split these into two different structures (as permission events need larger struct anyway) which will make the rules trivial again. But that can wait for later I guess (but I can add the patch to the pile if you want), now I wanted to make -rc1 boot for these guys" [ "These guys" being Jiri Kosina and Dave Jones that reported the slab corruption issues due to incorrect object lifetimes ] * 'for_linus' of git://git.kernel.org/pub/scm/linux/kernel/git/jack/linux-fs: fanotify: Fix use after free for permission events fsnotify: Do not return merged event from fsnotify_add_notify_event() fanotify: Fix use after free in mask checking
-rw-r--r--fs/notify/fanotify/fanotify.c40
-rw-r--r--fs/notify/fanotify/fanotify.h7
-rw-r--r--fs/notify/fanotify/fanotify_user.c7
-rw-r--r--fs/notify/inotify/inotify_fsnotify.c19
-rw-r--r--fs/notify/notification.c24
-rw-r--r--include/linux/fsnotify_backend.h8
6 files changed, 56 insertions, 49 deletions
diff --git a/fs/notify/fanotify/fanotify.c b/fs/notify/fanotify/fanotify.c
index 58772623f02a..0e792f5e3147 100644
--- a/fs/notify/fanotify/fanotify.c
+++ b/fs/notify/fanotify/fanotify.c
@@ -16,12 +16,6 @@ static bool should_merge(struct fsnotify_event *old_fsn,
16{ 16{
17 struct fanotify_event_info *old, *new; 17 struct fanotify_event_info *old, *new;
18 18
19#ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS
20 /* dont merge two permission events */
21 if ((old_fsn->mask & FAN_ALL_PERM_EVENTS) &&
22 (new_fsn->mask & FAN_ALL_PERM_EVENTS))
23 return false;
24#endif
25 pr_debug("%s: old=%p new=%p\n", __func__, old_fsn, new_fsn); 19 pr_debug("%s: old=%p new=%p\n", __func__, old_fsn, new_fsn);
26 old = FANOTIFY_E(old_fsn); 20 old = FANOTIFY_E(old_fsn);
27 new = FANOTIFY_E(new_fsn); 21 new = FANOTIFY_E(new_fsn);
@@ -34,14 +28,23 @@ static bool should_merge(struct fsnotify_event *old_fsn,
34} 28}
35 29
36/* and the list better be locked by something too! */ 30/* and the list better be locked by something too! */
37static struct fsnotify_event *fanotify_merge(struct list_head *list, 31static int fanotify_merge(struct list_head *list, struct fsnotify_event *event)
38 struct fsnotify_event *event)
39{ 32{
40 struct fsnotify_event *test_event; 33 struct fsnotify_event *test_event;
41 bool do_merge = false; 34 bool do_merge = false;
42 35
43 pr_debug("%s: list=%p event=%p\n", __func__, list, event); 36 pr_debug("%s: list=%p event=%p\n", __func__, list, event);
44 37
38#ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS
39 /*
40 * Don't merge a permission event with any other event so that we know
41 * the event structure we have created in fanotify_handle_event() is the
42 * one we should check for permission response.
43 */
44 if (event->mask & FAN_ALL_PERM_EVENTS)
45 return 0;
46#endif
47
45 list_for_each_entry_reverse(test_event, list, list) { 48 list_for_each_entry_reverse(test_event, list, list) {
46 if (should_merge(test_event, event)) { 49 if (should_merge(test_event, event)) {
47 do_merge = true; 50 do_merge = true;
@@ -50,10 +53,10 @@ static struct fsnotify_event *fanotify_merge(struct list_head *list,
50 } 53 }
51 54
52 if (!do_merge) 55 if (!do_merge)
53 return NULL; 56 return 0;
54 57
55 test_event->mask |= event->mask; 58 test_event->mask |= event->mask;
56 return test_event; 59 return 1;
57} 60}
58 61
59#ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS 62#ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS
@@ -149,7 +152,6 @@ static int fanotify_handle_event(struct fsnotify_group *group,
149 int ret = 0; 152 int ret = 0;
150 struct fanotify_event_info *event; 153 struct fanotify_event_info *event;
151 struct fsnotify_event *fsn_event; 154 struct fsnotify_event *fsn_event;
152 struct fsnotify_event *notify_fsn_event;
153 155
154 BUILD_BUG_ON(FAN_ACCESS != FS_ACCESS); 156 BUILD_BUG_ON(FAN_ACCESS != FS_ACCESS);
155 BUILD_BUG_ON(FAN_MODIFY != FS_MODIFY); 157 BUILD_BUG_ON(FAN_MODIFY != FS_MODIFY);
@@ -188,21 +190,19 @@ static int fanotify_handle_event(struct fsnotify_group *group,
188 event->response = 0; 190 event->response = 0;
189#endif 191#endif
190 192
191 notify_fsn_event = fsnotify_add_notify_event(group, fsn_event, 193 ret = fsnotify_add_notify_event(group, fsn_event, fanotify_merge);
192 fanotify_merge); 194 if (ret) {
193 if (notify_fsn_event) { 195 BUG_ON(mask & FAN_ALL_PERM_EVENTS);
194 /* Our event wasn't used in the end. Free it. */ 196 /* Our event wasn't used in the end. Free it. */
195 fsnotify_destroy_event(group, fsn_event); 197 fsnotify_destroy_event(group, fsn_event);
196 if (IS_ERR(notify_fsn_event)) 198 ret = 0;
197 return PTR_ERR(notify_fsn_event);
198 /* We need to ask about a different events after a merge... */
199 event = FANOTIFY_E(notify_fsn_event);
200 fsn_event = notify_fsn_event;
201 } 199 }
202 200
203#ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS 201#ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS
204 if (fsn_event->mask & FAN_ALL_PERM_EVENTS) 202 if (mask & FAN_ALL_PERM_EVENTS) {
205 ret = fanotify_get_response_from_access(group, event); 203 ret = fanotify_get_response_from_access(group, event);
204 fsnotify_destroy_event(group, fsn_event);
205 }
206#endif 206#endif
207 return ret; 207 return ret;
208} 208}
diff --git a/fs/notify/fanotify/fanotify.h b/fs/notify/fanotify/fanotify.h
index 0e90174a116a..32a2f034fb94 100644
--- a/fs/notify/fanotify/fanotify.h
+++ b/fs/notify/fanotify/fanotify.h
@@ -4,6 +4,13 @@
4 4
5extern struct kmem_cache *fanotify_event_cachep; 5extern struct kmem_cache *fanotify_event_cachep;
6 6
7/*
8 * Lifetime of the structure differs for normal and permission events. In both
9 * cases the structure is allocated in fanotify_handle_event(). For normal
10 * events the structure is freed immediately after reporting it to userspace.
11 * For permission events we free it only after we receive response from
12 * userspace.
13 */
7struct fanotify_event_info { 14struct fanotify_event_info {
8 struct fsnotify_event fse; 15 struct fsnotify_event fse;
9 /* 16 /*
diff --git a/fs/notify/fanotify/fanotify_user.c b/fs/notify/fanotify/fanotify_user.c
index 1fd66abe5740..b6175fa11bf8 100644
--- a/fs/notify/fanotify/fanotify_user.c
+++ b/fs/notify/fanotify/fanotify_user.c
@@ -319,7 +319,12 @@ static ssize_t fanotify_read(struct file *file, char __user *buf,
319 if (IS_ERR(kevent)) 319 if (IS_ERR(kevent))
320 break; 320 break;
321 ret = copy_event_to_user(group, kevent, buf); 321 ret = copy_event_to_user(group, kevent, buf);
322 fsnotify_destroy_event(group, kevent); 322 /*
323 * Permission events get destroyed after we
324 * receive response
325 */
326 if (!(kevent->mask & FAN_ALL_PERM_EVENTS))
327 fsnotify_destroy_event(group, kevent);
323 if (ret < 0) 328 if (ret < 0)
324 break; 329 break;
325 buf += ret; 330 buf += ret;
diff --git a/fs/notify/inotify/inotify_fsnotify.c b/fs/notify/inotify/inotify_fsnotify.c
index aad1a35e9af1..d5ee56348bb8 100644
--- a/fs/notify/inotify/inotify_fsnotify.c
+++ b/fs/notify/inotify/inotify_fsnotify.c
@@ -53,15 +53,13 @@ static bool event_compare(struct fsnotify_event *old_fsn,
53 return false; 53 return false;
54} 54}
55 55
56static struct fsnotify_event *inotify_merge(struct list_head *list, 56static int inotify_merge(struct list_head *list,
57 struct fsnotify_event *event) 57 struct fsnotify_event *event)
58{ 58{
59 struct fsnotify_event *last_event; 59 struct fsnotify_event *last_event;
60 60
61 last_event = list_entry(list->prev, struct fsnotify_event, list); 61 last_event = list_entry(list->prev, struct fsnotify_event, list);
62 if (!event_compare(last_event, event)) 62 return event_compare(last_event, event);
63 return NULL;
64 return last_event;
65} 63}
66 64
67int inotify_handle_event(struct fsnotify_group *group, 65int inotify_handle_event(struct fsnotify_group *group,
@@ -73,9 +71,8 @@ int inotify_handle_event(struct fsnotify_group *group,
73{ 71{
74 struct inotify_inode_mark *i_mark; 72 struct inotify_inode_mark *i_mark;
75 struct inotify_event_info *event; 73 struct inotify_event_info *event;
76 struct fsnotify_event *added_event;
77 struct fsnotify_event *fsn_event; 74 struct fsnotify_event *fsn_event;
78 int ret = 0; 75 int ret;
79 int len = 0; 76 int len = 0;
80 int alloc_len = sizeof(struct inotify_event_info); 77 int alloc_len = sizeof(struct inotify_event_info);
81 78
@@ -110,18 +107,16 @@ int inotify_handle_event(struct fsnotify_group *group,
110 if (len) 107 if (len)
111 strcpy(event->name, file_name); 108 strcpy(event->name, file_name);
112 109
113 added_event = fsnotify_add_notify_event(group, fsn_event, inotify_merge); 110 ret = fsnotify_add_notify_event(group, fsn_event, inotify_merge);
114 if (added_event) { 111 if (ret) {
115 /* Our event wasn't used in the end. Free it. */ 112 /* Our event wasn't used in the end. Free it. */
116 fsnotify_destroy_event(group, fsn_event); 113 fsnotify_destroy_event(group, fsn_event);
117 if (IS_ERR(added_event))
118 ret = PTR_ERR(added_event);
119 } 114 }
120 115
121 if (inode_mark->mask & IN_ONESHOT) 116 if (inode_mark->mask & IN_ONESHOT)
122 fsnotify_destroy_mark(inode_mark, group); 117 fsnotify_destroy_mark(inode_mark, group);
123 118
124 return ret; 119 return 0;
125} 120}
126 121
127static void inotify_freeing_mark(struct fsnotify_mark *fsn_mark, struct fsnotify_group *group) 122static void inotify_freeing_mark(struct fsnotify_mark *fsn_mark, struct fsnotify_group *group)
diff --git a/fs/notify/notification.c b/fs/notify/notification.c
index 952237b8e2d2..18b3c4427dca 100644
--- a/fs/notify/notification.c
+++ b/fs/notify/notification.c
@@ -79,15 +79,15 @@ void fsnotify_destroy_event(struct fsnotify_group *group,
79 79
80/* 80/*
81 * Add an event to the group notification queue. The group can later pull this 81 * Add an event to the group notification queue. The group can later pull this
82 * event off the queue to deal with. If the event is successfully added to the 82 * event off the queue to deal with. The function returns 0 if the event was
83 * group's notification queue, a reference is taken on event. 83 * added to the queue, 1 if the event was merged with some other queued event.
84 */ 84 */
85struct fsnotify_event *fsnotify_add_notify_event(struct fsnotify_group *group, 85int fsnotify_add_notify_event(struct fsnotify_group *group,
86 struct fsnotify_event *event, 86 struct fsnotify_event *event,
87 struct fsnotify_event *(*merge)(struct list_head *, 87 int (*merge)(struct list_head *,
88 struct fsnotify_event *)) 88 struct fsnotify_event *))
89{ 89{
90 struct fsnotify_event *return_event = NULL; 90 int ret = 0;
91 struct list_head *list = &group->notification_list; 91 struct list_head *list = &group->notification_list;
92 92
93 pr_debug("%s: group=%p event=%p\n", __func__, group, event); 93 pr_debug("%s: group=%p event=%p\n", __func__, group, event);
@@ -98,14 +98,14 @@ struct fsnotify_event *fsnotify_add_notify_event(struct fsnotify_group *group,
98 /* Queue overflow event only if it isn't already queued */ 98 /* Queue overflow event only if it isn't already queued */
99 if (list_empty(&group->overflow_event.list)) 99 if (list_empty(&group->overflow_event.list))
100 event = &group->overflow_event; 100 event = &group->overflow_event;
101 return_event = event; 101 ret = 1;
102 } 102 }
103 103
104 if (!list_empty(list) && merge) { 104 if (!list_empty(list) && merge) {
105 return_event = merge(list, event); 105 ret = merge(list, event);
106 if (return_event) { 106 if (ret) {
107 mutex_unlock(&group->notification_mutex); 107 mutex_unlock(&group->notification_mutex);
108 return return_event; 108 return ret;
109 } 109 }
110 } 110 }
111 111
@@ -115,7 +115,7 @@ struct fsnotify_event *fsnotify_add_notify_event(struct fsnotify_group *group,
115 115
116 wake_up(&group->notification_waitq); 116 wake_up(&group->notification_waitq);
117 kill_fasync(&group->fsn_fa, SIGIO, POLL_IN); 117 kill_fasync(&group->fsn_fa, SIGIO, POLL_IN);
118 return return_event; 118 return ret;
119} 119}
120 120
121/* 121/*
diff --git a/include/linux/fsnotify_backend.h b/include/linux/fsnotify_backend.h
index 7d8d5e608594..3d286ff49ab0 100644
--- a/include/linux/fsnotify_backend.h
+++ b/include/linux/fsnotify_backend.h
@@ -322,10 +322,10 @@ extern int fsnotify_fasync(int fd, struct file *file, int on);
322extern void fsnotify_destroy_event(struct fsnotify_group *group, 322extern void fsnotify_destroy_event(struct fsnotify_group *group,
323 struct fsnotify_event *event); 323 struct fsnotify_event *event);
324/* attach the event to the group notification queue */ 324/* attach the event to the group notification queue */
325extern struct fsnotify_event *fsnotify_add_notify_event(struct fsnotify_group *group, 325extern int fsnotify_add_notify_event(struct fsnotify_group *group,
326 struct fsnotify_event *event, 326 struct fsnotify_event *event,
327 struct fsnotify_event *(*merge)(struct list_head *, 327 int (*merge)(struct list_head *,
328 struct fsnotify_event *)); 328 struct fsnotify_event *));
329/* true if the group notification queue is empty */ 329/* true if the group notification queue is empty */
330extern bool fsnotify_notify_queue_is_empty(struct fsnotify_group *group); 330extern bool fsnotify_notify_queue_is_empty(struct fsnotify_group *group);
331/* return, but do not dequeue the first event on the notification queue */ 331/* return, but do not dequeue the first event on the notification queue */