DF-4958 / trig_leak.c
/* * DF-4958 trigger KLD: drives the *unmodified* tree code in * /usr/src/sys/dev/misc/led/led.c (compiled into this .ko verbatim via * SRCS=led.c) through both leak paths of led_parse(): * * 1. kernel API path : hw.ledleak.setn=N -> N x led_set("leaktest", "s") * (led.c:262 led_parse -> empty spec -> led.c:202-206 leaks the sbuf) * 2. cdev path : userland wr(1) writes "sz" to /dev/led/leaktest * (led.c:222 led_write -> led_parse -> same leak) * * hw.ledleak.create=1 creates the LED via led_create() so the real * /dev/led/leaktest node exists (mode 0600, root) — exactly the node * acpi_asus(4)/acpi_thinkpad(4) create on real hardware. * * Every leaked iteration costs sizeof(struct sbuf) (48 bytes) + the * initial 16-byte buffer (subr_sbuf.c:53-54,79,137: SBMALLOC/M_SBUF, * SBUF_MINEXTENDSIZE=16) = 64 bytes of M_SBUF per call, none of which * is ever sbuf_delete()d. */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/module.h> #include <sys/conf.h> #include <sys/sysctl.h> #include <sys/systm.h> #include <sys/malloc.h> #include <dev/misc/led/led.h> static struct cdev *leak_dev; static void noop_led(void *priv __unused, int on __unused) { /* the LED framework only needs a callable target */ } static int ledleak_create(SYSCTL_HANDLER_ARGS) { int v = 0, error; error = sysctl_handle_int(oidp, &v, 0, req); if (error != 0 || req->newptr == NULL) return (error); if (leak_dev == NULL) { leak_dev = led_create(noop_led, NULL, "leaktest"); kprintf("ledleak: created /dev/led/leaktest (%p)\n", leak_dev); } return (0); } static int ledleak_setn(SYSCTL_HANDLER_ARGS) { int v = 0, error, i; error = sysctl_handle_int(oidp, &v, 0, req); if (error != 0 || req->newptr == NULL) return (error); kprintf("ledleak: calling led_set(\"leaktest\", \"s\") x %d " "(each leaks one sbuf: led_parse led.c:202-206)\n", v); for (i = 0; i < v; i++) led_set("leaktest", "s"); kprintf("ledleak: done: %d sbufs leaked\n", v); return (0); } static int ledleak_modevent(module_t mod, int type, void *arg) { static struct sysctl_ctx_list ctx; switch (type) { case MOD_LOAD: sysctl_ctx_init(&ctx); SYSCTL_ADD_PROC(NULL, SYSCTL_STATIC_CHILDREN(_hw), OID_AUTO, "ledleak_create", CTLTYPE_INT | CTLFLAG_RW, 0, 0, ledleak_create, "I", "create /dev/led/leaktest"); SYSCTL_ADD_PROC(NULL, SYSCTL_STATIC_CHILDREN(_hw), OID_AUTO, "ledleak_setn", CTLTYPE_INT | CTLFLAG_RW, 0, 0, ledleak_setn, "I", "call led_set(name,\"s\") N times (leak N sbufs)"); kprintf("ledleak: loaded (tree led.c compiled in)\n"); return (0); case MOD_UNLOAD: if (leak_dev != NULL) led_destroy(leak_dev); sysctl_ctx_free(&ctx); return (0); } return (0); } DEV_MODULE(ledleak, ledleak_modevent, NULL); |