Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion docs/CHALLENGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ the source.

The pattern across them is the reason the document exists: **almost nothing
here was found by reading code or by a test written from imagination.** Of
407 defects, 327 were found only by running against a real device. Eighteen
408 defects, 328 were found only by running against a real device. Eighteen
more — 255, 280, 282, 327, 329, 353, 355, 356, 359, 371, 375, 380, 382, 384, 386, 387, 388 and 406 — were met while
running against one, though the fault lay in a check, the host or Mobium's own
design rather than in the platform. The other sixty-two — 4, 5, 6, 14, 23,
Expand Down Expand Up @@ -8885,6 +8885,20 @@ bytecode. It prints where and what kind, never the value, since a public
repository's CI log is public. Its selftest holds each rule to a case it must
flag, and a leak planted in a staged file failed `make docs-check`.

### 408. A car's screenshots carried a warning after the image

**Found by:** stills for Morpho Market's README, cut from a trace of checkout
test A on the Android Automotive 15 emulator. `ffmpeg` read every one as
damaged. 38 of the 39 screenshots, and a plain `mobium screenshot`, ended in
"FORTIFY: pthread_mutex_lock called on a destroyed mutex", which `screencap`
wrote to stdout after the PNG's IEND chunk; image viewers ignore it, strict
decoders do not. The same capture opens with "[Warning] Multiple displays
were found", which 211's rule already skips.

**Fixed:** a capture is cut at its IEND chunk when a short line of text
follows, the rule 211 applies to text before it; anything else after it is
refused. The same screenshot then ended at its IEND.

## Findings that were not defects

Worth recording because each one closed off an approach that looked obvious.
Expand Down
2 changes: 1 addition & 1 deletion docs/RELEASE-CHECKLIST.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
[CI workflow](../.github/workflows/ci.yml) runs on every push — formatting,
vet, both modules' tests, the four non-Go clients' own tests, and six
cross-compile targets. This file is the rest, and the rest is where most
serious defects in this project have come from: 327 of 407 were found only by
serious defects in this project have come from: 328 of 408 were found only by
running against a real device.

Work through it before tagging a release, on three substrates: an Android
Expand Down
35 changes: 29 additions & 6 deletions internal/mobiumdriver/android.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,8 @@ func (a *Android) Screenshot(ctx context.Context) ([]byte, error) {
return png, nil
}

// maxVendorPrefix bounds the text skipped ahead of a capture's PNG.
// maxVendorPrefix bounds the text skipped ahead of a capture's PNG, and cut
// after it.
const maxVendorPrefix = 4096

// pngIn is the PNG in what screencap wrote, past any lines a vendor library
Expand All @@ -236,14 +237,36 @@ func pngIn(out []byte) ([]byte, bool) {
if i < 0 {
return nil, false
}
if i == 0 {
return out, true
if i > 0 {
prefix := out[:i]
if i > maxVendorPrefix || prefix[len(prefix)-1] != '\n' || bytes.IndexByte(prefix, 0) >= 0 {
return nil, false
}
}
return withoutTrailer(out[i:])
}

// pngEnd is the IEND chunk every PNG ends with: no data, and its CRC.
var pngEnd = []byte{0, 0, 0, 0, 'I', 'E', 'N', 'D', 0xAE, 0x42, 0x60, 0x82}

// withoutTrailer is png without text written after it. The Android
// Automotive 15 emulator printed "FORTIFY: pthread_mutex_lock called on a
// destroyed mutex" after 38 of 39 captures, and the files kept it
// (CHALLENGES 408). Only a short line of text is cut, as with a prefix; a
// capture with no IEND is left as it is.
func withoutTrailer(png []byte) ([]byte, bool) {
j := bytes.LastIndex(png, pngEnd)
if j < 0 {
return png, true
}
tail := png[j+len(pngEnd):]
if len(tail) == 0 {
return png, true
}
prefix := out[:i]
if i > maxVendorPrefix || prefix[len(prefix)-1] != '\n' || bytes.IndexByte(prefix, 0) >= 0 {
if len(tail) > maxVendorPrefix || bytes.IndexByte(tail, 0) >= 0 {
return nil, false
}
return out[i:], true
return png[:j+len(pngEnd)], true
}

// Tap touches a point in device pixels.
Expand Down
28 changes: 28 additions & 0 deletions internal/mobiumdriver/screencap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,31 @@ func TestPNGInSkipsAVendorLine(t *testing.T) {
}
}
}

// The trailer is what the Android Automotive 15 emulator printed after 38 of
// 39 captures in one test run, 2026-11-11.
func TestPNGInCutsAVendorLineAfterIt(t *testing.T) {
png := append(append(append([]byte(nil), pngMagic...), "\x00\x00\x00\rIHDR"...), pngEnd...)
trailer := []byte("FORTIFY: pthread_mutex_lock called on a destroyed mutex (0x7ad10360c8)\n")
cases := []struct {
name string
in []byte
ok bool
}{
{"a whole capture", png, true},
{"the car's line after it", append(append([]byte(nil), png...), trailer...), true},
{"lines before and after it", append(append([]byte("[Warning] Multiple displays were found\n"), png...), trailer...), true},
{"binary after it", append(append([]byte(nil), png...), "\x00\x01junk"...), false},
{"too much after it", append(append([]byte(nil), png...), strings.Repeat("x", maxVendorPrefix+1)...), false},
}
for _, c := range cases {
got, ok := pngIn(c.in)
if ok != c.ok {
t.Errorf("%s: ok=%v, want %v", c.name, ok, c.ok)
continue
}
if ok && !bytes.Equal(got, png) {
t.Errorf("%s: got %d bytes, want the PNG's %d", c.name, len(got), len(png))
}
}
}
Loading