[GH-ISSUE #4175] [Bug] extract_firmware reports Unblob-rewritten symlink targets as the firmware's link targets #1633

Closed
opened 2026-10-16 21:08:14 +00:00 by bsakaguchi · 0 comments
Owner

Originally created by @Rinkia on GitHub (Oct 15, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/4175

Before submitting

  • I searched the open and closed issues for a related report (firmware symlink, link_target, unblob symlink): none found. #3380 concerns absolute symlinks in Electron fuse inspection, a different reader.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

Evidence, artifacts, or reconstruction

Observed behavior

extract_firmware lists symlinks in unpublished_entries with a link_target. For an OpenWrt root filesystem, all 16 symlinks that store an absolute target are reported with a relative target:

Entry link_target reported by REA Target stored in the SquashFS (unsquashfs)
sbin/insmod, sbin/rmmod, sbin/modinfo, … kmodloader /sbin/kmodloader
usr/bin/scp, usr/bin/ssh-keygen, … ../sbin/dropbear /usr/sbin/dropbear

The other 194 symlinks (already relative in the image) match exactly. All 1,094 regular files match unsquashfs by path and SHA-256, so only the link targets differ.

The rewritten targets resolve to the same files inside the extraction root, so they're equivalent there. They aren't the observed firmware content, though, and nothing in the result or the docs says so. That matters because:

  • an absolute link is resolved against the device's runtime root, not the rootfs (e.g. links into /tmp, /var or overlay mounts);
  • a relative rewrite of a link that escapes the root can't be told apart from a link that really was relative;
  • REA otherwise presents firmware Evidence as observed bytes, with derivations kept separate.

Cause

Unblob creates symlinks through its safe-link handling (unblob/file_utils.py), which rewrites absolute targets relative to the extraction root. REA then reads the published tree with readlink (src/firmware/FirmwareFiles.ts:258 on main @ e9a71efa3) and reports that value as link_target.

Reproduction

Public image: OpenWrt 24.10.8, ath79/generic, openwrt-24.10.8-ath79-generic-tplink_archer-c7-v5-squashfs-sysupgrade.bin.

  • sha256 feeab4e3142feb865450ab044a42e198af9510d263e4b0f6d1cdcc701d296003, matching the release sha256sums.
  • sha256sums.asc verified: good signature from "OpenWrt Build System (Nitrokey3)", primary key 8A8B C12F 46B8 36C0 F9CD B36F 1D53 D187 7742 E911.
rea extract-firmware ./openwrt-…-sysupgrade.bin /tmp/out --json
# unpublished_entries: .../sbin/insmod  link_target "kmodloader"

# reference: carve the SquashFS at the Binwalk-reported offset and list links
dd if=openwrt-…-sysupgrade.bin of=rootfs.sqfs bs=1 skip=2627792 count=4334768
unsquashfs -d root rootfs.sqfs && find root -type l -printf '%P\t%l\n'
# sbin/insmod	/sbin/kmodloader

Expected behavior

One of:

  • report the target as stored in the image (e.g. from the filesystem handler's metadata), or
  • keep the published value but label it as derived, e.g. an extracted_link_target plus a limitation saying Unblob may rewrite absolute targets relative to the extraction root, so link_target isn't the stored firmware value.

Impact and scope

  • Any firmware whose root filesystem uses absolute symlinks, which covers most Linux-based router and IoT images.
  • No file content is wrong; the link-target provenance is misreported.
  • Deterministic.

REA version

Executed on rea-agents@6.5.0 with Unblob 26.6.4 and Binwalk 3.1.0. The readlink source is unchanged on main @ e9a71efa3.

Runtime and platform

Linux x64 Docker (the rea image plus caller-supplied Binwalk 3.1.0 built from crates.io and Unblob 26.6.4 from PyPI, with sasquatch 4.5.1-6) on Windows 11 / Docker Desktop.

Target and artifact context

Public, signed OpenWrt release image (GPL).

Interface

CLI (MCP shares the provider).

Originally created by @Rinkia on GitHub (Oct 15, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/4175 ### Before submitting - [x] I searched the open and closed issues for a related report (firmware symlink, link_target, unblob symlink): none found. #3380 concerns absolute symlinks in Electron fuse inspection, a different reader. - [x] I removed credentials, tokens, private paths, and sensitive target data. ### Area Evidence, artifacts, or reconstruction ### Observed behavior `extract_firmware` lists symlinks in `unpublished_entries` with a `link_target`. For an OpenWrt root filesystem, all 16 symlinks that store an **absolute** target are reported with a **relative** target: | Entry | `link_target` reported by REA | Target stored in the SquashFS (`unsquashfs`) | | --- | --- | --- | | `sbin/insmod`, `sbin/rmmod`, `sbin/modinfo`, … | `kmodloader` | `/sbin/kmodloader` | | `usr/bin/scp`, `usr/bin/ssh-keygen`, … | `../sbin/dropbear` | `/usr/sbin/dropbear` | The other 194 symlinks (already relative in the image) match exactly. All 1,094 regular files match `unsquashfs` by path and SHA-256, so only the link targets differ. The rewritten targets resolve to the same files *inside the extraction root*, so they're equivalent there. They aren't the observed firmware content, though, and nothing in the result or the docs says so. That matters because: - an absolute link is resolved against the device's runtime root, not the rootfs (e.g. links into `/tmp`, `/var` or overlay mounts); - a relative rewrite of a link that escapes the root can't be told apart from a link that really was relative; - REA otherwise presents firmware Evidence as observed bytes, with derivations kept separate. ### Cause Unblob creates symlinks through its safe-link handling (`unblob/file_utils.py`), which rewrites absolute targets relative to the extraction root. REA then reads the published tree with `readlink` (`src/firmware/FirmwareFiles.ts:258` on `main` @ `e9a71efa3`) and reports that value as `link_target`. ### Reproduction Public image: OpenWrt 24.10.8, `ath79/generic`, `openwrt-24.10.8-ath79-generic-tplink_archer-c7-v5-squashfs-sysupgrade.bin`. - sha256 `feeab4e3142feb865450ab044a42e198af9510d263e4b0f6d1cdcc701d296003`, matching the release `sha256sums`. - `sha256sums.asc` verified: good signature from "OpenWrt Build System (Nitrokey3)", primary key `8A8B C12F 46B8 36C0 F9CD B36F 1D53 D187 7742 E911`. ```sh rea extract-firmware ./openwrt-…-sysupgrade.bin /tmp/out --json # unpublished_entries: .../sbin/insmod link_target "kmodloader" # reference: carve the SquashFS at the Binwalk-reported offset and list links dd if=openwrt-…-sysupgrade.bin of=rootfs.sqfs bs=1 skip=2627792 count=4334768 unsquashfs -d root rootfs.sqfs && find root -type l -printf '%P\t%l\n' # sbin/insmod /sbin/kmodloader ``` ### Expected behavior One of: - report the target as stored in the image (e.g. from the filesystem handler's metadata), or - keep the published value but label it as derived, e.g. an `extracted_link_target` plus a limitation saying Unblob may rewrite absolute targets relative to the extraction root, so `link_target` isn't the stored firmware value. ### Impact and scope - Any firmware whose root filesystem uses absolute symlinks, which covers most Linux-based router and IoT images. - No file content is wrong; the link-target provenance is misreported. - Deterministic. ### REA version Executed on `rea-agents@6.5.0` with Unblob 26.6.4 and Binwalk 3.1.0. The `readlink` source is unchanged on `main` @ `e9a71efa3`. ### Runtime and platform Linux x64 Docker (the `rea` image plus caller-supplied Binwalk 3.1.0 built from crates.io and Unblob 26.6.4 from PyPI, with sasquatch 4.5.1-6) on Windows 11 / Docker Desktop. ### Target and artifact context Public, signed OpenWrt release image (GPL). ### Interface CLI (MCP shares the provider).
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#1633
No description provided.