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
This commit is contained in:
parent
be34cfc587
commit
9c7b53cc65
@ -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_operations *ops)
|
||||
|
||||
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 uid, gid_t gid, int mode)
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -699,12 +699,20 @@ linux_dev_fdopen(struct cdev *dev, int fflags, struct thread *td, struct file *f
|
||||
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,11 +1972,27 @@ 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 = {
|
||||
.release = linux_cdev_release,
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user