mirror of
https://github.com/torvalds/linux.git
synced 2025-11-01 00:58:39 +02:00
A process might fail to allocate a new bitmap when trying to expand its
proc->dmap. In that case, dbitmap_grow() fails and frees the old bitmap
via dbitmap_free(). However, the driver calls dbitmap_free() again when
the same process terminates, leading to a double-free error:
==================================================================
BUG: KASAN: double-free in binder_proc_dec_tmpref+0x2e0/0x55c
Free of addr ffff00000b7c1420 by task kworker/9:1/209
CPU: 9 UID: 0 PID: 209 Comm: kworker/9:1 Not tainted 6.17.0-rc6-dirty #5 PREEMPT
Hardware name: linux,dummy-virt (DT)
Workqueue: events binder_deferred_func
Call trace:
kfree+0x164/0x31c
binder_proc_dec_tmpref+0x2e0/0x55c
binder_deferred_func+0xc24/0x1120
process_one_work+0x520/0xba4
[...]
Allocated by task 448:
__kmalloc_noprof+0x178/0x3c0
bitmap_zalloc+0x24/0x30
binder_open+0x14c/0xc10
[...]
Freed by task 449:
kfree+0x184/0x31c
binder_inc_ref_for_node+0xb44/0xe44
binder_transaction+0x29b4/0x7fbc
binder_thread_write+0x1708/0x442c
binder_ioctl+0x1b50/0x2900
[...]
==================================================================
Fix this issue by marking proc->map NULL in dbitmap_free().
Cc: stable@vger.kernel.org
Fixes: 15d9da3f81 ("binder: use bitmap for faster descriptor lookup")
Signed-off-by: Carlos Llamas <cmllamas@google.com>
Reviewed-by: Alice Ryhl <aliceryhl@google.com>
Reviewed-by: Tiffany Yang <ynaffit@google.com>
Link: https://lore.kernel.org/r/20250915221248.3470154-1-cmllamas@google.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
169 lines
4.1 KiB
C
169 lines
4.1 KiB
C
/* SPDX-License-Identifier: GPL-2.0-only */
|
|
/*
|
|
* Copyright 2024 Google LLC
|
|
*
|
|
* dbitmap - dynamically sized bitmap library.
|
|
*
|
|
* Used by the binder driver to optimize the allocation of the smallest
|
|
* available descriptor ID. Each bit in the bitmap represents the state
|
|
* of an ID.
|
|
*
|
|
* A dbitmap can grow or shrink as needed. This part has been designed
|
|
* considering that users might need to briefly release their locks in
|
|
* order to allocate memory for the new bitmap. These operations then,
|
|
* are verified to determine if the grow or shrink is sill valid.
|
|
*
|
|
* This library does not provide protection against concurrent access
|
|
* by itself. Binder uses the proc->outer_lock for this purpose.
|
|
*/
|
|
|
|
#ifndef _LINUX_DBITMAP_H
|
|
#define _LINUX_DBITMAP_H
|
|
#include <linux/bitmap.h>
|
|
|
|
#define NBITS_MIN BITS_PER_TYPE(unsigned long)
|
|
|
|
struct dbitmap {
|
|
unsigned int nbits;
|
|
unsigned long *map;
|
|
};
|
|
|
|
static inline int dbitmap_enabled(struct dbitmap *dmap)
|
|
{
|
|
return !!dmap->nbits;
|
|
}
|
|
|
|
static inline void dbitmap_free(struct dbitmap *dmap)
|
|
{
|
|
dmap->nbits = 0;
|
|
kfree(dmap->map);
|
|
dmap->map = NULL;
|
|
}
|
|
|
|
/* Returns the nbits that a dbitmap can shrink to, 0 if not possible. */
|
|
static inline unsigned int dbitmap_shrink_nbits(struct dbitmap *dmap)
|
|
{
|
|
unsigned int bit;
|
|
|
|
if (dmap->nbits <= NBITS_MIN)
|
|
return 0;
|
|
|
|
/*
|
|
* Determine if the bitmap can shrink based on the position of
|
|
* its last set bit. If the bit is within the first quarter of
|
|
* the bitmap then shrinking is possible. In this case, the
|
|
* bitmap should shrink to half its current size.
|
|
*/
|
|
bit = find_last_bit(dmap->map, dmap->nbits);
|
|
if (bit < (dmap->nbits >> 2))
|
|
return dmap->nbits >> 1;
|
|
|
|
/* find_last_bit() returns dmap->nbits when no bits are set. */
|
|
if (bit == dmap->nbits)
|
|
return NBITS_MIN;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Replace the internal bitmap with a new one of different size */
|
|
static inline void
|
|
dbitmap_replace(struct dbitmap *dmap, unsigned long *new, unsigned int nbits)
|
|
{
|
|
bitmap_copy(new, dmap->map, min(dmap->nbits, nbits));
|
|
kfree(dmap->map);
|
|
dmap->map = new;
|
|
dmap->nbits = nbits;
|
|
}
|
|
|
|
static inline void
|
|
dbitmap_shrink(struct dbitmap *dmap, unsigned long *new, unsigned int nbits)
|
|
{
|
|
if (!new)
|
|
return;
|
|
|
|
/*
|
|
* Verify that shrinking to @nbits is still possible. The @new
|
|
* bitmap might have been allocated without locks, so this call
|
|
* could now be outdated. In this case, free @new and move on.
|
|
*/
|
|
if (!dbitmap_enabled(dmap) || dbitmap_shrink_nbits(dmap) != nbits) {
|
|
kfree(new);
|
|
return;
|
|
}
|
|
|
|
dbitmap_replace(dmap, new, nbits);
|
|
}
|
|
|
|
/* Returns the nbits that a dbitmap can grow to. */
|
|
static inline unsigned int dbitmap_grow_nbits(struct dbitmap *dmap)
|
|
{
|
|
return dmap->nbits << 1;
|
|
}
|
|
|
|
static inline void
|
|
dbitmap_grow(struct dbitmap *dmap, unsigned long *new, unsigned int nbits)
|
|
{
|
|
/*
|
|
* Verify that growing to @nbits is still possible. The @new
|
|
* bitmap might have been allocated without locks, so this call
|
|
* could now be outdated. In this case, free @new and move on.
|
|
*/
|
|
if (!dbitmap_enabled(dmap) || nbits <= dmap->nbits) {
|
|
kfree(new);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Check for ENOMEM after confirming the grow operation is still
|
|
* required. This ensures we only disable the dbitmap when it's
|
|
* necessary. Once the dbitmap is disabled, binder will fallback
|
|
* to slow_desc_lookup_olocked().
|
|
*/
|
|
if (!new) {
|
|
dbitmap_free(dmap);
|
|
return;
|
|
}
|
|
|
|
dbitmap_replace(dmap, new, nbits);
|
|
}
|
|
|
|
/*
|
|
* Finds and sets the next zero bit in the bitmap. Upon success @bit
|
|
* is populated with the index and 0 is returned. Otherwise, -ENOSPC
|
|
* is returned to indicate that a dbitmap_grow() is needed.
|
|
*/
|
|
static inline int
|
|
dbitmap_acquire_next_zero_bit(struct dbitmap *dmap, unsigned long offset,
|
|
unsigned long *bit)
|
|
{
|
|
unsigned long n;
|
|
|
|
n = find_next_zero_bit(dmap->map, dmap->nbits, offset);
|
|
if (n == dmap->nbits)
|
|
return -ENOSPC;
|
|
|
|
*bit = n;
|
|
set_bit(n, dmap->map);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline void
|
|
dbitmap_clear_bit(struct dbitmap *dmap, unsigned long bit)
|
|
{
|
|
clear_bit(bit, dmap->map);
|
|
}
|
|
|
|
static inline int dbitmap_init(struct dbitmap *dmap)
|
|
{
|
|
dmap->map = bitmap_zalloc(NBITS_MIN, GFP_KERNEL);
|
|
if (!dmap->map) {
|
|
dmap->nbits = 0;
|
|
return -ENOMEM;
|
|
}
|
|
|
|
dmap->nbits = NBITS_MIN;
|
|
|
|
return 0;
|
|
}
|
|
#endif
|