forked from mirrors/linux
		
	ipc/shm: handle removed segments gracefully in shm_mmap()
remap_file_pages(2) emulation can reach file which represents removed
IPC ID as long as a memory segment is mapped.  It breaks expectations of
IPC subsystem.
Test case (rewritten to be more human readable, originally autogenerated
by syzkaller[1]):
	#define _GNU_SOURCE
	#include <stdlib.h>
	#include <sys/ipc.h>
	#include <sys/mman.h>
	#include <sys/shm.h>
	#define PAGE_SIZE 4096
	int main()
	{
		int id;
		void *p;
		id = shmget(IPC_PRIVATE, 3 * PAGE_SIZE, 0);
		p = shmat(id, NULL, 0);
		shmctl(id, IPC_RMID, NULL);
		remap_file_pages(p, 3 * PAGE_SIZE, 0, 7, 0);
	        return 0;
	}
The patch changes shm_mmap() and code around shm_lock() to propagate
locking error back to caller of shm_mmap().
[1] http://github.com/google/syzkaller
Signed-off-by: Kirill A. Shutemov <kirill.shutemov@linux.intel.com>
Reported-by: Dmitry Vyukov <dvyukov@google.com>
Cc: Davidlohr Bueso <dave@stgolabs.net>
Cc: Manfred Spraul <manfred@colorfullife.com>
Cc: <stable@vger.kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
			
			
This commit is contained in:
		
							parent
							
								
									64f0085001
								
							
						
					
					
						commit
						1ac0b6dec6
					
				
					 1 changed files with 44 additions and 11 deletions
				
			
		
							
								
								
									
										55
									
								
								ipc/shm.c
									
									
									
									
									
								
							
							
						
						
									
										55
									
								
								ipc/shm.c
									
									
									
									
									
								
							|  | @ -156,11 +156,12 @@ static inline struct shmid_kernel *shm_lock(struct ipc_namespace *ns, int id) | ||||||
| 	struct kern_ipc_perm *ipcp = ipc_lock(&shm_ids(ns), id); | 	struct kern_ipc_perm *ipcp = ipc_lock(&shm_ids(ns), id); | ||||||
| 
 | 
 | ||||||
| 	/*
 | 	/*
 | ||||||
| 	 * We raced in the idr lookup or with shm_destroy().  Either way, the | 	 * Callers of shm_lock() must validate the status of the returned ipc | ||||||
| 	 * ID is busted. | 	 * object pointer (as returned by ipc_lock()), and error out as | ||||||
|  | 	 * appropriate. | ||||||
| 	 */ | 	 */ | ||||||
| 	WARN_ON(IS_ERR(ipcp)); | 	if (IS_ERR(ipcp)) | ||||||
| 
 | 		return (void *)ipcp; | ||||||
| 	return container_of(ipcp, struct shmid_kernel, shm_perm); | 	return container_of(ipcp, struct shmid_kernel, shm_perm); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -186,18 +187,33 @@ static inline void shm_rmid(struct ipc_namespace *ns, struct shmid_kernel *s) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| /* This is called by fork, once for every shm attach. */ | static int __shm_open(struct vm_area_struct *vma) | ||||||
| static void shm_open(struct vm_area_struct *vma) |  | ||||||
| { | { | ||||||
| 	struct file *file = vma->vm_file; | 	struct file *file = vma->vm_file; | ||||||
| 	struct shm_file_data *sfd = shm_file_data(file); | 	struct shm_file_data *sfd = shm_file_data(file); | ||||||
| 	struct shmid_kernel *shp; | 	struct shmid_kernel *shp; | ||||||
| 
 | 
 | ||||||
| 	shp = shm_lock(sfd->ns, sfd->id); | 	shp = shm_lock(sfd->ns, sfd->id); | ||||||
|  | 
 | ||||||
|  | 	if (IS_ERR(shp)) | ||||||
|  | 		return PTR_ERR(shp); | ||||||
|  | 
 | ||||||
| 	shp->shm_atim = get_seconds(); | 	shp->shm_atim = get_seconds(); | ||||||
| 	shp->shm_lprid = task_tgid_vnr(current); | 	shp->shm_lprid = task_tgid_vnr(current); | ||||||
| 	shp->shm_nattch++; | 	shp->shm_nattch++; | ||||||
| 	shm_unlock(shp); | 	shm_unlock(shp); | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* This is called by fork, once for every shm attach. */ | ||||||
|  | static void shm_open(struct vm_area_struct *vma) | ||||||
|  | { | ||||||
|  | 	int err = __shm_open(vma); | ||||||
|  | 	/*
 | ||||||
|  | 	 * We raced in the idr lookup or with shm_destroy(). | ||||||
|  | 	 * Either way, the ID is busted. | ||||||
|  | 	 */ | ||||||
|  | 	WARN_ON_ONCE(err); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /*
 | /*
 | ||||||
|  | @ -260,6 +276,14 @@ static void shm_close(struct vm_area_struct *vma) | ||||||
| 	down_write(&shm_ids(ns).rwsem); | 	down_write(&shm_ids(ns).rwsem); | ||||||
| 	/* remove from the list of attaches of the shm segment */ | 	/* remove from the list of attaches of the shm segment */ | ||||||
| 	shp = shm_lock(ns, sfd->id); | 	shp = shm_lock(ns, sfd->id); | ||||||
|  | 
 | ||||||
|  | 	/*
 | ||||||
|  | 	 * We raced in the idr lookup or with shm_destroy(). | ||||||
|  | 	 * Either way, the ID is busted. | ||||||
|  | 	 */ | ||||||
|  | 	if (WARN_ON_ONCE(IS_ERR(shp))) | ||||||
|  | 		goto done; /* no-op */ | ||||||
|  | 
 | ||||||
| 	shp->shm_lprid = task_tgid_vnr(current); | 	shp->shm_lprid = task_tgid_vnr(current); | ||||||
| 	shp->shm_dtim = get_seconds(); | 	shp->shm_dtim = get_seconds(); | ||||||
| 	shp->shm_nattch--; | 	shp->shm_nattch--; | ||||||
|  | @ -267,6 +291,7 @@ static void shm_close(struct vm_area_struct *vma) | ||||||
| 		shm_destroy(ns, shp); | 		shm_destroy(ns, shp); | ||||||
| 	else | 	else | ||||||
| 		shm_unlock(shp); | 		shm_unlock(shp); | ||||||
|  | done: | ||||||
| 	up_write(&shm_ids(ns).rwsem); | 	up_write(&shm_ids(ns).rwsem); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -388,17 +413,25 @@ static int shm_mmap(struct file *file, struct vm_area_struct *vma) | ||||||
| 	struct shm_file_data *sfd = shm_file_data(file); | 	struct shm_file_data *sfd = shm_file_data(file); | ||||||
| 	int ret; | 	int ret; | ||||||
| 
 | 
 | ||||||
| 	ret = sfd->file->f_op->mmap(sfd->file, vma); | 	/*
 | ||||||
| 	if (ret != 0) | 	 * In case of remap_file_pages() emulation, the file can represent | ||||||
|  | 	 * removed IPC ID: propogate shm_lock() error to caller. | ||||||
|  | 	 */ | ||||||
|  | 	ret =__shm_open(vma); | ||||||
|  | 	if (ret) | ||||||
| 		return ret; | 		return ret; | ||||||
|  | 
 | ||||||
|  | 	ret = sfd->file->f_op->mmap(sfd->file, vma); | ||||||
|  | 	if (ret) { | ||||||
|  | 		shm_close(vma); | ||||||
|  | 		return ret; | ||||||
|  | 	} | ||||||
| 	sfd->vm_ops = vma->vm_ops; | 	sfd->vm_ops = vma->vm_ops; | ||||||
| #ifdef CONFIG_MMU | #ifdef CONFIG_MMU | ||||||
| 	WARN_ON(!sfd->vm_ops->fault); | 	WARN_ON(!sfd->vm_ops->fault); | ||||||
| #endif | #endif | ||||||
| 	vma->vm_ops = &shm_vm_ops; | 	vma->vm_ops = &shm_vm_ops; | ||||||
| 	shm_open(vma); | 	return 0; | ||||||
| 
 |  | ||||||
| 	return ret; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static int shm_release(struct inode *ino, struct file *file) | static int shm_release(struct inode *ino, struct file *file) | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue
	
	 Kirill A. Shutemov
						Kirill A. Shutemov