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:
Slava Shwartsman 2018-12-05 13:16:39 +00:00
parent be34cfc587
commit 9c7b53cc65
3 changed files with 44 additions and 9 deletions

View File

@ -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);
}

View File

@ -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

View 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,
};