Skip site navigation (1)Skip section navigation (2)
Date:      Wed, 5 Dec 2018 13:16:39 +0000 (UTC)
From:      Slava Shwartsman <slavash@FreeBSD.org>
To:        src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org
Subject:   svn commit: r341518 - in head/sys/compat/linuxkpi/common: include/linux src
Message-ID:  <201812051316.wB5DGdXo063881@repo.freebsd.org>

next in thread | raw e-mail | index | archive | help
Author: slavash
Date: Wed Dec  5 13:16:39 2018
New Revision: 341518
URL: https://svnweb.freebsd.org/changeset/base/341518

Log:
  linuxkpi: Fix for use-after-free when tearing down character devices.
  
  Make sure we hold a reference on the character device for every opened file
  to prevent the character device to be freed prematurely.
  
  Submitted by:   hselasky@
  Approved by:    hselasky (mentor)
  MFC after:      1 week
  Sponsored by:   Mellanox Technologies

Modified:
  head/sys/compat/linuxkpi/common/include/linux/cdev.h
  head/sys/compat/linuxkpi/common/include/linux/fs.h
  head/sys/compat/linuxkpi/common/src/linux_compat.c

Modified: head/sys/compat/linuxkpi/common/include/linux/cdev.h
==============================================================================
--- head/sys/compat/linuxkpi/common/include/linux/cdev.h	Wed Dec  5 13:15:57 2018	(r341517)
+++ head/sys/compat/linuxkpi/common/include/linux/cdev.h	Wed Dec  5 13:16:39 2018	(r341518)
@@ -36,6 +36,8 @@
 #include <linux/kdev_t.h>
 #include <linux/list.h>
 
+#include <asm/atomic-long.h>
+
 struct file_operations;
 struct inode;
 struct module;
@@ -50,6 +52,7 @@ struct linux_cdev {
 	struct cdev	*cdev;
 	dev_t		dev;
 	const struct file_operations *ops;
+	atomic_long_t	refs;
 };
 
 static inline void
@@ -58,6 +61,7 @@ cdev_init(struct linux_cdev *cdev, const struct file_o
 
 	kobject_init(&cdev->kobj, &linux_cdev_static_ktype);
 	cdev->ops = ops;
+	atomic_long_set(&cdev->refs, 0);
 }
 
 static inline struct linux_cdev *
@@ -130,13 +134,13 @@ cdev_add_ext(struct linux_cdev *cdev, dev_t dev, uid_t
 	return (0);
 }
 
+void linux_destroy_dev(struct linux_cdev *);
+
 static inline void
 cdev_del(struct linux_cdev *cdev)
 {
-	if (cdev->cdev) {
-		destroy_dev(cdev->cdev);
-		cdev->cdev = NULL;
-	}
+
+	linux_destroy_dev(cdev);
 	kobject_put(&cdev->kobj);
 }
 

Modified: head/sys/compat/linuxkpi/common/include/linux/fs.h
==============================================================================
--- head/sys/compat/linuxkpi/common/include/linux/fs.h	Wed Dec  5 13:15:57 2018	(r341517)
+++ head/sys/compat/linuxkpi/common/include/linux/fs.h	Wed Dec  5 13:16:39 2018	(r341518)
@@ -2,7 +2,7 @@
  * Copyright (c) 2010 Isilon Systems, Inc.
  * Copyright (c) 2010 iX Systems, Inc.
  * Copyright (c) 2010 Panasas, Inc.
- * Copyright (c) 2013-2017 Mellanox Technologies, Ltd.
+ * Copyright (c) 2013-2018 Mellanox Technologies, Ltd.
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -55,6 +55,7 @@ struct vm_area_struct;
 struct poll_table_struct;
 struct files_struct;
 struct pfs_node;
+struct linux_cdev;
 
 #define	inode	vnode
 #define	i_cdev	v_rdev
@@ -105,6 +106,9 @@ struct linux_file {
 	/* protects f_selinfo.si_note */
 	spinlock_t	f_kqlock;
 	struct linux_file_wait_queue f_wait_queue;
+
+	/* pointer to associated character device, if any */
+	struct linux_cdev *f_cdev;
 };
 
 #define	file		linux_file

Modified: head/sys/compat/linuxkpi/common/src/linux_compat.c
==============================================================================
--- head/sys/compat/linuxkpi/common/src/linux_compat.c	Wed Dec  5 13:15:57 2018	(r341517)
+++ head/sys/compat/linuxkpi/common/src/linux_compat.c	Wed Dec  5 13:16:39 2018	(r341518)
@@ -699,12 +699,20 @@ linux_dev_fdopen(struct cdev *dev, int fflags, struct 
 	filp->f_flags = file->f_flag;
 	filp->f_vnode = file->f_vnode;
 	filp->_file = file;
+	filp->f_cdev = ldev;
 
 	linux_set_current(td);
 
+	/* get a reference on the Linux character device */
+	if (atomic_long_add_unless(&ldev->refs, 1, -1L) == 0) {
+		kfree(filp);
+		return (EINVAL);
+	}
+
 	if (filp->f_op->open) {
 		error = -filp->f_op->open(file->f_vnode, filp);
 		if (error) {
+			atomic_long_dec(&ldev->refs);
 			kfree(filp);
 			return (error);
 		}
@@ -1396,6 +1404,10 @@ linux_file_close(struct file *file, struct thread *td)
 	funsetown(&filp->f_sigio);
 	if (filp->f_vnode != NULL)
 		vdrop(filp->f_vnode);
+	if (filp->f_cdev != NULL) {
+		/* put a reference on the Linux character device */
+		atomic_long_dec(&filp->f_cdev->refs);
+	}
 	kfree(filp);
 
 	return (error);
@@ -1947,8 +1959,7 @@ linux_cdev_release(struct kobject *kobj)
 
 	cdev = container_of(kobj, struct linux_cdev, kobj);
 	parent = kobj->parent;
-	if (cdev->cdev)
-		destroy_dev(cdev->cdev);
+	linux_destroy_dev(cdev);
 	kfree(cdev);
 	kobject_put(parent);
 }
@@ -1961,9 +1972,25 @@ linux_cdev_static_release(struct kobject *kobj)
 
 	cdev = container_of(kobj, struct linux_cdev, kobj);
 	parent = kobj->parent;
-	if (cdev->cdev)
-		destroy_dev(cdev->cdev);
+	linux_destroy_dev(cdev);
 	kobject_put(parent);
+}
+
+void
+linux_destroy_dev(struct linux_cdev *cdev)
+{
+
+	if (cdev->cdev == NULL)
+		return;
+
+	atomic_long_dec(&cdev->refs);
+
+	/* wait for all open files to be closed */
+	while (atomic_long_read(&cdev->refs) != -1L)
+		pause("ldevdrn", hz);
+
+	destroy_dev(cdev->cdev);
+	cdev->cdev = NULL;
 }
 
 const struct kobj_type linux_cdev_ktype = {



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201812051316.wB5DGdXo063881>