Skip to content

Commit f0fe2c0

Browse files
Christian Braunergregkh
authored andcommitted
binder: prevent UAF for binderfs devices II
This is a necessary follow up to the first fix I proposed and we merged in 2669b8b ("binder: prevent UAF for binderfs devices"). I have been overly optimistic that the simple fix I proposed would work. But alas, ihold() + iput() won't work since the inodes won't survive the destruction of the superblock. So all we get with my prior fix is a different race with a tinier race-window but it doesn't solve the issue. Fwiw, the problem lies with generic_shutdown_super(). It even has this cozy Al-style comment: if (!list_empty(&sb->s_inodes)) { printk("VFS: Busy inodes after unmount of %s. " "Self-destruct in 5 seconds. Have a nice day...\n", sb->s_id); } On binder_release(), binder_defer_work(proc, BINDER_DEFERRED_RELEASE) is called which punts the actual cleanup operation to a workqueue. At some point, binder_deferred_func() will be called which will end up calling binder_deferred_release() which will retrieve and cleanup the binder_context attach to this struct binder_proc. If we trace back where this binder_context is attached to binder_proc we see that it is set in binder_open() and is taken from the struct binder_device it is associated with. This obviously assumes that the struct binder_device that context is attached to is _never_ freed. While that might be true for devtmpfs binder devices it is most certainly wrong for binderfs binder devices. So, assume binder_open() is called on a binderfs binder devices. We now stash away the struct binder_context associated with that struct binder_devices: proc->context = &binder_dev->context; /* binderfs stashes devices in i_private */ if (is_binderfs_device(nodp)) { binder_dev = nodp->i_private; info = nodp->i_sb->s_fs_info; binder_binderfs_dir_entry_proc = info->proc_log_dir; } else { . . . proc->context = &binder_dev->context; Now let's assume that the binderfs instance for that binder devices is shutdown via umount() and/or the mount namespace associated with it goes away. As long as there is still an fd open for that binderfs binder device things are fine. But let's assume we now close the last fd for that binderfs binder device. Now binder_release() is called and punts to the workqueue. Assume that the workqueue has quite a bit of stuff to do and doesn't get to cleaning up the struct binder_proc and the associated struct binder_context with it for that binderfs binder device right away. In the meantime, the VFS is killing the super block and is ultimately calling sb->evict_inode() which means it will call binderfs_evict_inode() which does: static void binderfs_evict_inode(struct inode *inode) { struct binder_device *device = inode->i_private; struct binderfs_info *info = BINDERFS_I(inode); clear_inode(inode); if (!S_ISCHR(inode->i_mode) || !device) return; mutex_lock(&binderfs_minors_mutex); --info->device_count; ida_free(&binderfs_minors, device->miscdev.minor); mutex_unlock(&binderfs_minors_mutex); kfree(device->context.name); kfree(device); } thereby freeing the struct binder_device including struct binder_context. Now the workqueue finally has time to get around to cleaning up struct binder_proc and is now trying to access the associate struct binder_context. Since it's already freed it will OOPs. Fix this by introducing a refounct on binder devices. This is an alternative fix to 51d8a7e ("binder: prevent UAF read in print_binder_transaction_log_entry()"). Fixes: 3ad20fe ("binder: implement binderfs") Fixes: 2669b8b ("binder: prevent UAF for binderfs devices") Fixes: 03e2e07 ("binder: Make transaction_log available in binderfs") Related : 51d8a7e ("binder: prevent UAF read in print_binder_transaction_log_entry()") Cc: [email protected] Signed-off-by: Christian Brauner <[email protected]> Acked-by: Todd Kjos <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Greg Kroah-Hartman <[email protected]>
1 parent 3791163 commit f0fe2c0

File tree

3 files changed

+16
-18
lines changed

3 files changed

+16
-18
lines changed

drivers/android/binder.c

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5221,13 +5221,14 @@ static int binder_open(struct inode *nodp, struct file *filp)
52215221
proc->default_priority = task_nice(current);
52225222
/* binderfs stashes devices in i_private */
52235223
if (is_binderfs_device(nodp)) {
5224-
binder_dev = binderfs_device_get(nodp->i_private);
5224+
binder_dev = nodp->i_private;
52255225
info = nodp->i_sb->s_fs_info;
52265226
binder_binderfs_dir_entry_proc = info->proc_log_dir;
52275227
} else {
52285228
binder_dev = container_of(filp->private_data,
52295229
struct binder_device, miscdev);
52305230
}
5231+
refcount_inc(&binder_dev->ref);
52315232
proc->context = &binder_dev->context;
52325233
binder_alloc_init(&proc->alloc);
52335234

@@ -5422,6 +5423,12 @@ static void binder_deferred_release(struct binder_proc *proc)
54225423
context->binder_context_mgr_node = NULL;
54235424
}
54245425
mutex_unlock(&context->context_mgr_node_lock);
5426+
device = container_of(proc->context, struct binder_device, context);
5427+
if (refcount_dec_and_test(&device->ref)) {
5428+
kfree(context->name);
5429+
kfree(device);
5430+
}
5431+
proc->context = NULL;
54255432
binder_inner_proc_lock(proc);
54265433
/*
54275434
* Make sure proc stays alive after we
@@ -5485,8 +5492,6 @@ static void binder_deferred_release(struct binder_proc *proc)
54855492
outgoing_refs, active_transactions);
54865493

54875494
binder_proc_dec_tmpref(proc);
5488-
device = container_of(proc->context, struct binder_device, context);
5489-
binderfs_device_put(device);
54905495
}
54915496

54925497
static void binder_deferred_func(struct work_struct *work)
@@ -6080,6 +6085,7 @@ static int __init init_binder_device(const char *name)
60806085
binder_device->miscdev.minor = MISC_DYNAMIC_MINOR;
60816086
binder_device->miscdev.name = name;
60826087

6088+
refcount_set(&binder_device->ref, 1);
60836089
binder_device->context.binder_context_mgr_uid = INVALID_UID;
60846090
binder_device->context.name = name;
60856091
mutex_init(&binder_device->context.context_mgr_node_lock);

drivers/android/binder_internal.h

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <linux/list.h>
99
#include <linux/miscdevice.h>
1010
#include <linux/mutex.h>
11+
#include <linux/refcount.h>
1112
#include <linux/stddef.h>
1213
#include <linux/types.h>
1314
#include <linux/uidgid.h>
@@ -33,21 +34,9 @@ struct binder_device {
3334
struct miscdevice miscdev;
3435
struct binder_context context;
3536
struct inode *binderfs_inode;
37+
refcount_t ref;
3638
};
3739

38-
static inline struct binder_device *binderfs_device_get(struct binder_device *dev)
39-
{
40-
if (dev->binderfs_inode)
41-
ihold(dev->binderfs_inode);
42-
return dev;
43-
}
44-
45-
static inline void binderfs_device_put(struct binder_device *dev)
46-
{
47-
if (dev->binderfs_inode)
48-
iput(dev->binderfs_inode);
49-
}
50-
5140
/**
5241
* binderfs_mount_opts - mount options for binderfs
5342
* @max: maximum number of allocatable binderfs binder devices

drivers/android/binderfs.c

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ static int binderfs_binder_device_create(struct inode *ref_inode,
154154
if (!name)
155155
goto err;
156156

157+
refcount_set(&device->ref, 1);
157158
device->binderfs_inode = inode;
158159
device->context.binder_context_mgr_uid = INVALID_UID;
159160
device->context.name = name;
@@ -257,8 +258,10 @@ static void binderfs_evict_inode(struct inode *inode)
257258
ida_free(&binderfs_minors, device->miscdev.minor);
258259
mutex_unlock(&binderfs_minors_mutex);
259260

260-
kfree(device->context.name);
261-
kfree(device);
261+
if (refcount_dec_and_test(&device->ref)) {
262+
kfree(device->context.name);
263+
kfree(device);
264+
}
262265
}
263266

264267
/**

0 commit comments

Comments
 (0)