82 lines
3.3 KiB
Bash
82 lines
3.3 KiB
Bash
#!/usr/bin/env bash
|
|||
|
|
# Full ELF health scan of the extra-rootfs /usr/lib (read-only loop mount).
|
||
|
|
# Detects: non-ELF files, smashed headers (e_entry=0), truncated files
|
||
|
|
# (any PT_LOAD p_offset+p_filesz > file size -> ld.so SIGBUS on mmap tail).
|
||
|
|
set -uo pipefail
|
||
|
|
IMG="${1:?usage: tmp_elf_scan.sh IMG}"
|
||
|
|
L=$(losetup --find --show --partscan "$IMG" 2>/dev/null) || { echo LOSETUP_FAIL; exit 1; }
|
||
|
|
sleep 0.3
|
||
|
|
M=$(mktemp -d)
|
||
|
|
mount -o ro "${L}p1" "$M" || { echo ESP_MOUNT_FAIL; exit 1; }
|
||
|
|
R=$(mktemp -d)
|
||
|
|
mount -o loop,ro "$M/boot/linux-extra-rootfs.img" "$R" || { echo EXTRA_MOUNT_FAIL; exit 1; }
|
||
|
|
|
||
|
|
python3 - "$R" <<'PY'
|
||
|
|
import os, struct, sys
|
||
|
|
root = sys.argv[1]
|
||
|
|
dirs = [os.path.join(root, "usr", "lib"), os.path.join(root, "lib")]
|
||
|
|
bad = {}
|
||
|
|
checked = 0
|
||
|
|
for d in dirs:
|
||
|
|
if not os.path.isdir(d):
|
||
|
|
continue
|
||
|
|
for name in sorted(os.listdir(d)):
|
||
|
|
p = os.path.join(d, name)
|
||
|
|
if os.path.islink(p) or not os.path.isfile(p):
|
||
|
|
continue
|
||
|
|
if not (name.endswith(".so") or ".so." in name):
|
||
|
|
continue
|
||
|
|
checked += 1
|
||
|
|
try:
|
||
|
|
sz = os.path.getsize(p)
|
||
|
|
with open(p, "rb") as f:
|
||
|
|
hdr = f.read(64)
|
||
|
|
if len(hdr) < 64 or hdr[:4] != b"\x7fELF":
|
||
|
|
bad[p] = "NOT-ELF(%d)" % sz
|
||
|
|
continue
|
||
|
|
if hdr[4] != 2 or hdr[5] != 1: # 64-bit LE
|
||
|
|
bad[p] = "BAD-CLASS"
|
||
|
|
continue
|
||
|
|
# Explicit offsets: e_type@16 e_entry@24 e_phoff@32
|
||
|
|
# e_phentsize@54 e_phnum@56 (ELF64 little-endian)
|
||
|
|
e_type = struct.unpack_from("<H", hdr, 16)[0]
|
||
|
|
e_entry = struct.unpack_from("<Q", hdr, 24)[0]
|
||
|
|
e_phoff = struct.unpack_from("<Q", hdr, 32)[0]
|
||
|
|
e_phentsize = struct.unpack_from("<H", hdr, 54)[0]
|
||
|
|
e_phnum = struct.unpack_from("<H", hdr, 56)[0]
|
||
|
|
if e_type not in (2, 3): # ET_EXEC / ET_DYN
|
||
|
|
bad[p] = "BAD-TYPE(%d)" % e_type
|
||
|
|
continue
|
||
|
|
if e_entry == 0 and e_type == 3: # ET_DYN with null entry
|
||
|
|
bad[p] = "ENTRY0"
|
||
|
|
if e_phentsize < 56 or e_phnum == 0 or e_phnum > 128:
|
||
|
|
bad[p] = "PHDR-GARBAGE(phentsize=%d phnum=%d)" % (e_phentsize, e_phnum)
|
||
|
|
continue
|
||
|
|
end = 0
|
||
|
|
with open(p, "rb") as f:
|
||
|
|
f.seek(e_phoff)
|
||
|
|
ph = f.read(e_phentsize * e_phnum)
|
||
|
|
if len(ph) < e_phentsize * e_phnum:
|
||
|
|
bad[p] = "TRUNCATED(phdr table file=%d)" % sz
|
||
|
|
continue
|
||
|
|
for i in range(e_phnum):
|
||
|
|
off = i * e_phentsize
|
||
|
|
p_type, p_flags = struct.unpack_from("<II", ph, off)
|
||
|
|
p_offset = struct.unpack_from("<Q", ph, off + 8)[0]
|
||
|
|
# ELF64 Phdr: p_offset@8 p_vaddr@16 p_paddr@24
|
||
|
|
# p_filesz@32 p_memsz@40 p_align@48
|
||
|
|
p_filesz = struct.unpack_from("<Q", ph, off + 32)[0]
|
||
|
|
if p_type == 1: # PT_LOAD
|
||
|
|
end = max(end, p_offset + p_filesz)
|
||
|
|
if end > sz:
|
||
|
|
bad.setdefault(p, "TRUNCATED(phdr=%d file=%d short=%d)" % (end, sz, end - sz))
|
||
|
|
except OSError as e:
|
||
|
|
bad[p] = "IOERR:%s" % e
|
||
|
|
print("checked=%d bad=%d" % (checked, len(bad)))
|
||
|
|
for p, why in sorted(bad.items()):
|
||
|
|
print("BAD %-12s %s" % (why, os.path.basename(p)))
|
||
|
|
PY
|
||
|
|
|
||
|
|
umount "$R"; rmdir "$R"
|
||
|
|
umount "$M"; rmdir "$M"
|
||
|
|
losetup -d "$L"
|