feat(flash): pm3flash --build PLATFORM (build from the submodule, then flash)
build_firmware()/find_firmware_src() cross-compile fullimage.elf via 'make -C firmware PLATFORM=<explicit> armsrc/all' and pm3flash --build flashes it. PLATFORM is explicit (never inferred from is_rdv4 — that's the running firmware's flag, not the board). Hardened per an adversarial multi-agent review of the change: - --build now implies --force: it must flash the image it just built. A dirty rebuild keeps the same base SHA, so matches_pin can't distinguish it from the old firmware and would otherwise silently skip the flash (exit 0). - make missing/not-executable -> FlashError (was an uncaught OSError traceback). - find_firmware_src no longer falls back to a CWD-relative ./firmware — running make on a stray Makefile is arbitrary code execution; only explicit arg / $PM3PY_FIRMWARE_SRC / the package repo root. Suite 1045 pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -12,10 +12,23 @@ from pm3py.core.protocol import (
|
||||
DEVICE_INFO_FLAG_CURRENT_MODE_BOOTROM, DEVICE_INFO_FLAG_CURRENT_MODE_OS,
|
||||
)
|
||||
from pm3py.core.transport import PM3Response, encode_old_frame, decode_old_frame
|
||||
from pm3py.core import flash as flashmod
|
||||
from pm3py.core.flash import (
|
||||
Flasher, FlashError, parse_elf_segments, build_blocks, resolve_image,
|
||||
find_firmware_src, build_firmware,
|
||||
)
|
||||
|
||||
|
||||
def _proc(returncode):
|
||||
return type("P", (), {"returncode": returncode})()
|
||||
|
||||
|
||||
def _fake_src(tmp_path):
|
||||
(tmp_path / "Makefile").write_text("x")
|
||||
(tmp_path / "armsrc").mkdir()
|
||||
(tmp_path / "armsrc" / "Makefile").write_text("x")
|
||||
return tmp_path
|
||||
|
||||
OS_FLAGS = (DEVICE_INFO_FLAG_BOOTROM_PRESENT | DEVICE_INFO_FLAG_OSIMAGE_PRESENT
|
||||
| DEVICE_INFO_FLAG_CURRENT_MODE_OS)
|
||||
BOOTROM_FLAGS = (DEVICE_INFO_FLAG_BOOTROM_PRESENT | DEVICE_INFO_FLAG_OSIMAGE_PRESENT
|
||||
@@ -160,6 +173,74 @@ def test_resolve_image_none_no_build(tmp_path, monkeypatch):
|
||||
resolve_image(None)
|
||||
|
||||
|
||||
# --- build_firmware / find_firmware_src ---
|
||||
|
||||
def test_find_firmware_src_env(tmp_path, monkeypatch):
|
||||
src = _fake_src(tmp_path)
|
||||
monkeypatch.setenv("PM3PY_FIRMWARE_SRC", str(src))
|
||||
assert find_firmware_src() == src
|
||||
|
||||
|
||||
def test_build_firmware_invalid_platform():
|
||||
with pytest.raises(FlashError, match="PLATFORM"):
|
||||
build_firmware("PM3; rm -rf /")
|
||||
|
||||
|
||||
def test_build_firmware_runs_make(tmp_path, monkeypatch):
|
||||
src = _fake_src(tmp_path)
|
||||
obj = src / "armsrc" / "obj"
|
||||
obj.mkdir()
|
||||
(obj / "fullimage.elf").write_bytes(b"\x7fELF")
|
||||
seen = {}
|
||||
|
||||
def fake_run(cmd, *a, **k):
|
||||
seen["cmd"] = cmd
|
||||
return _proc(0)
|
||||
|
||||
monkeypatch.setattr(flashmod.subprocess, "run", fake_run)
|
||||
elf = build_firmware("PM3GENERIC", firmware_dir=str(src))
|
||||
assert elf == obj / "fullimage.elf"
|
||||
# explicit platform, non-shell argv, armsrc/all target
|
||||
assert seen["cmd"] == ["make", "-C", str(src), "PLATFORM=PM3GENERIC", "armsrc/all"]
|
||||
|
||||
|
||||
def test_build_firmware_make_fails(tmp_path, monkeypatch):
|
||||
src = _fake_src(tmp_path)
|
||||
monkeypatch.setattr(flashmod.subprocess, "run", lambda *a, **k: _proc(2))
|
||||
with pytest.raises(FlashError, match="build failed"):
|
||||
build_firmware("PM3RDV4", firmware_dir=str(src))
|
||||
|
||||
|
||||
def test_build_firmware_missing_source(monkeypatch):
|
||||
monkeypatch.setattr(flashmod, "find_firmware_src", lambda fd=None: None)
|
||||
with pytest.raises(FlashError, match="source not found"):
|
||||
build_firmware("PM3GENERIC")
|
||||
|
||||
|
||||
def test_build_firmware_make_not_on_path(tmp_path, monkeypatch):
|
||||
# make missing/not-executable -> FlashError, not a raw OSError traceback
|
||||
src = _fake_src(tmp_path)
|
||||
|
||||
def boom(*a, **k):
|
||||
raise FileNotFoundError(2, "No such file or directory", "make")
|
||||
|
||||
monkeypatch.setattr(flashmod.subprocess, "run", boom)
|
||||
with pytest.raises(FlashError, match="could not run make"):
|
||||
build_firmware("PM3GENERIC", firmware_dir=str(src))
|
||||
|
||||
|
||||
def test_find_firmware_src_ignores_cwd(tmp_path, monkeypatch):
|
||||
# A firmware/ in the CWD must NOT be picked up — build_firmware runs `make` on
|
||||
# the result, so a stray ./firmware would execute an untrusted Makefile.
|
||||
cwd_fw = tmp_path / "firmware"
|
||||
cwd_fw.mkdir()
|
||||
_fake_src(cwd_fw)
|
||||
monkeypatch.chdir(tmp_path)
|
||||
monkeypatch.delenv("PM3PY_FIRMWARE_SRC", raising=False)
|
||||
monkeypatch.setattr(flashmod, "_PACKAGE_ROOT", tmp_path / "nonexistent")
|
||||
assert find_firmware_src() is None
|
||||
|
||||
|
||||
# --- Flasher: probing ---
|
||||
|
||||
def test_device_info_parses_flags():
|
||||
|
||||
Reference in New Issue
Block a user