freebsd-nq/tools/test/callout_free/callout_free.c
Eric van Gyzen 8c5a9161d1 Save the last callout function executed on each CPU
Save the last callout function pointer (and its argument) executed
on each CPU for inspection by a debugger.  Add a ddb `show callout_last`
command to show these pointers.  Add a kernel module that I used
for testing that command.

Relocate `ce_migration_cpu` to reduce padding and therefore preserve
the size of `struct callout_cpu` (320 bytes on amd64) despite the
added members.

This should help diagnose reference-after-free bugs where the
callout's mutex has already been freed when `softclock_call_cc`
tries to unlock it.

You might hope that the pointer would still be available, but it
isn't.  The argument to that function is on the stack (because
`softclock_call_cc` uses it later), and that might be enough in
some cases, but even then, it's very laborious.  A pointer to the
callout is saved right before these newly added fields, but that
callout might have been freed.  We still have the pointer to its
associated mutex, and the name within might be enough, but it might
also have been freed.

Reviewed by:	markj jhb
MFC after:	2 weeks
Sponsored by:	Dell EMC Isilon
Differential Revision:	https://reviews.freebsd.org/D20794
2019-07-03 19:22:44 +00:00

88 lines
2.6 KiB
C

/*-
* SPDX-License-Identifier: BSD-2-Clause
*
* Copyright (c) 2019 Eric van Gyzen
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $FreeBSD$
*/
/*
* Free a pending callout. This was useful for testing the
* "show callout_last" ddb command.
*/
#include <sys/param.h>
#include <sys/conf.h>
#include <sys/kernel.h>
#include <sys/lock.h>
#include <sys/module.h>
#include <sys/mutex.h>
#include <sys/systm.h>
static struct callout callout_free;
static struct mtx callout_free_mutex;
static int callout_free_arg;
static void
callout_free_func(void *arg)
{
printf("squirrel!\n");
mtx_destroy(&callout_free_mutex);
memset(&callout_free, 'C', sizeof(callout_free));
}
static int
callout_free_load(module_t mod, int cmd, void *arg)
{
int error;
switch (cmd) {
case MOD_LOAD:
mtx_init(&callout_free_mutex, "callout_free", NULL, MTX_DEF);
/*
* Do not pass CALLOUT_RETURNUNLOCKED so the callout
* subsystem will unlock the "destroyed" mutex.
*/
callout_init_mtx(&callout_free, &callout_free_mutex, 0);
printf("callout_free_func = %p\n", callout_free_func);
printf("callout_free_arg = %p\n", &callout_free_arg);
callout_reset(&callout_free, hz/10, callout_free_func,
&callout_free_arg);
error = 0;
break;
case MOD_UNLOAD:
error = 0;
break;
default:
error = EOPNOTSUPP;
break;
}
return (error);
}
DEV_MODULE(callout_free, callout_free_load, NULL);