Linux 内核模块¶
inspect_kernel_module / inspect-kernel-module 读取一个选定的本地 ELF32/ELF64 可重定位对象(.ko)的元数据,而无需执行它。这些元数据包括 .modinfo 键/值字符串、__versions 符号 CRC、.gnu.linkonce.this_module 和 __ksymtab* 段的存在性、.init.text 和 .exit.text 的观测文件大小,以及可选的附加签名尾部。它不需要 Linux 主机或 modinfo 程序。
CLI 会相对于调用者的工作目录解析相对路径。MCP 需要绝对文件系统路径,并保留该确切选择:
观测结果¶
内联证据结果包含工件路径、SHA-256 和字节长度。applicability 仅对 ET_REL 为 applicable。其他 ELF 类型为 not_applicable:头部事实会被填充,而 .modinfo、__versions 和签名尾部不会被检查。
.modinfo 条目保持文件顺序。alias 和 parm 等重复键会被保留。每个条目保留其键、值、文件范围和 SHA-256。值是第一个 = 之后的字节。字符串之间的 NUL 填充会被跳过。键和值使用字节保持的 Latin1 映射:每个源字节成为相同编号的 Unicode 码点。它们不会被解码为 UTF-8。raw_sha256 对原始条目字节(包括存在时的终止 NUL)进行哈希;raw_hex 在这些字节适合内联限制时显示这些字节。
__versions 条目是 struct modversion_info:一个 64 字节的记录,其 CRC 是一个 unsigned long,名称占用其余字节。ELF32 存储 32 位 CRC 和 60 字节名称。ELF64 存储 64 位 unsigned long 和 56 字节名称。CRC 是零填充的小写十六进制,8 或 16 个字符,而不是 JavaScript 数字。布局根据 ELF 类别报告,即使该段不存在也是如此。名称使用与 .modinfo 键和值相同的字节保持 Latin1 映射。只对这种经典 __versions 布局进行解码。versions.status: "absent" 表示在段名称完整时未找到 __versions 段;它并不确定 __version_ext_names 和 __version_ext_crcs 中不存在版本元数据,Linux 在 CONFIG_EXTENDED_MODVERSIONS 下使用这些元数据,而本工作流不对其进行解码。
.gnu.linkonce.this_module 被报告为存在、不存在或未知。layout 始终为 unknown,因为 struct module 随内核版本变化且不进行解码。文件支持的 __ksymtab* 段按段头顺序列出名称和文件范围;它们的记录不进行解码。SHT_NOBITS 或超出文件的段会在一条限制中计数,且不包含其名称。.init.text 和 .exit.text 报告观测到的文件支持大小。压缩载荷报告存储大小,并有一条限制说明解码后大小未知。
签名尾部是 27 字节的魔数 ~Module signature appended~\n,其前面是一个 12 字节的 module_signature(algo、hash、id_type、signer_len、key_id_len、三个填充字节和大端 sig_len)。当这些参数字段为零且 id_type 为 PKCS#7 (2) 时,前面的 sig_len 字节就是 PKCS#7 范围及其 SHA-256。该范围不会被解析,也不做任何证书、签名者或摘要覆盖范围的声明。缺失尾部是观测到的缺失,而不是模块未签名的证明。
当段名称不完整时,缺失的命名段是 unknown 而不是 absent。空的文件支持段是存在且为空的。
限制与失败¶
默认情况下,所选文件限制为 64 MiB(最大 512 MiB)。默认检查最多 8192 个 .modinfo 和 __versions 条目以及列出的 __ksymtab* 段(最大 65536);coverage.examined_entries 对这三类全部计数。超过该预算会失败,且没有任何部分成功。超过 256 字节的键和超过 4096 字节的值在每个条目中保持格式错误;适合的字段仍会被复制。超过 256 字节的 __ksymtab* 段名称保留其范围,并带有 name: null 和 ksymtab_name_exceeds_limit。内联原始十六进制在 4096 字节处停止,SHA-256 仍然保留。
没有尾部 NUL 的 .modinfo 跨度是 unterminated_modinfo_entry,之前发现的条目会被保留。没有 = 的字符串是 modinfo_missing_separator。空键是 modinfo_empty_key。没有 NUL 的 __versions 名称是 version_name_unterminated。大小不是 64 字节倍数的 __versions 段以 versions_size_not_multiple 结束,且不包含任何条目。SHT_NOBITS 或声明的范围超出文件的段是 modinfo_section_not_file_backed 或 versions_section_not_file_backed。SHF_COMPRESSED 载荷不会被解压(modinfo_section_compressed、versions_section_compressed)。
附加的魔数前面不足 12 字节时,会得到 signature_trailer_truncated。sig_len 未留下至少一个模块字节时,会得到 signature_length_inconsistent。对于 PKCS#7,非零的 algo、hash、签名者、密钥 ID 或填充字段会得到 signature_parameters_nonzero。参数块的 id_type 不是 PKCS#7 时,会得到 signature_id_type_not_pkcs7,并且它是不受支持的,而不是经过验证的签名。
gzip(1f 8b)、xz(fd 37 7a 58 5a 00)和 zstd(28 b5 2f fd)整文件容器是 unsupported。结果将 external_decompression 作为下一个探测,并且不声称载荷是内核模块。这是一个外部步骤,不是 REA 操作:此工具和 extract_artifact 都不会解码这些容器。使用外部的 gzip、xz 或 zstd 工具将其解压到单独的文件,然后对该文件调用 inspect_kernel_module 以检查 ET_REL 镜像。
格式错误的 ELF 头部不会返回任何部分的内核模块元数据。此工作流不会执行模块,也不会验证附加的签名。
格式参考¶
记录布局遵循 Linux v6.12 的 modversion_info 和 MAX_PARAM_PREFIX_LEN。Linux kbuild 模块版本化指南 描述了独立扩展版本段。尾部字段和接受规则遵循 module_signature 和 mod_check_sig。这些参考资料确定了解码后的布局,而非运行时验证。
真实模块验证通道¶
从已构建的检出目录运行,并确保 Docker 可用。该流程会将 Debian 内核软件包下载到一次性容器中,提取文件系统模块,必要时进行解压,并使用 REA 对其进行检查。它绝不会安装或加载该内核模块。临时输出位于仓库之外;请将打印的软件包版本和模块摘要与结果一并记录。
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 和 parmtype 由 REA 分别保留。合成测试覆盖了它们的通用键/值表示,以及全部四种 ELF 类别/字节序组合。成功的真实通道仅验证所选模块和所打印的字段;它不会验证证书。由于访问 Docker socket 的权限被拒绝,该通道未在实现所用的沙箱中运行。
本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 rea2/rea