mirror of
https://github.com/morluto/rea.git
synced 2026-11-10 16:57:19 +00:00
* refactor(analysis): centralize resource budgets and recovery facts * feat(analysis): expose ASAR and Ghidra runtime admission controls * feat(artifacts): expose reader admission and preserve limited recognition * feat(javascript): expose recoverable semantic precision budgets * feat(web): add recoverable source-map budgets and compact retained views * feat(web): make historical capture decoding budgets recoverable * fix(desktop): enforce selected view policies across capture and export * feat(runtime): make Frida and LLDB retention budgets selectable * feat(native): expose diagnostic budgets and preserve exhaustion causes * feat(managed): expose recoverable snapshot and representation budgets * feat(metadata): make Go and Rust inspection budgets recoverable * feat(wasm): expose representation and producer admissions * feat(evm): expose per-call worker resource budgets * feat(android): make resource and string-pool omissions recoverable * feat(producers): expose command and recorded-capture budgets * docs(analysis): record recovery contracts and integrated validation * fix(docs): link calibration oracle from published verification pages * fix(docs): restore desktop task anchors and receipt links * test(analysis): align fixtures with selectable budget contracts * fix(analysis): remove source-location projector dependency cycle * test(analysis): refresh budget recovery and verifier regressions * test(analysis): preserve format and publication recovery expectations * test(windows): align conformance with owned runtime admission * fix(desktop): preserve selected alias kinds in exported state * test(analysis): align provider fixtures with recoverable budgets * test(desktop): validate emitted alias metadata structurally * fix(ghidra): classify bounded Windows endpoint refusals * test(cli): assert selectable artifact admission recovery * fix(cli): preserve capture provenance in environment builds
196 lines
10 KiB
Markdown
196 lines
10 KiB
Markdown
# Linux kernel modules
|
|
|
|
`inspect_kernel_module` / `inspect-kernel-module` reads metadata from one
|
|
selected local ELF32/ELF64 relocatable object (`.ko`) without executing it.
|
|
That metadata is the `.modinfo` key/value strings, the `__versions` symbol
|
|
CRCs, the presence of `.gnu.linkonce.this_module` and `__ksymtab*` sections,
|
|
the observed file sizes of `.init.text` and `.exit.text`, and an optional
|
|
appended signature trailer. It does not require a Linux host or the `modinfo`
|
|
program.
|
|
|
|
```bash
|
|
rea inspect-kernel-module /absolute/path/to/module.ko --json
|
|
```
|
|
|
|
The CLI resolves relative paths against the caller's working directory. MCP
|
|
requires an absolute filesystem path and preserves that exact selection:
|
|
|
|
```json
|
|
{
|
|
"name": "inspect_kernel_module",
|
|
"arguments": { "path": "/artifacts/demo.ko" }
|
|
}
|
|
```
|
|
|
|
## Observations
|
|
|
|
The inline Evidence result contains the artifact path, SHA-256, and byte
|
|
length. `applicability` is `applicable` only for `ET_REL`. Other ELF types are
|
|
`not_applicable`: header facts are filled and `.modinfo`, `__versions`, and
|
|
the signature trailer are not examined.
|
|
|
|
`.modinfo` entries stay in file order. Duplicate keys such as `alias` and
|
|
`parm` are preserved. Each entry keeps its key, value, file range, and
|
|
SHA-256. Values are the bytes after the first `=`. NUL padding between
|
|
strings is skipped. Keys and values use a byte-preserving Latin1 projection:
|
|
each source byte becomes the same-numbered Unicode code point. They are not
|
|
decoded as UTF-8. `raw_sha256` hashes the original entry bytes, including its
|
|
terminating NUL when present; `raw_hex` shows those bytes when they fit the
|
|
inline limit.
|
|
|
|
`__versions` entries are `struct modversion_info`: a 64-byte record whose CRC
|
|
is an `unsigned long` and whose name occupies the remaining bytes. ELF32
|
|
stores a 32-bit CRC and a 60-byte name. ELF64 stores a 64-bit unsigned long
|
|
and a 56-byte name. CRCs are zero-padded lowercase hex, 8 or 16 characters,
|
|
not a JavaScript number. The layout is reported from the ELF class even when
|
|
the section is absent. Names use the same byte-preserving Latin1 projection
|
|
as `.modinfo` keys and values. Only this classic `__versions` layout is
|
|
decoded. `versions.status: "absent"` means no `__versions` section was found
|
|
when section names were complete; it does not establish the absence of version
|
|
metadata in `__version_ext_names` and `__version_ext_crcs`, which Linux uses
|
|
with `CONFIG_EXTENDED_MODVERSIONS` and this workflow does not decode.
|
|
|
|
`.gnu.linkonce.this_module` is reported as present, absent, or unknown.
|
|
`layout` is always `unknown` because `struct module` changes with the kernel
|
|
version and is not decoded. File-backed `__ksymtab*` sections are listed in
|
|
section-header order by name and file range; their records are not decoded.
|
|
Sections that are `SHT_NOBITS` or leave the file are counted in one limitation
|
|
without their names. `.init.text` and `.exit.text` report the
|
|
observed file-backed size. A compressed payload reports the stored size and a
|
|
limitation that the decoded size is unknown.
|
|
|
|
The signature trailer is the 27-byte magic `~Module signature appended~\n`
|
|
preceded by a 12-byte `module_signature` (`algo`, `hash`, `id_type`,
|
|
`signer_len`, `key_id_len`, three pad bytes, and big-endian `sig_len`). When
|
|
those parameter fields are zero and `id_type` is PKCS#7 (`2`), the preceding
|
|
`sig_len` bytes are the PKCS#7 range and its SHA-256. The range is not parsed
|
|
and no certificate, signer, or digest-coverage claim is made. An absent
|
|
trailer is observed absence, not proof the module is unsigned.
|
|
|
|
When section names are incomplete, a missing named section is `unknown`
|
|
rather than `absent`. An empty file-backed section is present and empty.
|
|
|
|
`coverage.status` is `partial` when supported facts cannot all be examined or
|
|
interpreted, including incomplete section names, malformed or unsupported
|
|
metadata, and omitted duplicate named sections. When multiple `.modinfo`,
|
|
`__versions`, `.gnu.linkonce.this_module`, `.init.text`, or `.exit.text`
|
|
sections exist, the first is examined and a limitation identifies the omission.
|
|
`complete` covers the supported inspection described here; it does not claim
|
|
that `struct module`, symbol-table records, or certificate chains were decoded.
|
|
|
|
## Limits and failures
|
|
|
|
The selected file is limited to 64 MiB by default (512 MiB maximum). At most
|
|
8192 `.modinfo` and `__versions` entries plus listed `__ksymtab*` sections are
|
|
examined by default (65536 maximum); `coverage.examined_entries` counts all
|
|
three. Exceeding that budget fails with no partial success. `.modinfo` keys
|
|
retain at most 256 bytes and values at most 4096 bytes by default. A larger
|
|
field becomes `null` and is named in the entry's `omitted_fields`; the field
|
|
that fits is still copied. Structural `status` and `reason` remain independent:
|
|
a terminated, nonempty `key=value` entry stays `observed` when its text is
|
|
omitted, while overall coverage becomes `partial`. Source range and digest
|
|
remain available.
|
|
|
|
Raise `max_modinfo_key_bytes` or `max_modinfo_value_bytes` on the next MCP call
|
|
to retain the omitted text. The returned limitation names the required byte
|
|
budget. CLI flags are `--max-modinfo-key-bytes` and `--max-modinfo-value-bytes`.
|
|
Each accepts an integer from 1 through 536870912 bytes, matching the maximum
|
|
input size. No restart is required; file, entry and raw-hex budgets do not
|
|
change. These are per-call controls, not runtime settings.
|
|
|
|
A `__ksymtab*` section name longer than
|
|
256 bytes keeps its range with `name: null` and `ksymtab_name_exceeds_limit`. Inline raw hex stops at 4096 bytes and
|
|
the SHA-256 remains.
|
|
|
|
A `.modinfo` span with no trailing NUL is `unterminated_modinfo_entry` and
|
|
earlier entries are kept. A string with no `=` is `modinfo_missing_separator`.
|
|
An empty key is `modinfo_empty_key`. A `__versions` name with no NUL is
|
|
`version_name_unterminated`. A `__versions` section whose size is not a
|
|
multiple of 64 bytes is closed with `versions_size_not_multiple` and no
|
|
entries. `SHT_NOBITS` or a declared range that leaves the file is
|
|
`modinfo_section_not_file_backed` or `versions_section_not_file_backed`.
|
|
`SHF_COMPRESSED` payloads are not inflated
|
|
(`modinfo_section_compressed`, `versions_section_compressed`).
|
|
|
|
An appended magic with fewer than 12 preceding bytes is
|
|
`signature_trailer_truncated`. A `sig_len` that does not leave at least one
|
|
module byte is `signature_length_inconsistent`. Nonzero `algo`, `hash`,
|
|
signer, key-id, or pad fields for PKCS#7 are `signature_parameters_nonzero`.
|
|
A parameter block whose `id_type` is not PKCS#7 is
|
|
`signature_id_type_not_pkcs7` and is unsupported rather than a validated
|
|
signature.
|
|
|
|
gzip (`1f 8b`), xz (`fd 37 7a 58 5a 00`), and zstd (`28 b5 2f fd`) whole-file
|
|
containers are `unsupported`. The result names `external_decompression` as the
|
|
next probe and does not claim the payload is a kernel module. This is an external
|
|
step, not a REA operation: neither this tool nor `extract_artifact` decodes these
|
|
containers. Use an external gzip, xz, or zstd utility to decompress into a separate
|
|
file, then call `inspect_kernel_module` on that file to inspect the `ET_REL` image.
|
|
|
|
Malformed ELF headers return no partial kernel-module metadata. This workflow
|
|
does not execute the module or validate an appended signature.
|
|
|
|
## Format references
|
|
|
|
The record layout follows Linux v6.12's
|
|
[`modversion_info`](https://github.com/torvalds/linux/blob/v6.12/include/linux/module.h)
|
|
and [`MAX_PARAM_PREFIX_LEN`](https://github.com/torvalds/linux/blob/v6.12/include/linux/moduleparam.h).
|
|
The [Linux kbuild module versioning guide](https://docs.kernel.org/kbuild/modules.html#module-versioning)
|
|
describes the separate extended version sections.
|
|
Trailer fields and acceptance rules follow
|
|
[`module_signature`](https://github.com/torvalds/linux/blob/v6.12/include/linux/module_signature.h)
|
|
and [`mod_check_sig`](https://github.com/torvalds/linux/blob/v6.12/kernel/module_signature.c).
|
|
These references establish the decoded layout, not runtime verification.
|
|
|
|
## Real-module verification lane
|
|
|
|
Run from a built checkout with Docker available. This downloads a Debian kernel
|
|
package into a disposable container, extracts a filesystem module, decompresses
|
|
it when necessary, and inspects it with REA. It never installs or loads that
|
|
kernel module. The temporary output is outside the repository; record the
|
|
printed package version and module digest with results.
|
|
|
|
```bash
|
|
module_lane=$(mktemp -d)
|
|
docker run --rm -i -v "$module_lane:/out" debian:stable bash -se <<'CONTAINER'
|
|
apt-get update -qq
|
|
apt-get install -y --no-install-recommends xz-utils zstd ca-certificates
|
|
cd /tmp
|
|
arch=$(dpkg --print-architecture)
|
|
package=$(apt-cache depends "linux-image-$arch" | awk '/Depends: linux-image-[0-9]/{print $2; exit}')
|
|
test -n "$package"
|
|
apt-get download "$package"
|
|
dpkg-deb -f ./*.deb Package Version > /out/package.txt
|
|
dpkg-deb -x ./*.deb /tmp/kernel
|
|
module=$(find /tmp/kernel -type f -path '*/kernel/fs/*' \( -name '*.ko' -o -name '*.ko.xz' -o -name '*.ko.zst' -o -name '*.ko.gz' \) | sort | head -1)
|
|
test -n "$module"
|
|
case "$module" in
|
|
*.xz) xz -dc "$module" > /out/module.ko ;;
|
|
*.zst) zstd -dc "$module" > /out/module.ko ;;
|
|
*.gz) gzip -dc "$module" > /out/module.ko ;;
|
|
*) cp "$module" /out/module.ko ;;
|
|
esac
|
|
cat /out/package.txt
|
|
sha256sum /out/module.ko
|
|
CONTAINER
|
|
node scripts/rea.mjs inspect-kernel-module "$module_lane/module.ko" --json > "$module_lane/rea.json"
|
|
python3 - "$module_lane" <<'PY'
|
|
import json, pathlib, sys
|
|
root = pathlib.Path(sys.argv[1])
|
|
report = json.loads((root / 'rea.json').read_text())['normalized_result']
|
|
entries = report['modinfo']['entries']
|
|
assert entries and all(entry['status'] == 'observed' for entry in entries)
|
|
keys = [entry['key'] for entry in entries]
|
|
for key in ('license', 'vermagic', 'name'):
|
|
assert key in keys, key
|
|
print('Observed .modinfo entries:', len(entries), 'distinct keys:', len(set(keys)))
|
|
print('Artifact:', report['artifact'])
|
|
PY
|
|
```
|
|
|
|
`parm` and `parmtype` are retained separately by REA. Synthetic tests cover
|
|
their generic key/value representation and all four ELF class/byte-order
|
|
combinations. A successful real lane verifies only the selected module and
|
|
printed fields; it does not validate certificates. This lane has not run in the
|
|
sandbox used for implementation because access to the Docker socket was denied.
|