|
16 | 16 | # <type> <region>[:<name>] pos=<pos> conf=<conf> \ |
17 | 17 | # [lo=<hex>] [hi=<hex>|sz=<hex>] [sample=<hex>] [base_align=<hex>] |
18 | 18 | # |
19 | | -# type: P (physical), V (virtual), S (scalar-fact), D (derived) |
| 19 | +# type: P (physical), V (virtual), S (scalar-fact), R (disposition) |
20 | 20 | # region: closed enum (kernel_text, kernel_data, kernel_bss, |
21 | | -# kernel_image, module, module_region, ram, dma, dma32, |
| 21 | +# kernel_image, module, module_band, ram, dma, dma32, |
22 | 22 | # initrd, cmdline, cmdline_memmap, reserved_mem, swiotlb, |
23 | 23 | # vmcoreinfo, crashkernel, pmem, acpi_table, acpi_nvs, |
24 | 24 | # efi_memmap, numa_node, mmio, pci_mmio, directmap, |
@@ -144,7 +144,15 @@ numeric() { |
144 | 144 | # Usage: validate_inferred LABEL TRUTH16 RENDERED_LINE |
145 | 145 | validate_inferred() { |
146 | 146 | _label="$1"; _truth="$2"; _line="$3" |
147 | | - [ -n "$_line" ] || return # window not rendered (base leaked/pinned) |
| 147 | + # No window to check: the base was leaked or pinned outright, so nothing was |
| 148 | + # rendered to validate. Reported as a skip like every other inapplicable |
| 149 | + # check, and with an explicit status -- a bare `return` here carries the |
| 150 | + # status of the failed test above it, which under `set -e` ends the run at the |
| 151 | + # call site and takes the remaining checks and the summary with it. |
| 152 | + [ -n "$_line" ] || { |
| 153 | + skip "$_label — window not rendered (base leaked/pinned)" |
| 154 | + return 0 |
| 155 | + } |
148 | 156 | if [ -z "$_truth" ]; then |
149 | 157 | skip "$_label — no ground truth" |
150 | 158 | return |
@@ -186,9 +194,21 @@ else |
186 | 194 | exit 1 |
187 | 195 | fi |
188 | 196 |
|
| 197 | +# SECURITY: strip C0 control characters and DEL before any value is extracted. |
| 198 | +# Every field printed below comes from this input; the input is untrusted (its |
| 199 | +# natural source is the machine under assessment); and this script runs as root. |
| 200 | +# An escape sequence reaching the terminal can move the cursor, erase the line |
| 201 | +# just written and redraw it, so a FAIL can be made to read as a PASS by the |
| 202 | +# very report meant to expose it. Newline and tab are kept -- they separate |
| 203 | +# records and fields. Bytes >= 0x80 are kept too: they carry UTF-8, and deleting |
| 204 | +# them would corrupt text rather than protect anything. |
| 205 | +# @ctrl-filter -- the same property is asserted for extra/ksymoff, which strips |
| 206 | +# control characters from its file-derived fields; review the two together. |
| 207 | +raw_input=$(printf '%s\n' "$tagged_lines" | tr -d '\000-\010\013-\037\177') |
| 208 | + |
189 | 209 | # Filter to tagged lines only (drops `[infer]`, `[parser]`, banner, etc.) |
190 | | -raw_input="$tagged_lines" # full input, incl. the rendered summary lines |
191 | | -tagged_lines=$(printf '%s\n' "$raw_input" | grep '^[VPDS] ' || true) |
| 210 | +# raw_input is the full input, incl. the rendered summary lines. |
| 211 | +tagged_lines=$(printf '%s\n' "$raw_input" | grep '^[VPS] ' || true) |
192 | 212 |
|
193 | 213 | if [ -z "$tagged_lines" ]; then |
194 | 214 | echo "error: no tagged lines found" >&2 |
@@ -407,7 +427,7 @@ printf '%s\n' "$tagged_lines" | while IFS= read -r line; do |
407 | 427 | continue |
408 | 428 | fi |
409 | 429 | case "$region_name" in |
410 | | - image_size|init_size) |
| 430 | + image_size_min|image_size_max) |
411 | 431 | # A kernel image (or its init region) cannot exceed the RAM it lives in. |
412 | 432 | if [ "$value" = "0x0" ] || [ "$value" = "0" ]; then |
413 | 433 | skip "S $region_name — zero" |
@@ -481,6 +501,21 @@ printf '%s\n' "$tagged_lines" | while IFS= read -r line; do |
481 | 501 | fi |
482 | 502 | fi |
483 | 503 | ;; |
| 504 | + virt_page_offset) |
| 505 | + # The exact left edge of the linear map. Whatever emits this pins |
| 506 | + # PAGE_OFFSET outright rather than bounding it, so a wrong value |
| 507 | + # mis-places the direct map and, on the architectures where text rides |
| 508 | + # in it, kernel text with it. Ground truth is the same page_offset_base, |
| 509 | + # derived here from /proc/kcore by this script's own reader — so the |
| 510 | + # comparison is between two independent parses of the same bytes. |
| 511 | + if [ -z "$truth_page_offset" ]; then |
| 512 | + skip "S virt_page_offset=$value — no /proc/kcore page_offset_base" |
| 513 | + elif [ "$(hex16 "$value")" = "$truth_page_offset" ]; then |
| 514 | + pass "S virt_page_offset=$value — matches /proc/kcore page_offset_base" |
| 515 | + else |
| 516 | + fail "S virt_page_offset=$value — != page_offset_base 0x$truth_page_offset (/proc/kcore)" |
| 517 | + fi |
| 518 | + ;; |
484 | 519 | physical_start) |
485 | 520 | # The real phys base sits at physical_start + KASLR offset, so the |
486 | 521 | # configured minimum start can never exceed the real base. |
@@ -614,7 +649,7 @@ printf '%s\n' "$tagged_lines" | while IFS= read -r line; do |
614 | 649 | ;; |
615 | 650 |
|
616 | 651 | # ---- Virtual: modules ---------------------------------------------- |
617 | | - "V module"|"V module_region") |
| 652 | + "V module"|"V module_band") |
618 | 653 | if [ -z "$truth_mod_lo" ]; then |
619 | 654 | skip "$desc — no modules loaded" |
620 | 655 | continue |
@@ -666,7 +701,32 @@ printf '%s\n' "$tagged_lines" | while IFS= read -r line; do |
666 | 701 | ;; |
667 | 702 |
|
668 | 703 | "V virt_page_offset") |
669 | | - skip "$desc — informational (PAGE_OFFSET)" |
| 704 | + # The region names PAGE_OFFSET itself, so page_offset_base is the value |
| 705 | + # to compare against. What the record claims varies by form, and the |
| 706 | + # check follows it rather than assuming the tightest one: |
| 707 | + # both edges a window PAGE_OFFSET lies in -> truth must fall inside it |
| 708 | + # base only the base itself -> exact match |
| 709 | + # otherwise an address in the linear map -> truth is at or below it, |
| 710 | + # which also holds if the record turns out to be the base |
| 711 | + if [ -z "$truth_page_offset" ]; then |
| 712 | + skip "$desc — no /proc/kcore page_offset_base" |
| 713 | + elif [ -n "$hi" ]; then |
| 714 | + if hex_in_range "$truth_page_offset" "$addr" "$hi"; then |
| 715 | + pass "$desc — page_offset_base 0x$truth_page_offset within [0x$addr - 0x$hi]" |
| 716 | + else |
| 717 | + fail "$desc — page_offset_base 0x$truth_page_offset outside [0x$addr - 0x$hi]" |
| 718 | + fi |
| 719 | + elif [ "$pos" = "base" ]; then |
| 720 | + if [ "$addr" = "$truth_page_offset" ]; then |
| 721 | + pass "$desc — matches /proc/kcore page_offset_base" |
| 722 | + else |
| 723 | + fail "$desc — 0x$addr != page_offset_base 0x$truth_page_offset (/proc/kcore)" |
| 724 | + fi |
| 725 | + elif hex_le "$truth_page_offset" "$addr"; then |
| 726 | + pass "$desc — at/above page_offset_base 0x$truth_page_offset" |
| 727 | + else |
| 728 | + fail "$desc — 0x$addr below page_offset_base 0x$truth_page_offset (/proc/kcore)" |
| 729 | + fi |
670 | 730 | ;; |
671 | 731 |
|
672 | 732 | # ---- Physical: kernel image regions -------------------------------- |
|
0 commit comments