fanotify: sanitize failure exits in copy_event_to_user()
* do copy_to_user() before prepare_for_access_response(); that kills the need in remove_access_response(). * don't do fd_install() until we are past the last possible failure exit. Don't use sys_close() on cleanup side - just put_unused_fd() and fput(). Less racy that way... Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
This commit is contained in:
		
					parent
					
						
							
								ab72a7028c
							
						
					
				
			
			
				commit
				
					
						352e3b2492
					
				
			
		
					 1 changed files with 20 additions and 39 deletions
				
			
		|  | @ -58,7 +58,9 @@ static struct fsnotify_event *get_one_event(struct fsnotify_group *group, | ||||||
| 	return fsnotify_remove_notify_event(group); | 	return fsnotify_remove_notify_event(group); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static int create_fd(struct fsnotify_group *group, struct fsnotify_event *event) | static int create_fd(struct fsnotify_group *group, | ||||||
|  | 			struct fsnotify_event *event, | ||||||
|  | 			struct file **file) | ||||||
| { | { | ||||||
| 	int client_fd; | 	int client_fd; | ||||||
| 	struct file *new_file; | 	struct file *new_file; | ||||||
|  | @ -98,7 +100,7 @@ static int create_fd(struct fsnotify_group *group, struct fsnotify_event *event) | ||||||
| 		put_unused_fd(client_fd); | 		put_unused_fd(client_fd); | ||||||
| 		client_fd = PTR_ERR(new_file); | 		client_fd = PTR_ERR(new_file); | ||||||
| 	} else { | 	} else { | ||||||
| 		fd_install(client_fd, new_file); | 		*file = new_file; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return client_fd; | 	return client_fd; | ||||||
|  | @ -106,13 +108,15 @@ static int create_fd(struct fsnotify_group *group, struct fsnotify_event *event) | ||||||
| 
 | 
 | ||||||
| static int fill_event_metadata(struct fsnotify_group *group, | static int fill_event_metadata(struct fsnotify_group *group, | ||||||
| 				   struct fanotify_event_metadata *metadata, | 				   struct fanotify_event_metadata *metadata, | ||||||
| 				   struct fsnotify_event *event) | 				   struct fsnotify_event *event, | ||||||
|  | 				   struct file **file) | ||||||
| { | { | ||||||
| 	int ret = 0; | 	int ret = 0; | ||||||
| 
 | 
 | ||||||
| 	pr_debug("%s: group=%p metadata=%p event=%p\n", __func__, | 	pr_debug("%s: group=%p metadata=%p event=%p\n", __func__, | ||||||
| 		 group, metadata, event); | 		 group, metadata, event); | ||||||
| 
 | 
 | ||||||
|  | 	*file = NULL; | ||||||
| 	metadata->event_len = FAN_EVENT_METADATA_LEN; | 	metadata->event_len = FAN_EVENT_METADATA_LEN; | ||||||
| 	metadata->metadata_len = FAN_EVENT_METADATA_LEN; | 	metadata->metadata_len = FAN_EVENT_METADATA_LEN; | ||||||
| 	metadata->vers = FANOTIFY_METADATA_VERSION; | 	metadata->vers = FANOTIFY_METADATA_VERSION; | ||||||
|  | @ -121,7 +125,7 @@ static int fill_event_metadata(struct fsnotify_group *group, | ||||||
| 	if (unlikely(event->mask & FAN_Q_OVERFLOW)) | 	if (unlikely(event->mask & FAN_Q_OVERFLOW)) | ||||||
| 		metadata->fd = FAN_NOFD; | 		metadata->fd = FAN_NOFD; | ||||||
| 	else { | 	else { | ||||||
| 		metadata->fd = create_fd(group, event); | 		metadata->fd = create_fd(group, event, file); | ||||||
| 		if (metadata->fd < 0) | 		if (metadata->fd < 0) | ||||||
| 			ret = metadata->fd; | 			ret = metadata->fd; | ||||||
| 	} | 	} | ||||||
|  | @ -220,25 +224,6 @@ static int prepare_for_access_response(struct fsnotify_group *group, | ||||||
| 	return 0; | 	return 0; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void remove_access_response(struct fsnotify_group *group, |  | ||||||
| 				   struct fsnotify_event *event, |  | ||||||
| 				   __s32 fd) |  | ||||||
| { |  | ||||||
| 	struct fanotify_response_event *re; |  | ||||||
| 
 |  | ||||||
| 	if (!(event->mask & FAN_ALL_PERM_EVENTS)) |  | ||||||
| 		return; |  | ||||||
| 
 |  | ||||||
| 	re = dequeue_re(group, fd); |  | ||||||
| 	if (!re) |  | ||||||
| 		return; |  | ||||||
| 
 |  | ||||||
| 	BUG_ON(re->event != event); |  | ||||||
| 
 |  | ||||||
| 	kmem_cache_free(fanotify_response_event_cache, re); |  | ||||||
| 
 |  | ||||||
| 	return; |  | ||||||
| } |  | ||||||
| #else | #else | ||||||
| static int prepare_for_access_response(struct fsnotify_group *group, | static int prepare_for_access_response(struct fsnotify_group *group, | ||||||
| 				       struct fsnotify_event *event, | 				       struct fsnotify_event *event, | ||||||
|  | @ -247,12 +232,6 @@ static int prepare_for_access_response(struct fsnotify_group *group, | ||||||
| 	return 0; | 	return 0; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void remove_access_response(struct fsnotify_group *group, |  | ||||||
| 				   struct fsnotify_event *event, |  | ||||||
| 				   __s32 fd) |  | ||||||
| { |  | ||||||
| 	return; |  | ||||||
| } |  | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| static ssize_t copy_event_to_user(struct fsnotify_group *group, | static ssize_t copy_event_to_user(struct fsnotify_group *group, | ||||||
|  | @ -260,31 +239,33 @@ static ssize_t copy_event_to_user(struct fsnotify_group *group, | ||||||
| 				  char __user *buf) | 				  char __user *buf) | ||||||
| { | { | ||||||
| 	struct fanotify_event_metadata fanotify_event_metadata; | 	struct fanotify_event_metadata fanotify_event_metadata; | ||||||
|  | 	struct file *f; | ||||||
| 	int fd, ret; | 	int fd, ret; | ||||||
| 
 | 
 | ||||||
| 	pr_debug("%s: group=%p event=%p\n", __func__, group, event); | 	pr_debug("%s: group=%p event=%p\n", __func__, group, event); | ||||||
| 
 | 
 | ||||||
| 	ret = fill_event_metadata(group, &fanotify_event_metadata, event); | 	ret = fill_event_metadata(group, &fanotify_event_metadata, event, &f); | ||||||
| 	if (ret < 0) | 	if (ret < 0) | ||||||
| 		goto out; | 		goto out; | ||||||
| 
 | 
 | ||||||
| 	fd = fanotify_event_metadata.fd; | 	fd = fanotify_event_metadata.fd; | ||||||
|  | 	ret = -EFAULT; | ||||||
|  | 	if (copy_to_user(buf, &fanotify_event_metadata, | ||||||
|  | 			 fanotify_event_metadata.event_len)) | ||||||
|  | 		goto out_close_fd; | ||||||
|  | 
 | ||||||
| 	ret = prepare_for_access_response(group, event, fd); | 	ret = prepare_for_access_response(group, event, fd); | ||||||
| 	if (ret) | 	if (ret) | ||||||
| 		goto out_close_fd; | 		goto out_close_fd; | ||||||
| 
 | 
 | ||||||
| 	ret = -EFAULT; | 	fd_install(fd, f); | ||||||
| 	if (copy_to_user(buf, &fanotify_event_metadata, |  | ||||||
| 			 fanotify_event_metadata.event_len)) |  | ||||||
| 		goto out_kill_access_response; |  | ||||||
| 
 |  | ||||||
| 	return fanotify_event_metadata.event_len; | 	return fanotify_event_metadata.event_len; | ||||||
| 
 | 
 | ||||||
| out_kill_access_response: |  | ||||||
| 	remove_access_response(group, event, fd); |  | ||||||
| out_close_fd: | out_close_fd: | ||||||
| 	if (fd != FAN_NOFD) | 	if (fd != FAN_NOFD) { | ||||||
| 		sys_close(fd); | 		put_unused_fd(fd); | ||||||
|  | 		fput(f); | ||||||
|  | 	} | ||||||
| out: | out: | ||||||
| #ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS | #ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS | ||||||
| 	if (event->mask & FAN_ALL_PERM_EVENTS) { | 	if (event->mask & FAN_ALL_PERM_EVENTS) { | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Al Viro
				Al Viro