git: 2407abc9fb3b - main - witness: harden tunables for large settings
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Mon, 29 Jun 2026 17:13:21 UTC
The branch main has been updated by rlibby:
URL: https://cgit.FreeBSD.org/src/commit/?id=2407abc9fb3b45d6d734d564706c76aa793b102a
commit 2407abc9fb3b45d6d734d564706c76aa793b102a
Author: Ryan Libby <rlibby@FreeBSD.org>
AuthorDate: 2026-06-29 16:54:30 +0000
Commit: Ryan Libby <rlibby@FreeBSD.org>
CommitDate: 2026-06-29 16:54:30 +0000
witness: harden tunables for large settings
Harden witness against reasonable tunable settings causing boot panics
or scribblers. For now, don't bother to harden this completely. Panics
are still achievable by setting the tunables to negative or very large
values.
The recent change to respect the setting of debug.witness.witness_count
(08180f1b613b) exposed that there are no guard rails on that setting,
setting it to large but plausible values can cause integer overflow or
cause attempted use of memory beyond what is actually available. Add
some guard rails, fall back to default configuration if the tuned values
are too large, and fall back to a minimum memory allocation if even the
defaults are too large. Also don't allow witness to use over half of
the memory segment, as there are other early allocators after witness
too.
Reported by: asomers
Reviewed by: kib, markj (previous version)
Fixes: 08180f1b613b ("witness: actually set read-only tunables in time for witness_startup")
Sponsored by: Dell Inc.
Differential Revision: https://reviews.freebsd.org/D57793
---
sys/kern/subr_witness.c | 67 ++++++++++++++++++++++++++++++++++++++++++++-----
sys/sys/lock.h | 2 +-
sys/vm/vm_page.c | 11 ++++++--
3 files changed, 71 insertions(+), 9 deletions(-)
diff --git a/sys/kern/subr_witness.c b/sys/kern/subr_witness.c
index 21d015c4f381..52f8fa51fd25 100644
--- a/sys/kern/subr_witness.c
+++ b/sys/kern/subr_witness.c
@@ -419,19 +419,16 @@ static u_long witness_count = WITNESS_COUNT;
SYSCTL_ULONG(_debug_witness, OID_AUTO, witness_count,
CTLFLAG_RDTUN | CTLFLAG_NOFETCH, &witness_count, 0,
"Maximum count of lock type entries");
-TUNABLE_ULONG("debug.witness.witness_count", &witness_count);
static u_long witness_lo_data_count = WITNESS_LO_DATA_COUNT;
SYSCTL_ULONG(_debug_witness, OID_AUTO, lock_order_data_count,
CTLFLAG_RDTUN | CTLFLAG_NOFETCH, &witness_lo_data_count, 0,
"Maximum count of lock order data (stacks) to track");
-TUNABLE_ULONG("debug.witness.lock_order_data_count", &witness_lo_data_count);
static u_long witness_lo_hash_size = WITNESS_LO_HASH_SIZE;
SYSCTL_ULONG(_debug_witness, OID_AUTO, lock_order_hash_size,
CTLFLAG_RDTUN | CTLFLAG_NOFETCH, &witness_lo_hash_size, 0,
"Hash table size for lock order data");
-TUNABLE_ULONG("debug.witness.lock_order_hash_size", &witness_lo_hash_size);
/*
* Output channel for witness messages. By default we print to the console.
@@ -489,6 +486,7 @@ static struct witness *w_data;
static uint8_t **w_rmatrix;
static struct lock_list_entry w_locklistdata[LOCK_CHILDCOUNT];
static struct witness_hash w_hash; /* The witness hash table. */
+static u_long w_sz; /* Witness startup memory allocation size */
/* The lock order data hash */
static struct witness_lock_order_data *w_lodata;
@@ -791,10 +789,10 @@ fixup_filename(const char *file)
/*
* Calculate the size of early witness structures.
*/
-int
-witness_startup_count(void)
+static u_long
+witness_startup_calc(void)
{
- int sz;
+ u_long sz;
sz = sizeof(struct witness) * witness_count;
sz += sizeof(*w_rmatrix) * (witness_count + 1);
@@ -803,10 +801,65 @@ witness_startup_count(void)
sz += sizeof(void *);
sz += sizeof(w_lodata[0]) * witness_lo_data_count;
sz += sizeof(w_lohash.wloh_array[0]) * witness_lo_hash_size;
+ sz = round_page(sz);
return (sz);
}
+u_long
+witness_startup_count(u_long avail)
+{
+
+ /*
+ * Tune witness. We make an effort to protect against misconfiguration
+ * consuming more memory than available, but we do not robustly protect
+ * against integer overflow for all possible user-supplied values.
+ */
+ TUNABLE_ULONG_FETCH("debug.witness.witness_count", &witness_count);
+ witness_count = ulmax(witness_count, 1);
+ TUNABLE_ULONG_FETCH("debug.witness.lock_order_data_count",
+ &witness_lo_data_count);
+ TUNABLE_ULONG_FETCH("debug.witness.lock_order_hash_size",
+ &witness_lo_hash_size);
+ w_sz = witness_startup_calc();
+ if (bootverbose)
+ printf("WITNESS configuration requests %lu KiB "
+ "of startup allocations with witness_count=%lu, "
+ "lock_order_data_count=%lu, lock_order_hash_size=%lu\n",
+ w_sz / 1024, witness_count, witness_lo_data_count,
+ witness_lo_hash_size);
+ if (w_sz <= avail)
+ return (w_sz);
+
+ /* Memory allocation would be too large, try fallbacks. */
+ printf("WARNING: WITNESS configuration requests %lu KiB, "
+ "with %lu KiB available\n", w_sz / 1024, avail / 1024);
+ witness_count = ulmin(witness_count, WITNESS_COUNT);
+ witness_lo_data_count = ulmin(witness_lo_data_count,
+ WITNESS_LO_DATA_COUNT);
+ witness_lo_hash_size = ulmin(witness_lo_hash_size,
+ WITNESS_LO_HASH_SIZE);
+ w_sz = witness_startup_calc();
+ if (w_sz <= avail) {
+ printf("WARNING: WITNESS configuration reduced to defaults\n");
+ return (w_sz);
+ }
+
+ /* Minimize startup allocations, functionally disabling witness. */
+ witness_count = 1;
+ witness_lo_data_count = 0;
+ witness_lo_hash_size = 1;
+ w_sz = witness_startup_calc();
+ if (w_sz <= avail) {
+ printf("WARNING: WITNESS configuration defaults too large, "
+ "lock order checks disabled\n");
+ return (w_sz);
+ }
+
+ panic("WITNESS unable to initialize with %lu KiB available",
+ avail / 1024);
+}
+
/*
* The WITNESS-enabled diagnostic code. Note that the witness code does
* assume that the early boot is single-threaded at least until after this
@@ -843,6 +896,8 @@ witness_startup(void *mem)
w_lohash.wloh_array = (void *)p;
p += sizeof(w_lohash.wloh_array[0]) * witness_lo_hash_size;
+ MPASS(p <= (uintptr_t)mem + w_sz);
+
badstack_sbuf_size = witness_count * 256;
/*
diff --git a/sys/sys/lock.h b/sys/sys/lock.h
index 9d81a49ab52a..74e222176b4a 100644
--- a/sys/sys/lock.h
+++ b/sys/sys/lock.h
@@ -247,7 +247,7 @@ const char *witness_file(struct lock_object *);
void witness_thread_exit(struct thread *);
#ifdef WITNESS
-int witness_startup_count(void);
+u_long witness_startup_count(u_long);
void witness_startup(void *);
/* Flags for witness_warn(). */
diff --git a/sys/vm/vm_page.c b/sys/vm/vm_page.c
index 233292bdd6cb..8c953149e6de 100644
--- a/sys/vm/vm_page.c
+++ b/sys/vm/vm_page.c
@@ -585,7 +585,7 @@ vm_page_startup(vm_offset_t vaddr)
int biggestone, i, segind;
#ifdef WITNESS
void *mapped;
- int witness_size;
+ u_long witness_size;
#endif
#if defined(__i386__) && defined(VM_PHYSSEG_DENSE)
long ii;
@@ -610,7 +610,14 @@ vm_page_startup(vm_offset_t vaddr)
new_end = end;
#ifdef WITNESS
- witness_size = round_page(witness_startup_count());
+ /*
+ * witness(4) support. Allocate and map memory and initialize.
+ * Advertised available memory is limited in order to avoid witness
+ * misconfiguration consuming memory needed for subsequent essential
+ * allocations.
+ */
+ witness_size = round_page(witness_startup_count(
+ trunc_page((new_end - phys_avail[biggestone]) / 2)));
new_end -= witness_size;
mapped = pmap_map(&vaddr, new_end, new_end + witness_size,
VM_PROT_READ | VM_PROT_WRITE);