Skip to content

Commit 75f0f6e

Browse files
committed
render: carry the hole-aware slot count into json and markdown
The memory-KASLR windows (direct map, vmalloc, vmemmap) exported only min/max, so a machine consumer could see the window but not how many candidates it holds. That number is not derivable downstream: interior C_EXCLUDE holes are carved at read time inside quantity_slots() and never reach the wire, so (max - min) / align yields the hole-blind figure with no route to the real one. Emit slots and entropy_bits on each json region and in the markdown row, matching what the text readout already prints. The speculative sub-window gains its own slots for the same reason: json exported likely.min and likely.max while dropping how many candidates survive inside them. The regression test seeds an eight-slot-wide window with a stored count of seven, so a renderer that recomputed from the bounds emits nine and fails.
1 parent 5b1b10e commit 75f0f6e

3 files changed

Lines changed: 73 additions & 13 deletions

File tree

src/render/json.c

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -397,16 +397,22 @@ void render_json(const struct summary *s) {
397397
struct {
398398
const char *name;
399399
unsigned long min, max, lmin, lmax;
400+
unsigned long slots, lslots;
401+
int bits;
400402
} regions[] = {
401403
{"virt_page_offset_base", s->kaslr.virt_page_offset_min,
402404
s->kaslr.virt_page_offset_max, s->kaslr.virt_page_offset_likely_min,
403-
s->kaslr.virt_page_offset_likely_max},
405+
s->kaslr.virt_page_offset_likely_max, s->kaslr.virt_page_offset_slots,
406+
s->kaslr.virt_page_offset_likely_slots,
407+
s->kaslr.virt_page_offset_bits},
404408
{"virt_vmalloc_base", s->kaslr.virt_vmalloc_min,
405409
s->kaslr.virt_vmalloc_max, s->kaslr.virt_vmalloc_likely_min,
406-
s->kaslr.virt_vmalloc_likely_max},
410+
s->kaslr.virt_vmalloc_likely_max, s->kaslr.virt_vmalloc_slots,
411+
s->kaslr.virt_vmalloc_likely_slots, s->kaslr.virt_vmalloc_bits},
407412
{"virt_vmemmap_base", s->kaslr.virt_vmemmap_min,
408413
s->kaslr.virt_vmemmap_max, s->kaslr.virt_vmemmap_likely_min,
409-
s->kaslr.virt_vmemmap_likely_max},
414+
s->kaslr.virt_vmemmap_likely_max, s->kaslr.virt_vmemmap_slots,
415+
s->kaslr.virt_vmemmap_likely_slots, s->kaslr.virt_vmemmap_bits},
410416
};
411417
for (size_t i = 0; i < sizeof(regions) / sizeof(regions[0]); i++) {
412418
if (!regions[i].min && !regions[i].max)
@@ -422,13 +428,23 @@ void render_json(const struct summary *s) {
422428
printf("\"0x%016lx\"", regions[i].max);
423429
else
424430
printf("null");
431+
/* The hole-aware candidate count and its residual entropy. A consumer
432+
* cannot derive these from min/max: interior C_EXCLUDE holes are carved
433+
* at read time inside quantity_slots() and never appear on the wire, so
434+
* (max - min) / align is the hole-blind number, not this one. */
435+
if (regions[i].slots > 0)
436+
printf(", \"slots\": %lu, \"entropy_bits\": %d", regions[i].slots,
437+
regions[i].bits);
425438
/* Speculative sub-window from the all-signals snapshot; subset of
426439
* [min, max] and may be wrong. Absent unless a sub-floor signal narrowed
427440
* the region. */
428-
if (regions[i].lmax || regions[i].lmin)
429-
printf(", \"likely\": { \"min\": \"0x%016lx\", \"max\": \"0x%016lx\", "
430-
"\"speculative\": true }",
441+
if (regions[i].lmax || regions[i].lmin) {
442+
printf(", \"likely\": { \"min\": \"0x%016lx\", \"max\": \"0x%016lx\"",
431443
regions[i].lmin, regions[i].lmax);
444+
if (regions[i].lslots > 0)
445+
printf(", \"slots\": %lu", regions[i].lslots);
446+
printf(", \"speculative\": true }");
447+
}
432448
printf(" }");
433449
first = 0;
434450
}

src/render/markdown.c

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,8 @@ static void print_group_sources(enum kasld_addr_type type,
7373
* arch-specific RANDOMIZE_MEMORY_ALIGN, which the table omits for brevity). */
7474
static void md_memory_kaslr_row(const char *name, unsigned long min,
7575
unsigned long max, unsigned long lmin,
76-
unsigned long lmax) {
76+
unsigned long lmax, unsigned long slots,
77+
int bits) {
7778
if (!min && !max)
7879
return;
7980
if (min && max && min > max)
@@ -84,6 +85,12 @@ static void md_memory_kaslr_row(const char *name, unsigned long min,
8485
printf("| %s | <= `0x%016lx` |\n", name, max);
8586
else if (min == max)
8687
printf("| %s | `0x%016lx` (pinned) |\n", name, min);
88+
else if (slots > 0)
89+
/* The hole-aware candidate count and its residual entropy, which a reader
90+
* cannot derive from the bounds: interior excludes are carved at read time
91+
* and never surface, so (max - min) / align is the hole-blind figure. */
92+
printf("| %s | `0x%016lx` - `0x%016lx` (%lu slots, ~%d bits) |\n", name,
93+
min, max, slots, bits);
8794
else
8895
printf("| %s | `0x%016lx` - `0x%016lx` |\n", name, min, max);
8996
/* Speculative sub-window from the all-signals snapshot (subset of the row
@@ -304,16 +311,19 @@ void render_markdown(const struct summary *s) {
304311
printf("| Phys/Virt coupling | %s |\n", kasld_coupling_descr());
305312

306313
/* Memory KASLR (CONFIG_RANDOMIZE_MEMORY) region bounds. */
307-
md_memory_kaslr_row("Direct map base", s->kaslr.virt_page_offset_min,
308-
s->kaslr.virt_page_offset_max,
309-
s->kaslr.virt_page_offset_likely_min,
310-
s->kaslr.virt_page_offset_likely_max);
314+
md_memory_kaslr_row(
315+
"Direct map base", s->kaslr.virt_page_offset_min,
316+
s->kaslr.virt_page_offset_max, s->kaslr.virt_page_offset_likely_min,
317+
s->kaslr.virt_page_offset_likely_max, s->kaslr.virt_page_offset_slots,
318+
s->kaslr.virt_page_offset_bits);
311319
md_memory_kaslr_row(
312320
"vmalloc base", s->kaslr.virt_vmalloc_min, s->kaslr.virt_vmalloc_max,
313-
s->kaslr.virt_vmalloc_likely_min, s->kaslr.virt_vmalloc_likely_max);
321+
s->kaslr.virt_vmalloc_likely_min, s->kaslr.virt_vmalloc_likely_max,
322+
s->kaslr.virt_vmalloc_slots, s->kaslr.virt_vmalloc_bits);
314323
md_memory_kaslr_row(
315324
"vmemmap base", s->kaslr.virt_vmemmap_min, s->kaslr.virt_vmemmap_max,
316-
s->kaslr.virt_vmemmap_likely_min, s->kaslr.virt_vmemmap_likely_max);
325+
s->kaslr.virt_vmemmap_likely_min, s->kaslr.virt_vmemmap_likely_max,
326+
s->kaslr.virt_vmemmap_slots, s->kaslr.virt_vmemmap_bits);
317327

318328
printf("\n");
319329
}

tests/test_render.c

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -828,6 +828,39 @@ static void test_render_memory_kaslr_uses_stored_slots(void) {
828828
assert(strstr(render_cap, "7 slots") != NULL);
829829
}
830830

831+
/* json and markdown must carry the hole-aware candidate count for the
832+
* memory-KASLR windows. A consumer cannot recompute it: interior C_EXCLUDE
833+
* holes are carved at read time inside quantity_slots() and never reach the
834+
* wire, so (max - min) / align is the hole-blind figure, not this one. */
835+
static void test_render_memory_kaslr_slots_reach_machine_formats(void) {
836+
#if RANDOMIZE_MEMORY_ALIGN > 0
837+
struct summary s;
838+
reset_results();
839+
num_comp_logs = 0;
840+
num_scalar_facts = 0;
841+
memset(&s, 0, sizeof(s));
842+
s.kaslr.vslots = 60;
843+
s.kaslr.vbits = 6;
844+
unsigned long align = (unsigned long)RANDOMIZE_MEMORY_ALIGN;
845+
s.kaslr.virt_page_offset_min = (unsigned long)PAGE_OFFSET_BASE_L4;
846+
s.kaslr.virt_page_offset_max =
847+
(unsigned long)PAGE_OFFSET_BASE_L4 + 8ul * align;
848+
s.kaslr.virt_page_offset_slots = 7; /* engine value; a naive width gives 9 */
849+
s.kaslr.virt_page_offset_bits = 3;
850+
851+
set_render_mode(1, 0, 0); /* json */
852+
capture_stdout(wrap_render_summary, &s);
853+
assert(strstr(render_cap, "\"slots\": 7") != NULL);
854+
assert(strstr(render_cap, "\"entropy_bits\": 3") != NULL);
855+
856+
set_render_mode(0, 0, 1); /* markdown */
857+
capture_stdout(wrap_render_summary, &s);
858+
assert(strstr(render_cap, "7 slots") != NULL);
859+
assert(strstr(render_cap, "~3 bits") != NULL);
860+
set_render_mode(0, 0, 0);
861+
#endif
862+
}
863+
831864
/* A KASLR-disabled base is a proven pin, not a speculative "likely" value: the
832865
* word "Likely" must not prefix the kernel image base. */
833866
static void test_render_disabled_base_not_labeled_likely(void) {
@@ -2495,6 +2528,7 @@ int main(void) {
24952528
RUN(test_render_directmap_base_promoted);
24962529
RUN(test_render_directmap_base_promoted_unbounded);
24972530
RUN(test_render_entropy_states_its_baseline);
2531+
RUN(test_render_memory_kaslr_slots_reach_machine_formats);
24982532
RUN(test_render_window_row_always_graded);
24992533
RUN(test_render_coupling_gated);
25002534
RUN(test_render_markdown_text_order_caution);

0 commit comments

Comments
 (0)