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 = {
_______________________________________________
svn-src-head@freebsd.org mailing list
https://lists.freebsd.org/mailman/listinfo/svn-src-head
To unsubscribe, send any mail to "svn-src-head-unsubscr...@freebsd.org"

Reply via email to