Linux USB
 help / color / mirror / Atom feed
* usb: ohci: double dma_pool_destroy of td_cache when the second pool allocation fails in ohci_mem_init()
@ 2026-10-30 11:39 徐晗翔
  2026-10-30 11:46 ` Greg KH
  0 siblings, 1 reply; 2+ messages in thread
From: 徐晗翔 @ 2026-10-30 11:39 UTC (permalink / raw)
  To: stern, gregkh; +Cc: linux-usb, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 3906 bytes --]

When the second DMA pool allocation in ohci_mem_init() fails, the
error branch destroys the first pool (td_cache) but leaves
ohci->td_cache dangling, and ohci_init() then calls the FULL
ohci_stop() on the failure path -- whose ohci_mem_cleanup() destroys
the same pool a second time through freed memory: the second
dma_pool_destroy() re-does list_del on LIST_POISON'ed links (or
silently corrupts whatever now owns the reused slab object),
dereferences pool->dev, walks the freed page list double-freeing the
coherent DMA pages, and kfrees the pool object again.  I confirmed it
deterministically on a pristine Linux 7.3 KASAN kernel by injecting
exactly the allocation failure (kretprobe flipping the second
dma_pool_create() return to NULL, equivalent to slab fault
injection): the very next controller probe produced
"BUG: KASAN: slab-use-after-free in dma_pool_destroy" with the use
stack dma_pool_destroy <- ohci_stop <- ohci_init <- ohci_pci_reset <-
usb_add_hcd and the allocation stack kmalloc <- dma_pool_create_node
<- ohci_init.  The fix is one line (NULL the pointer), and a broader
cleanup would stop ohci_init()'s failure path from running a full
ohci_stop() that was designed for started controllers (it also calls
free_irq() with hcd->irq==0 at this stage, printing "Trying to free
already-free IRQ 0" on the same path).

The detailed description:

* The code (Linux 7.3)
  drivers/usb/host/ohci-mem.c:46-60 (ohci_mem_init):
      ohci->td_cache = dma_pool_create("ohci_td", ...);
      if (!ohci->td_cache)
              return -ENOMEM;
      ohci->ed_cache = dma_pool_create("ohci_ed", ...);
      if (!ohci->ed_cache) {
              dma_pool_destroy(ohci->td_cache);   /* NOT NULLed */
              return -ENOMEM;
      }
  drivers/usb/host/ohci-hcd.c:528-529 (ohci_init failure path):
      retval = ohci_mem_init(ohci);
      if (retval < 0)
              ohci_stop(hcd);            /* full stop on a never-started hc */
  and ohci_stop() ends with ohci_mem_cleanup() (ohci-hcd.c:1019),
  which destroys ohci->td_cache again (ohci-mem.c:67) -- ed_cache is
  NULL and safely skipped.

* What the second destroy does on freed memory (mm/dmapool.c:363-394)
  list_del(&pool->pools) on the first destroy's LIST_POISON (page
  fault) -- or, if the slab object was reused in between (ohci_stop
  does free_irq/remove_debug_files etc., which allocate), a silent
  list_del of an unrelated object's node; then
  list_empty(&pool->dev->dma_pools) and device_remove_file(pool->dev)
  as UAF reads, a walk of the freed page list double-freeing every
  coherent DMA page, and a second kfree(pool).

* Reachability and counting (closed)
  Any OHCI controller probe whose second pool allocation fails
  (memory pressure / fault injection) executes exactly two destroys:
  usb_add_hcd()'s .reset failure chain does not call .stop again
  (hcd.c:3003-3015), and the PCI probe failure path is
  goto put_hcd -> usb_put_hcd() (hcd-pci.c:275-284), not remove.
  The first-pool-fails variant is safe (early return, cleanup sees
  two NULLs).  ohci_restart() re-entering ohci_init() is safe
  (hcca != NULL early return, ohci-hcd.c:510).

* Evidence (pristine 7.3 KASAN, QEMU pci-ohci; instrument attached)
  ohcifail.c is a kretprobe module that flips the next
  dma_pool_create("ohci_ed") return to NULL -- the exact failure the
  error branch models; no kernel source is modified and the whole
  double-destroy path is native.  After insmod + unbind/bind of the
  OHCI device:
    BUG: KASAN: slab-use-after-free in dma_pool_destroy+0x3f
    Read of size 8 ... by task init
      dma_pool_destroy <- ohci_stop <- ohci_init
                        <- ohci_pci_reset <- usb_add_hcd
    Allocated: kmalloc <- dma_pool_create_node <- ohci_init (td_cache)
  Usage: insmod ohcifail.ko; echo <bdf> >
  /sys/bus/pci/drivers/ohci-pci/unbind; echo <bdf> > .../bind.



Regards,
Hanxiang Xu
xuhx@hust.edu.cn



[-- Attachment #2: ohcifail.c --]
[-- Type: text/plain, Size: 2914 bytes --]

// SPDX-License-Identifier: GPL-2.0
/*
 * ohcifail.c - BUG-1 (OHCI td_cache double dma_pool_destroy) trigger.
 *
 * Audit case: drivers/usb/host manual review 2026-10-29, BUG-1.
 *
 *   ohci_mem_init()            drivers/usb/host/ohci-mem.c
 *     td_cache = dma_pool_create("ohci_td")   -> OK
 *     ed_cache = dma_pool_create("ohci_ed")   -> FAILS
 *       dma_pool_destroy(td_cache);           [1st destroy, NOT nulled]
 *       return -ENOMEM
 *   ohci_init()                drivers/usb/host/ohci-hcd.c:528
 *     ohci_stop(hcd)
 *       ohci_mem_cleanup()
 *         dma_pool_destroy(td_cache)          [2nd destroy on freed pool]
 *
 * The "ed_cache creation fails" precondition is injected with a kretprobe on
 * dma_pool_create_node(): when name == "ohci_ed", the return handler rewrites
 * the return value to NULL. This models exactly the kmalloc/dma_alloc_coherent
 * failure inside the second dma_pool_create() (slab fault-injection outcome).
 * The kernel itself is NOT modified.
 *
 * Expected: on ohci-pci re-bind, KASAN reports use-after-free / double-free
 * in dma_pool_destroy() operating on the pool freed by the first destroy.
 */
#define pr_fmt(fmt) "OHCIFAIL: " fmt

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/kprobes.h>
#include <linux/string.h>

static char pool_name[32] = "ohci_ed";
module_param_string(pool_name, pool_name, sizeof(pool_name), 0600);
MODULE_PARM_DESC(pool_name, "dma_pool name whose creation is forced to fail");

static atomic_t n_fail;

static int entry_handler(struct kretprobe_instance *ri, struct pt_regs *regs)
{
	const char *name = (const char *)regs->di;
	int *m = (int *)ri->data;

	*m = 0;
	/* regs->di = first argument (const char *name) */
	if (name && !strncmp(name, pool_name, strlen(pool_name)))
		*m = 1;
	return 0;
}

static int return_handler(struct kretprobe_instance *ri, struct pt_regs *regs)
{
	int *m = (int *)ri->data;

	if (*m) {
		atomic_inc(&n_fail);
		pr_info("forcing %s creation failure #%d (original ret %px)\n",
			pool_name, atomic_read(&n_fail), (void *)regs->ax);
		regs->ax = 0;	/* return NULL */
	}
	return 0;
}

static struct kretprobe kretp = {
	.kp.symbol_name = "dma_pool_create_node",
	.entry_handler	= entry_handler,
	.handler	= return_handler,
	.data_size	= sizeof(int),
	.maxactive	= 20,
};

static int __init ohcifail_init(void)
{
	int ret = register_kretprobe(&kretp);

	if (ret) {
		pr_err("register_kretprobe(dma_pool_create_node) failed: %d\n", ret);
		return ret;
	}
	pr_info("armed: %s creation will fail on next ohci probe\n", pool_name);
	return 0;
}

static void __exit ohcifail_exit(void)
{
	unregister_kretprobe(&kretp);
	pr_info("removed, injected failures=%d\n", atomic_read(&n_fail));
}

module_init(ohcifail_init);
module_exit(ohcifail_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("BUG-1 OHCI td_cache double-destroy trigger (kretprobe fault injection)");

^ permalink raw reply	[flat|nested] 2+ messages in thread

* Re: usb: ohci: double dma_pool_destroy of td_cache when the second pool allocation fails in ohci_mem_init()
  2026-10-30 11:39 usb: ohci: double dma_pool_destroy of td_cache when the second pool allocation fails in ohci_mem_init() 徐晗翔
@ 2026-10-30 11:46 ` Greg KH
  0 siblings, 0 replies; 2+ messages in thread
From: Greg KH @ 2026-10-30 11:46 UTC (permalink / raw)
  To: 徐晗翔; +Cc: stern, linux-usb, linux-kernel

On Fri, Oct 30, 2026 at 07:39:07PM +0800, 徐晗翔 wrote:
> When the second DMA pool allocation in ohci_mem_init() fails, the
> error branch destroys the first pool (td_cache) but leaves
> ohci->td_cache dangling, and ohci_init() then calls the FULL
> ohci_stop() on the failure path -- whose ohci_mem_cleanup() destroys
> the same pool a second time through freed memory: the second
> dma_pool_destroy() re-does list_del on LIST_POISON'ed links (or
> silently corrupts whatever now owns the reused slab object),
> dereferences pool->dev, walks the freed page list double-freeing the
> coherent DMA pages, and kfrees the pool object again.  I confirmed it
> deterministically on a pristine Linux 7.3 KASAN kernel by injecting
> exactly the allocation failure (kretprobe flipping the second
> dma_pool_create() return to NULL, equivalent to slab fault
> injection): the very next controller probe produced
> "BUG: KASAN: slab-use-after-free in dma_pool_destroy" with the use
> stack dma_pool_destroy <- ohci_stop <- ohci_init <- ohci_pci_reset <-
> usb_add_hcd and the allocation stack kmalloc <- dma_pool_create_node
> <- ohci_init.  The fix is one line (NULL the pointer), and a broader
> cleanup would stop ohci_init()'s failure path from running a full
> ohci_stop() that was designed for started controllers (it also calls
> free_irq() with hcd->irq==0 at this stage, printing "Trying to free
> already-free IRQ 0" on the same path).

Great, please send a patch to fix this.  Creating random kernel modules
to break existing kernel modules is a bit odd of a thing to do :)

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2026-10-30 11:46 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-30 11:39 usb: ohci: double dma_pool_destroy of td_cache when the second pool allocation fails in ohci_mem_init() 徐晗翔
2026-10-30 11:46 ` Greg KH

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox