uma: Add KMSAN hooks

For now, just hook the allocation path: upon allocation, items are
marked as initialized (absent M_ZERO).  Some zones are exempted from
this when it would otherwise raise false positives.

Use kmsan_orig() to update the origin map for UMA and malloc(9)
allocations.  This allows KMSAN to print the return address when an
uninitialized UMA item is implicated in a report.  For example:
  panic: MSan: Uninitialized UMA memory from m_getm2+0x7fe

Sponsored by:	The FreeBSD Foundation
This commit is contained in:
Mark Johnston 2021-08-10 17:15:03 -04:00
parent 693c9516fa
commit 100949103a
4 changed files with 80 additions and 10 deletions

View File

@ -58,6 +58,7 @@ __FBSDID("$FreeBSD$");
#include <sys/kernel.h>
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/msan.h>
#include <sys/mutex.h>
#include <sys/vmmeter.h>
#include <sys/proc.h>
@ -654,8 +655,13 @@ void *
indx = kmemsize[size >> KMEM_ZSHIFT];
zone = kmemzones[indx].kz_zone[mtp_get_subzone(mtp)];
va = uma_zalloc(zone, flags);
if (va != NULL)
if (va != NULL) {
size = zone->uz_size;
if ((flags & M_ZERO) == 0) {
kmsan_mark(va, size, KMSAN_STATE_UNINIT);
kmsan_orig(va, size, KMSAN_TYPE_MALLOC, KMSAN_RET_ADDR);
}
}
malloc_type_zone_allocated(mtp, va == NULL ? 0 : size, indx);
if (__predict_false(va == NULL)) {
KASSERT((flags & M_WAITOK) == 0,
@ -735,6 +741,12 @@ malloc_domainset(size_t size, struct malloc_type *mtp, struct domainset *ds,
#ifdef KASAN
if (va != NULL)
kasan_mark((void *)va, osize, size, KASAN_MALLOC_REDZONE);
#endif
#ifdef KMSAN
if ((flags & M_ZERO) == 0) {
kmsan_mark(va, size, KMSAN_STATE_UNINIT);
kmsan_orig(va, size, KMSAN_TYPE_MALLOC, KMSAN_RET_ADDR);
}
#endif
return (va);
}
@ -1232,7 +1244,7 @@ mallocinit(void *dummy)
for (subzone = 0; subzone < numzones; subzone++) {
kmemzones[indx].kz_zone[subzone] =
uma_zcreate(name, size,
#if defined(INVARIANTS) && !defined(KASAN)
#if defined(INVARIANTS) && !defined(KASAN) && !defined(KMSAN)
mtrash_ctor, mtrash_dtor, mtrash_init, mtrash_fini,
#else
NULL, NULL, NULL, NULL,

View File

@ -683,7 +683,7 @@ mb_dtor_pack(void *mem, int size, void *arg)
KASSERT(m->m_ext.ext_arg2 == NULL, ("%s: ext_arg2 != NULL", __func__));
KASSERT(m->m_ext.ext_size == MCLBYTES, ("%s: ext_size != MCLBYTES", __func__));
KASSERT(m->m_ext.ext_type == EXT_PACKET, ("%s: ext_type != EXT_PACKET", __func__));
#ifdef INVARIANTS
#if defined(INVARIANTS) && !defined(KMSAN)
trash_dtor(m->m_ext.ext_buf, MCLBYTES, arg);
#endif
/*
@ -742,7 +742,7 @@ mb_zinit_pack(void *mem, int size, int how)
m->m_ext.ext_buf == NULL)
return (ENOMEM);
m->m_ext.ext_type = EXT_PACKET; /* Override. */
#ifdef INVARIANTS
#if defined(INVARIANTS) && !defined(KMSAN)
trash_init(m->m_ext.ext_buf, MCLBYTES, how);
#endif
return (0);
@ -758,11 +758,11 @@ mb_zfini_pack(void *mem, int size)
struct mbuf *m;
m = (struct mbuf *)mem;
#ifdef INVARIANTS
#if defined(INVARIANTS) && !defined(KMSAN)
trash_fini(m->m_ext.ext_buf, MCLBYTES);
#endif
uma_zfree_arg(zone_clust, m->m_ext.ext_buf, NULL);
#ifdef INVARIANTS
#if defined(INVARIANTS) && !defined(KMSAN)
trash_dtor(mem, size, NULL);
#endif
}
@ -784,7 +784,7 @@ mb_ctor_pack(void *mem, int size, void *arg, int how)
type = args->type;
MPASS((flags & M_NOFREE) == 0);
#ifdef INVARIANTS
#if defined(INVARIANTS) && !defined(KMSAN)
trash_ctor(m->m_ext.ext_buf, MCLBYTES, arg, how);
#endif

View File

@ -69,7 +69,7 @@ __FBSDID("$FreeBSD$");
#include <sys/malloc.h>
#include <sys/ktr.h>
#include <sys/lock.h>
#include <sys/sysctl.h>
#include <sys/msan.h>
#include <sys/mutex.h>
#include <sys/proc.h>
#include <sys/random.h>
@ -79,6 +79,7 @@ __FBSDID("$FreeBSD$");
#include <sys/sleepqueue.h>
#include <sys/smp.h>
#include <sys/smr.h>
#include <sys/sysctl.h>
#include <sys/taskqueue.h>
#include <sys/vmmeter.h>
@ -632,6 +633,60 @@ kasan_mark_slab_invalid(uma_keg_t keg __unused, void *mem __unused)
}
#endif /* KASAN */
#ifdef KMSAN
static inline void
kmsan_mark_item_uninitialized(uma_zone_t zone, void *item)
{
void *pcpu_item;
size_t sz;
int i;
if ((zone->uz_flags &
(UMA_ZFLAG_CACHE | UMA_ZONE_SECONDARY | UMA_ZONE_MALLOC)) != 0) {
/*
* Cache zones should not be instrumented by default, as UMA
* does not have enough information to do so correctly.
* Consumers can mark items themselves if it makes sense to do
* so.
*
* Items from secondary zones are initialized by the parent
* zone and thus cannot safely be marked by UMA.
*
* malloc zones are handled directly by malloc(9) and friends,
* since they can provide more precise origin tracking.
*/
return;
}
if (zone->uz_keg->uk_init != NULL) {
/*
* By definition, initialized items cannot be marked. The
* best we can do is mark items from these zones after they
* are freed to the keg.
*/
return;
}
sz = zone->uz_size;
if ((zone->uz_flags & UMA_ZONE_PCPU) == 0) {
kmsan_orig(item, sz, KMSAN_TYPE_UMA, KMSAN_RET_ADDR);
kmsan_mark(item, sz, KMSAN_STATE_UNINIT);
} else {
pcpu_item = zpcpu_base_to_offset(item);
for (i = 0; i <= mp_maxid; i++) {
kmsan_orig(zpcpu_get_cpu(pcpu_item, i), sz,
KMSAN_TYPE_UMA, KMSAN_RET_ADDR);
kmsan_mark(zpcpu_get_cpu(pcpu_item, i), sz,
KMSAN_STATE_INITED);
}
}
}
#else /* !KMSAN */
static inline void
kmsan_mark_item_uninitialized(uma_zone_t zone __unused, void *item __unused)
{
}
#endif /* KMSAN */
/*
* Acquire the domain lock and record contention.
*/
@ -2799,7 +2854,7 @@ zone_ctor(void *mem, int size, void *udata, int flags)
STAILQ_INIT(&zdom->uzd_buckets);
}
#if defined(INVARIANTS) && !defined(KASAN)
#if defined(INVARIANTS) && !defined(KASAN) && !defined(KMSAN)
if (arg->uminit == trash_init && arg->fini == trash_fini)
zone->uz_flags |= UMA_ZFLAG_TRASH | UMA_ZFLAG_CTORDTOR;
#elif defined(KASAN)
@ -3227,7 +3282,7 @@ uma_zcreate(const char *name, size_t size, uma_ctor ctor, uma_dtor dtor,
args.dtor = dtor;
args.uminit = uminit;
args.fini = fini;
#if defined(INVARIANTS) && !defined(KASAN)
#if defined(INVARIANTS) && !defined(KASAN) && !defined(KMSAN)
/*
* Inject procedures which check for memory use after free if we are
* allowed to scramble the memory while it is not allocated. This
@ -3387,6 +3442,7 @@ item_ctor(uma_zone_t zone, int uz_flags, int size, void *udata, int flags,
#endif
kasan_mark_item_valid(zone, item);
kmsan_mark_item_uninitialized(zone, item);
#ifdef INVARIANTS
skipdbg = uma_dbg_zskip(zone, item);

View File

@ -73,6 +73,7 @@ __FBSDID("$FreeBSD$");
#include <sys/limits.h>
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/msan.h>
#include <sys/mutex.h>
#include <sys/proc.h>
#include <sys/racct.h>
@ -387,6 +388,7 @@ vm_thread_new(struct thread *td, int pages)
td->td_kstack = ks;
td->td_kstack_pages = pages;
kasan_mark((void *)ks, ptoa(pages), ptoa(pages), 0);
kmsan_mark((void *)ks, ptoa(pages), KMSAN_STATE_UNINIT);
return (1);
}