Skip to content

Commit 50ee65d

Browse files
committed
check-results: validate scalars, RAM-map coverage, block-coarse RAM tops
Extend the validator beyond addresses and image_size now that more facts drive the inference and RAM maps flow through coverings. - scalar facts: validate phys_addr_bits / virt_addr_bits (cpuinfo), page_size (getconf), phys_max_pfn (iomem RAM top), efi_present (/sys/firmware/efi), phys_memtotal / physical_start / phys_kernel_align (iomem), and init_size — each bounded so only a bogus over-claim fails; guard against an empty or malformed value aborting the run under set -e - RAM-map coverage: every /proc/iomem System RAM extent must lie within the union of the pos=extent coverings, or a covering map is incomplete (the false-gap failure the coverings mechanism guards against) - P ram: accept a top that rounds up to the next memory-block boundary, as the block-coarse hotplug map (sysfs_memory_blocks) legitimately does
1 parent eaf7bc2 commit 50ee65d

1 file changed

Lines changed: 207 additions & 6 deletions

File tree

extra/check-results

Lines changed: 207 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,15 @@ hex_ge() { awk -v a="$1" -v b="$2" 'BEGIN { exit !(a >= b) }'; }
8686
# hex_in_range ADDR LO HI → return 0 (true) if LO <= ADDR <= HI
8787
hex_in_range() { hex_ge "$1" "$2" && hex_le "$1" "$3"; }
8888

89+
# covered_by_merged RLO RHI (merged disjoint runs "lo hi\n..." on stdin)
90+
# → return 0 (true) if some single run contains [RLO, RHI]. Inputs are decimal
91+
# integers; addresses fit in awk's exact-integer range (< 2^53). Because the
92+
# runs are merged-maximal and disjoint, [RLO,RHI] lies in the union iff one run
93+
# contains it — a span crossing two runs has a real gap between them.
94+
covered_by_merged() {
95+
awk -v rlo="$1" -v rhi="$2" '$1 <= rlo && $2 >= rhi { f = 1 } END { exit !f }'
96+
}
97+
8998
# Extract the value of a `key=value` token from a tagged line.
9099
# Usage: get_field LINE KEY → echoes VALUE (without 0x), empty if absent.
91100
get_field() {
@@ -235,6 +244,40 @@ if [ -n "$iomem_ram" ]; then
235244
truth_ram_hi=$(hex16 "$(printf '%s' "$last_ram" | sed 's/^ *//' | cut -d'-' -f2 | cut -d' ' -f1 | tr -d ' ')")
236245
fi
237246

247+
# Hotplug memory-block size. sysfs_memory_blocks reports RAM at this
248+
# granularity, so its highest online block legitimately rounds the RAM top up to
249+
# the next block boundary — which sits above the fine-grained /proc/iomem top.
250+
# block_ram_ceiling is that boundary; a P ram address between truth_ram_hi and
251+
# it is block-rounded (sound), anything beyond is a genuine out-of-RAM leak.
252+
block_ram_ceiling=""
253+
if [ -n "$truth_ram_hi" ]; then
254+
_bs=$(tr -d ' \n' < /sys/devices/system/memory/block_size_bytes 2>/dev/null || true)
255+
if [ -n "$_bs" ] && [ "0x$_bs" != "0x0" ]; then
256+
_bc=$(( ( (0x$truth_ram_hi / 0x$_bs) + 1 ) * 0x$_bs - 1 ))
257+
block_ram_ceiling=$(hex16 "$(printf '%x' "$_bc")")
258+
fi
259+
fi
260+
261+
# -------------------------------------------------------------------------
262+
# Scalar-fact ground truth (for the S <fact> validators below). Each source is
263+
# root-cheap and arch-portable: absent fields leave the truth empty, and the
264+
# validator SKIPs rather than fails. These facts now drive the inference
265+
# (phys_addr_bits -> phys_bits_ceiling, phys_memtotal -> ceiling, virt_addr_bits
266+
# / page_size -> paging level), so a wrong leak silently mis-bounds the window.
267+
# -------------------------------------------------------------------------
268+
# CPU address sizes (x86: "address sizes : N bits physical, M bits virtual").
269+
gt_phys_bits=""
270+
gt_virt_bits=""
271+
_as=$(grep -m1 'address sizes' /proc/cpuinfo 2>/dev/null || true)
272+
if [ -n "$_as" ]; then
273+
gt_phys_bits=$(printf '%s' "$_as" | sed -n 's/.*: *\([0-9]\{1,\}\) bits physical.*/\1/p')
274+
gt_virt_bits=$(printf '%s' "$_as" | sed -n 's/.*physical, *\([0-9]\{1,\}\) bits virtual.*/\1/p')
275+
fi
276+
# Page size (universal).
277+
gt_page_size=$(getconf PAGESIZE 2>/dev/null || getconf PAGE_SIZE 2>/dev/null || true)
278+
# EFI presence.
279+
if [ -d /sys/firmware/efi ]; then gt_efi=1; else gt_efi=0; fi
280+
238281
# -------------------------------------------------------------------------
239282
# Print ground truth
240283
# -------------------------------------------------------------------------
@@ -291,16 +334,113 @@ printf '%s\n' "$tagged_lines" | while IFS= read -r line; do
291334
# ceiling is a reliable plausibility bound for image_size.
292335
if [ "$type" = "S" ]; then
293336
value=$(get_field "$line" value)
337+
if [ -z "$value" ]; then
338+
skip "S $region_name — no value field"
339+
continue
340+
fi
341+
# Guard the arithmetic below: a malformed value (e.g. "0xZZ") would make
342+
# $(( value )) error and, under `set -e`, abort the whole validation. The
343+
# wire is hex per api.h; reject anything else rather than die mid-run.
344+
if ! printf '%s' "$value" | grep -qiE '^0x[0-9a-f]+$|^[0-9]+$'; then
345+
skip "S $region_name=$value — malformed value (not hex)"
346+
continue
347+
fi
294348
case "$region_name" in
295-
image_size)
296-
if [ -z "$value" ] || [ "$value" = "0x0" ] || [ "$value" = "0" ]; then
297-
skip "S image_size — zero/absent"
349+
image_size|init_size)
350+
# A kernel image (or its init region) cannot exceed the RAM it lives in.
351+
if [ "$value" = "0x0" ] || [ "$value" = "0" ]; then
352+
skip "S $region_name — zero"
298353
elif [ -z "$truth_ram_hi" ]; then
299-
skip "S image_size=$value — no /proc/iomem System RAM ground truth"
354+
skip "S $region_name=$value — no /proc/iomem System RAM ground truth"
300355
elif hex_le "$(hex16 "$value")" "$truth_ram_hi"; then
301-
pass "S image_size=$value — within RAM (top 0x$truth_ram_hi)"
356+
pass "S $region_name=$value — within RAM (top 0x$truth_ram_hi)"
357+
else
358+
fail "S $region_name=$value — exceeds all RAM (top 0x$truth_ram_hi); bogus"
359+
fi
360+
;;
361+
phys_addr_bits)
362+
if [ -z "$gt_phys_bits" ]; then
363+
skip "S phys_addr_bits=$value — no cpuinfo 'address sizes' ground truth"
364+
elif [ "$(( value ))" = "$gt_phys_bits" ]; then
365+
pass "S phys_addr_bits=$value — matches cpuinfo ($gt_phys_bits bits physical)"
366+
else
367+
fail "S phys_addr_bits=$value ($(( value ))) — cpuinfo says $gt_phys_bits bits physical"
368+
fi
369+
;;
370+
virt_addr_bits)
371+
if [ -z "$gt_virt_bits" ]; then
372+
skip "S virt_addr_bits=$value — no cpuinfo 'address sizes' ground truth"
373+
elif [ "$(( value ))" = "$gt_virt_bits" ]; then
374+
pass "S virt_addr_bits=$value — matches cpuinfo ($gt_virt_bits bits virtual)"
375+
else
376+
fail "S virt_addr_bits=$value ($(( value ))) — cpuinfo says $gt_virt_bits bits virtual"
377+
fi
378+
;;
379+
page_size)
380+
if [ -z "$gt_page_size" ]; then
381+
skip "S page_size=$value — getconf unavailable"
382+
elif [ "$(( value ))" = "$gt_page_size" ]; then
383+
pass "S page_size=$value — matches getconf PAGESIZE ($gt_page_size)"
384+
else
385+
fail "S page_size=$value ($(( value ))) — getconf PAGESIZE is $gt_page_size"
386+
fi
387+
;;
388+
efi_present)
389+
if [ "$(( value ))" = "$gt_efi" ]; then
390+
pass "S efi_present=$value — matches /sys/firmware/efi presence ($gt_efi)"
391+
else
392+
fail "S efi_present=$value — /sys/firmware/efi presence is $gt_efi"
393+
fi
394+
;;
395+
phys_max_pfn)
396+
if [ -z "$truth_ram_hi" ]; then
397+
skip "S phys_max_pfn=$value — no /proc/iomem System RAM"
398+
else
399+
_pfn_ram=$(( (0x$truth_ram_hi + 1) >> 12 ))
400+
if [ "$(( value ))" = "$_pfn_ram" ]; then
401+
pass "S phys_max_pfn=$value — matches RAM-top PFN"
402+
elif [ "$(( value ))" -ge "$_pfn_ram" ]; then
403+
pass "S phys_max_pfn=$value — at/above RAM-top PFN (reserved above RAM)"
404+
else
405+
fail "S phys_max_pfn=$value — below RAM-top PFN (0x$(printf '%x' "$_pfn_ram")); under-counts RAM"
406+
fi
407+
fi
408+
;;
409+
phys_memtotal)
410+
if [ -z "$truth_ram_lo" ]; then
411+
skip "S phys_memtotal=$value — no /proc/iomem System RAM"
412+
else
413+
# Usable RAM cannot exceed the physical RAM span (a too-LOW value only
414+
# under-bounds the ceiling, which is conservative — so bound above only).
415+
_ramspan=$(( 0x$truth_ram_hi - 0x$truth_ram_lo + 1 ))
416+
if [ "$(( value ))" -le "$_ramspan" ]; then
417+
pass "S phys_memtotal=$value — within physical RAM span"
418+
else
419+
fail "S phys_memtotal=$value — exceeds physical RAM span (0x$(printf '%x' "$_ramspan"))"
420+
fi
421+
fi
422+
;;
423+
physical_start)
424+
# The real phys base sits at physical_start + KASLR offset, so the
425+
# configured minimum start can never exceed the real base.
426+
if [ -z "$truth_ptext_lo" ]; then
427+
skip "S physical_start=$value — no /proc/iomem Kernel code base"
428+
elif hex_le "$(hex16 "$value")" "$truth_ptext_lo"; then
429+
pass "S physical_start=$value — at/below real phys base (0x$truth_ptext_lo)"
302430
else
303-
fail "S image_size=$value — exceeds all RAM (top 0x$truth_ram_hi); bogus"
431+
fail "S physical_start=$value — above real phys base (0x$truth_ptext_lo)"
432+
fi
433+
;;
434+
phys_kernel_align)
435+
# The real phys base must be a multiple of the KASLR physical alignment.
436+
if [ -z "$truth_ptext_lo" ]; then
437+
skip "S phys_kernel_align=$value — no /proc/iomem Kernel code base"
438+
elif [ "$(( value ))" -eq 0 ]; then
439+
skip "S phys_kernel_align=$value — zero"
440+
elif [ "$(( 0x$truth_ptext_lo % value ))" -eq 0 ]; then
441+
pass "S phys_kernel_align=$value — real phys base is $value-aligned"
442+
else
443+
fail "S phys_kernel_align=$value — real phys base 0x$truth_ptext_lo not $value-aligned"
304444
fi
305445
;;
306446
*)
@@ -487,6 +627,14 @@ printf '%s\n' "$tagged_lines" | while IFS= read -r line; do
487627
fi
488628
if hex_in_range "$addr" "$truth_ram_lo" "$truth_ram_hi"; then
489629
pass "$desc — within System RAM"
630+
elif [ "$region" = "ram" ] && [ -n "$block_ram_ceiling" ] && \
631+
hex_ge "$addr" "$truth_ram_hi" && \
632+
hex_le "$addr" "$block_ram_ceiling"; then
633+
# The hotplug-block map (sysfs_memory_blocks) is block-coarse: its top
634+
# online block rounds the RAM top up to the next block boundary, above
635+
# the fine /proc/iomem top. Sound — the extra slice carries no real RAM
636+
# and is never excluded — so accept up to the block boundary.
637+
pass "$desc — block-rounded RAM top (hotplug-block granularity)"
490638
else
491639
# Some regions (efi_memmap, acpi_*) may sit in reserved holes
492640
# outside the System RAM extents. Don't fail those — skip with a
@@ -540,6 +688,59 @@ phys_win=$(printf '%s\n' "$raw_input" | grep 'Inferred phys text range:' | head
540688
validate_inferred "Inferred virt text window" "$truth_stext" "$virt_win"
541689
validate_inferred "Inferred phys text window" "$truth_ptext_lo" "$phys_win"
542690

691+
# -------------------------------------------------------------------------
692+
# RAM-map coverage. A `pos=extent` line is one member of a COMPLETE single-source
693+
# RAM map (a covering); the gaps between a source's extents are what
694+
# ram_map_phys_exclude carves as non-RAM. So every /proc/iomem System RAM extent
695+
# MUST lie within the union of the coverings — a hole here means a covering map
696+
# is incomplete, which would carve a false non-RAM gap (the failure this whole
697+
# mechanism guards against).
698+
#
699+
# Coverage is one-directional (RAM ⊆ union): coverings may extend ABOVE real RAM
700+
# (the block-coarse hotplug map rounds up) — that only adds slack.
701+
#
702+
# The wire carries no origin, so a full `kasld -v` validates the AGGREGATE map.
703+
# To validate ONE source's completeness, pipe that component alone, e.g.:
704+
# ./build/*/components/firmware_memmap | sudo ./extra/check-results -
705+
# then every pos=extent line belongs to that single origin.
706+
# -------------------------------------------------------------------------
707+
coverings=$(printf '%s\n' "$tagged_lines" | awk '$1 == "P" && $2 == "ram"' \
708+
| grep 'pos=extent' || true)
709+
if [ -z "$coverings" ]; then
710+
: # no covering sources in this input — nothing to check
711+
elif [ -z "$truth_ram_lo" ]; then
712+
skip "RAM coverage — no /proc/iomem System RAM ground truth"
713+
else
714+
# Covering extents as decimal "lo hi", sorted by lo.
715+
cov_intervals=$(printf '%s\n' "$coverings" | while IFS= read -r cl; do
716+
clo=$(get_field "$cl" lo); chi=$(get_field "$cl" hi); csz=$(get_field "$cl" sz)
717+
# get_field yields the 0x-prefixed value, so use it directly in $(( )).
718+
[ -n "$clo" ] || continue
719+
if [ -z "$chi" ] && [ -n "$csz" ]; then chi=$(printf '0x%x' $(( clo + csz - 1 ))); fi
720+
[ -n "$chi" ] || continue
721+
printf '%d %d\n' "$(( clo ))" "$(( chi ))"
722+
done | sort -n)
723+
# Merge into disjoint maximal runs (touching/overlapping coalesced).
724+
merged=$(printf '%s\n' "$cov_intervals" | awk '
725+
NR == 1 { lo = $1; hi = $2; next }
726+
{ if ($1 <= hi + 1) { if ($2 > hi) hi = $2 }
727+
else { print lo, hi; lo = $1; hi = $2 } }
728+
END { if (NR > 0) print lo, hi }')
729+
# Each System RAM extent must sit inside one merged covering run.
730+
printf '%s\n' "$iomem_ram" | while IFS= read -r row; do
731+
[ -n "$row" ] || continue
732+
range=$(printf '%s' "$row" | sed 's/^ *//' | awk '{ print $1 }')
733+
rlo_h=$(printf '%s' "$range" | cut -d- -f1)
734+
rhi_h=$(printf '%s' "$range" | cut -d- -f2)
735+
if [ -z "$rlo_h" ] || [ -z "$rhi_h" ]; then continue; fi
736+
if printf '%s\n' "$merged" | covered_by_merged "$(( 0x$rlo_h ))" "$(( 0x$rhi_h ))"; then
737+
pass "RAM coverage — System RAM [0x$rlo_h-0x$rhi_h] within covering map"
738+
else
739+
fail "RAM coverage — System RAM [0x$rlo_h-0x$rhi_h] NOT covered (covering map incomplete)"
740+
fi
741+
done
742+
fi
743+
543744
# -------------------------------------------------------------------------
544745
# Summary
545746
# -------------------------------------------------------------------------

0 commit comments

Comments
 (0)