#!/usr/bin/env python3 """Check a Linux module's versioned imports against its target Module.symvers. Requires kmod's modprobe. A pass checks symbol CRCs only, not complete ABI, vermagic, CFI, signatures, firmware or hardware behaviour. """ import argparse from pathlib import Path import subprocess import sys def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("symvers", type=Path) parser.add_argument("modules", nargs="+", type=Path) args = parser.parse_args() exports = {} for line in args.symvers.read_text().splitlines(): fields = line.split() if len(fields) >= 2: exports[fields[1]] = int(fields[0], 16) if not exports: parser.error("Module.symvers contains no exports") failed = False for module in args.modules: result = subprocess.run( ["modprobe", "--show-modversions", str(module.resolve())], text=True, capture_output=True, ) if result.returncode or not result.stdout.strip(): print(f"UNVERIFIED {module}: {result.stderr.strip() or 'no versioned imports'}") failed = True continue mismatch = 0 imports = result.stdout.splitlines() for line in imports: crc, symbol = line.split() expected = exports.get(symbol) if expected != int(crc, 16): mismatch += 1 target = "missing export" if expected is None else f"0x{expected:08x}" print(f" {symbol}: module={crc}, kernel={target}") failed |= bool(mismatch) print(f"{'FAIL' if mismatch else 'PASS'} {module}: " f"{mismatch}/{len(imports)} incompatible versioned imports") return int(failed) if __name__ == "__main__": sys.exit(main())