Skip to content

init: refuse to format an overlay that runs off the end of the flash - #2409

Draft
openipc-ai wants to merge 1 commit into
masterfrom
sigmastar-overlay-format-guard
Draft

init: refuse to format an overlay that runs off the end of the flash#2409
openipc-ai wants to merge 1 commit into
masterfrom
sigmastar-overlay-format-guard

Conversation

@openipc-ai

Copy link
Copy Markdown
Collaborator

Problem

A SigmaStar SSC325 camera with an 8 MB NOR chip boots OpenIPC once, comes up with no
network, and is dead after the next power cycle — the whole chip reads back 0xFF.
Reported in #1998 (SSC325, 8 MB NOR) and reproduced by a second user on 2026-09-13.

The camera erases its own flash. drivers/sstar/flash_isp sizes any chip missing from
its JEDEC table at 16 MB — the FLASH_IC_UNKNOWN row declares 0x1000000 next to a
sector count that says 4 MB, so it was never a real geometry — and mtd.size is taken
straight from it. Our SigmaStar bootargs end in -(rootfs_data), so the overlay
partition becomes whatever is left of the believed chip:

detected size rootfs_data
correct 8 MB 704 KB = 11 erase blocks
believed 16 MB 8896 KB = 139 erase blocks

#1998's log says jffs2: empty_blocks 40, bad_blocks 0, c->nr_blocks 139. jffs2 is
then scanning an aliased mirror of the squashfs — that is what the "Old JFFS2 bitmask"
noise in that log is — so it cannot mount, /init falls back to flash_eraseall, and
the erase walks past 0x800000. A 3-byte-addressed NOR wraps every address at or above
the real end back to zero, so the run takes the bootloader, the environment, the kernel
and the rootfs with it, while the system is still running out of page cache.

This PR is the half that stops the damage on images already in the field. The driver is
the real fix and belongs in OpenIPC/linux; that
change is waiting on the reporter's hardware and is tracked in #1998.

The guard is not SigmaStar-specific. Any driver that over-reports a chip lands here.

Hardware tested on

Not run on a camera — opened as a draft for that reason. Nobody here has an SSC325,
and the only SigmaStar board in the lab was down. The reported failure also cannot be
reproduced without a flash chip that is absent from the vendor's table, which is not
something that can be sourced on purpose.

What can be evidenced without a board is evidenced below: the guard is driven against
synthetic chips built byte-for-byte the way the driver would present them, including the
wrap, in both the tree form and the comment-stripped form that actually ships.

Evidence

bash .github/scripts/test_overlay_format_guard.sh — the new suite, against
general/overlay/init:

ok   8MB chip sized 16MB: refuses to format (#1998)
ok   healthy 8MB camera: formats as before
ok   real 16MB chip, 8MB layout: formats as before
ok   blank flash proves nothing: formats as before
ok   unreadable mtd0: falls through and formats
ok   no rootfs_data partition: guard stands down
ok   hex sizes with leading zeros are not read as octal

All overlay format guard checks passed.

The same suite against the form the camera executes
(awk -f general/scripts/strip-shell-comments.awk general/overlay/init):

ok   8MB chip sized 16MB: refuses to format (#1998)
ok   healthy 8MB camera: formats as before
ok   real 16MB chip, 8MB layout: formats as before
ok   blank flash proves nothing: formats as before
ok   unreadable mtd0: falls through and formats
ok   no rootfs_data partition: guard stands down
ok   hex sizes with leading zeros are not read as octal

All overlay format guard checks passed.

Cases 2 and 3 are the ones that matter for a false positive. Case 3 is the geometry
openipc.org hands out when a visitor picks the 8 MB layout on a 16 MB chip: the same
partition table as the brick, on a chip that really is 16 MB. It must format, and does.

The rest of the shell gates, unchanged:

$ STRICT=1 bash .github/scripts/test_shell_parse.sh
checked 144 shell script(s)
all parsed clean under busybox ash

$ STRICT=1 bash .github/scripts/test_strip_shell_comments.sh
ok   144 shipped scripts parse identically after stripping (147978 bytes saved)
All strip-shell-comments checks passed.

$ python3 .github/scripts/lint-workflow-shell.py --self-test
checked 59 run block(s)
all run blocks parse clean
self-test passed

$ python3 .github/scripts/ci-matrix.py --self-test
ci-matrix: self-test ok (99 boards, 136 packages, 56 cases)

general/overlay/init widens the matrix to every board, as it should:

$ printf 'general/overlay/init\n' | python3 .github/scripts/ci-matrix.py --stdin
needs-build=true
reason=general/overlay/init affects every board

Scope

  • No kernel patches under general/package/all-patches/linux/ (those go to OpenIPC/linux)
  • No files specific to a single retail camera model (those go to OpenIPC/builder)
  • No probing or bring-up tooling (that goes to OpenIPC/ipctool)
  • Nothing under general/overlay/ or in a shared load_<vendor> script hardcodes a value specific to my board
  • Package sources come from an OpenIPC repository, and any version bump keeps at least the specificity of the pin it replaces (a new package should pin a full 40-character SHA)
  • No LD_PRELOAD, and no binaries that cannot be rebuilt from source
  • New code is selected by a defconfig, so CI actually builds it

Note for review

general/overlay/usr/sbin/sysupgrade's do_wipe_overlay() erases the same partition and
has the same exposure. It is deliberately left alone here so this change stays one idea;
say the word if you would rather they move together.

SigmaStar sizes any NOR chip missing from its JEDEC table at 16MB --
drivers/sstar/flash_isp's FLASH_IC_UNKNOWN row, which declares 0x1000000
alongside a sector count that says 4MB, so it was never a real geometry.
mtd.size is taken straight from it, and our bootargs end in
"-(rootfs_data)", so on a real 8MB camera the overlay partition becomes
8896KB. jffs2 then mounts an aliased mirror of the squashfs rather than
an overlay, and the format that follows erases past 0x800000 -- which a
3-byte-addressed chip wraps back to zero, taking the bootloader with it.
The camera boots once and never again.

#1998's log is that arithmetic exactly: c->nr_blocks 139 is 16384k less
the 7488k the other four partitions hold, where a correctly sized chip
would have given 11 blocks.

Prove the wrap before refusing, so the guard can only ever stop an erase
on positive evidence: compare the first sector of the flash with the
sector the partition presents at each plausible real end of the chip. A
blank first sector, an unreadable mtd0 or a missing rootfs_data all fall
through and format as before -- the cost of a false positive is a camera
that silently forgets its settings, and that must not be paid on a guess.

The driver is the real fix and belongs in OpenIPC/linux. This is the half
that stops the damage on the images already in the field, and it is not
SigmaStar-specific: any driver that over-reports a chip lands here.
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