Skip to content

Fetch opkg only from Entware over HTTPS, and check the download - #20

Open
arlophoenix wants to merge 1 commit into
C0DEbrained:mainfrom
arlophoenix:fix/entware-installer-supply-chain
Open

Fetch opkg only from Entware over HTTPS, and check the download#20
arlophoenix wants to merge 1 commit into
C0DEbrained:mainfrom
arlophoenix:fix/entware-installer-supply-chain

Conversation

@arlophoenix

Copy link
Copy Markdown

The Entware installer downloads /opt/bin/opkg over plain HTTP from a third-party mirror, makes it executable and runs it as root, with no integrity check of any kind. Anyone able to MITM that connection, spoof DNS or compromise that host gets root on the printer. This is only reached on non-2025 models, since install_entware sends K1_2025 down the bin.entware.net path instead.

While confirming that, I found the download helper is missing -f, which turns out to matter more than the HTTP fallback itself. Without -f, curl exits 0 on an HTTP error status and writes the error body to the output path. I checked this against the live host: a 404 from bin.entware.net exits 0 and leaves a 153-byte HTML document at /opt/bin/opkg, which the next lines chmod 755 and execute as root. Because the primary is reported as having succeeded, the mirror fallback never fires — it only ever fires on a connection-level failure, not on an HTTP one.

I also checked whether the mirror can still serve a working install, and it cannot. www.openk1.org returns 403 for opkg.conf (verified over both http and https; the file is listed in the directory index at 164 bytes but denied on fetch). So the fallback downloads the opkg binary fine, then writes a 1019-byte HTML error page to /opt/etc/opkg.conf and still exits 0. I reproduced the full path in a sandbox against stubs matching the hosts' verified live responses: with the primary down and the mirror up, the current code finishes with exit 0, a real opkg binary, and an HTML document as its config. The user is told the install succeeded and opkg update then fails with an unhelpful error.

So the fallback is not the recovery path it looks like — it recovers into a broken install that reports success. On that basis I've removed it rather than pinning a checksum, and I'd rather present that as a proposal than assume it. If you want the recovery path back, I'll happily restore it — but the mirror needs to serve opkg.conf again first, and I'd want a pinned hash to go with it.

For the record, the other two options and why I set them aside. Pinning a SHA256 for the primary is the one I'd most want, but neither host publishes a checksum to anchor against (both 404 on SHA256SUMS), so any pin would just be trust-on-first-use from my own fetch, and Entware does rebuild opkg — the two hosts serve different builds today, OpenWrt GCC r2711 on the primary against r2533 on the mirror, which is a stale but genuine Feb-2024 copy, not evidence of tampering. A pin would turn a working install into a hard failure for every user the day Entware rebuilds, which is a worse regression than the bug. Upgrading the mirror to HTTPS does work at the scheme level — it serves 200 over https with a valid cert — but it fixes neither the 403 nor the stale build, and the vendored files/fixes/curl is a stripped curl/7.68.0 with no compiled-in CA bundle path that I could find in its strings, so I can't claim from here that it would actually validate rather than just change the scheme.

What's left is: fetch only from Entware over HTTPS, fail closed with a message naming the host to check, fail on HTTP error statuses, check both downloads rather than only the first, and reject an implausibly small opkg before running it. That last one is a sanity check against truncation and error-page bodies, not authentication, and the comment says so.

Separately and uncontroversially, the vendored curl was hardcoded to /usr/data/helper-script/files/fixes/curl, which breaks any non-default clone path. Worth noting the obvious fix doesn't work: entware.sh runs this file with sh, not ., and paths.sh never exports $CURL, so $CURL is simply unset here and using it directly would have silently broken every install. It now honours $CURL if a caller ever exports it and otherwise resolves the binary relative to its own location.

Test plan

  • shellcheck -s bash -S warning -e SC2154,SC2034,SC1090,SC1091,SC1111 files/entware/generic.sh — clean
  • Sandbox harness, clone at a non-default path containing a space: primary succeeds (install proceeds), primary unreachable (fails closed, /opt cleaned up), opkg.conf 403 (fails closed), error page with a 200 status (rejected by the size check) — 4/4
  • Same harness against the current code confirms the two regressions above: opkg.conf 403 and error-page-with-200 both exit 0 today
  • Live check of both hosts: primary 200 over HTTPS; mirror 200 for opkg, 403 for opkg.conf; curl without -f exits 0 on a 404
  • Manual test pass on a non-2025 printer (see below) — I have no legacy hardware, so this is unverified on-device

Manual test pass

  • On a K1 / 3V3 / 3KE / 10SE / E5M, install Entware from a clone at the default path — expect it to complete as before and opkg update to succeed
  • Repeat from a clone at a different path (e.g. /usr/data/hs-test) — expect success, confirming the curl path fix
  • Block bin.entware.net (e.g. point it at 127.0.0.1 in /etc/hosts) and install — expect the "Failed to download opkg" message, exit 1, and no leftover /opt or /usr/data/opt
  • Confirm K1_2025 is unaffected, since it never reaches this file

Not addressed

entware.sh:79 pipes a remote installer straight to sh as root on the K1_2025 path using the same vendored curl, which is the same class of exposure as this one; it's left for a separate change so this PR stays to one file.

The Entware installer downloaded /opt/bin/opkg over plain HTTP from a
third-party mirror, made it executable and ran it as root with no
integrity check of any kind.

curl was also called without -f, so an HTTP error status exited 0 and the
error body was written to the output path. A 404 or 403 from either host
therefore left an HTML document at /opt/bin/opkg, which the next lines
chmod 755 and execute as root, and the mirror fallback never fired
because the primary was reported as having succeeded.

The mirror cannot serve a working install today regardless: it returns
403 for opkg.conf, so the fallback wrote an HTML error page to
/opt/etc/opkg.conf and still exited 0.

Drop the mirror, add -f, check both downloads, and reject an implausibly
small opkg before running it. Also resolve the vendored curl relative to
this script instead of hardcoding /usr/data/helper-script, which broke
any non-default clone path.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant