svn commit: r325767 - head/sys/compat/linuxkpi/common/src

Hans Petter Selasky hselasky at FreeBSD.org
Mon Nov 13 18:16:28 UTC 2017


Author: hselasky
Date: Mon Nov 13 18:16:26 2017
New Revision: 325767
URL: https://svnweb.freebsd.org/changeset/base/325767

Log:
  Properly handle the case where the linux_cdev_handle_insert() function
  in the LinuxKPI returns NULL. This happens when the VM area's private
  data handle already exists and could cause a so-called NULL pointer
  dereferencing issue prior to this fix.
  
  Found by:	greg at unrelenting.technology
  MFC after:	1 week
  Sponsored by:	Mellanox Technologies

Modified:
  head/sys/compat/linuxkpi/common/src/linux_compat.c

Modified: head/sys/compat/linuxkpi/common/src/linux_compat.c
==============================================================================
--- head/sys/compat/linuxkpi/common/src/linux_compat.c	Mon Nov 13 17:46:38 2017	(r325766)
+++ head/sys/compat/linuxkpi/common/src/linux_compat.c	Mon Nov 13 18:16:26 2017	(r325767)
@@ -607,24 +607,6 @@ linux_cdev_handle_free(struct vm_area_struct *vmap)
 	kfree(vmap);
 }
 
-static struct vm_area_struct *
-linux_cdev_handle_insert(void *handle, struct vm_area_struct *vmap)
-{
-	struct vm_area_struct *ptr;
-
-	rw_wlock(&linux_vma_lock);
-	TAILQ_FOREACH(ptr, &linux_vma_head, vm_entry) {
-		if (ptr->vm_private_data == handle) {
-			rw_wunlock(&linux_vma_lock);
-			linux_cdev_handle_free(vmap);
-			return (NULL);
-		}
-	}
-	TAILQ_INSERT_TAIL(&linux_vma_head, vmap, vm_entry);
-	rw_wunlock(&linux_vma_lock);
-	return (vmap);
-}
-
 static void
 linux_cdev_handle_remove(struct vm_area_struct *vmap)
 {
@@ -1318,20 +1300,55 @@ linux_dev_mmap_single(struct cdev *dev, vm_ooffset_t *
 	attr = pgprot2cachemode(vmap->vm_page_prot);
 
 	if (vmap->vm_ops != NULL) {
+		struct vm_area_struct *ptr;
 		void *vm_private_data;
+		bool vm_no_fault;
 
 		if (vmap->vm_ops->open == NULL ||
 		    vmap->vm_ops->close == NULL ||
 		    vmap->vm_private_data == NULL) {
+			/* free allocated VM area struct */
 			linux_cdev_handle_free(vmap);
 			return (EINVAL);
 		}
 
 		vm_private_data = vmap->vm_private_data;
 
-		vmap = linux_cdev_handle_insert(vm_private_data, vmap);
+		rw_wlock(&linux_vma_lock);
+		TAILQ_FOREACH(ptr, &linux_vma_head, vm_entry) {
+			if (ptr->vm_private_data == vm_private_data)
+				break;
+		}
+		/* check if there is an existing VM area struct */
+		if (ptr != NULL) {
+			/* check if the VM area structure is invalid */
+			if (ptr->vm_ops == NULL ||
+			    ptr->vm_ops->open == NULL ||
+			    ptr->vm_ops->close == NULL) {
+				error = ESTALE;
+				vm_no_fault = 1;
+			} else {
+				error = EEXIST;
+				vm_no_fault = (ptr->vm_ops->fault == NULL);
+			}
+		} else {
+			/* insert VM area structure into list */
+			TAILQ_INSERT_TAIL(&linux_vma_head, vmap, vm_entry);
+			error = 0;
+			vm_no_fault = (vmap->vm_ops->fault == NULL);
+		}
+		rw_wunlock(&linux_vma_lock);
 
-		if (vmap->vm_ops->fault == NULL) {
+		if (error != 0) {
+			/* free allocated VM area struct */
+			linux_cdev_handle_free(vmap);
+			/* check for stale VM area struct */
+			if (error != EEXIST)
+				return (error);
+		}
+
+		/* check if there is no fault handler */
+		if (vm_no_fault) {
 			*object = cdev_pager_allocate(vm_private_data, OBJT_DEVICE,
 			    &linux_cdev_pager_ops[1], size, nprot, *offset,
 			    curthread->td_ucred);
@@ -1341,9 +1358,14 @@ linux_dev_mmap_single(struct cdev *dev, vm_ooffset_t *
 			    curthread->td_ucred);
 		}
 
+		/* check if allocating the VM object failed */
 		if (*object == NULL) {
-			linux_cdev_handle_remove(vmap);
-			linux_cdev_handle_free(vmap);
+			if (error == 0) {
+				/* remove VM area struct from list */
+				linux_cdev_handle_remove(vmap);
+				/* free allocated VM area struct */
+				linux_cdev_handle_free(vmap);
+			}
 			return (EINVAL);
 		}
 	} else {


More information about the svn-src-all mailing list