fix(altivec): vec_strcpy over-reads past the source page boundary (DSI crash on G4) - #438
Conversation
vec_strcpy guarded its speculative source loads against crossing a 4K
page using the DESTINATION pointer, but the lvx loads come from the
SOURCE. When the source string ends near a page boundary while the
destination is mid-page, the dest-based counter is too large and the
loop keeps loading source vectors into the next (possibly unmapped)
page, causing a DSI crash on AltiVec CPUs (G4 / MPC744x/745x).
Guard from the QW-aligned source instead (lvx loads use addr & ~15), so
the loop stops at the source's page boundary, finds the NUL in the last
in-page vector, and finishes the tail by bytes. AltiVec acceleration is
preserved.
Adds test_programs/memory/{altivec_guard,memcpy_guard}.c -- guard-page
over-read/over-write tests for the AltiVec string/mem routines. Verified
under QEMU emulating an MPC 7447/7457 (G4, AltiVec): altivec_guard goes
from a DSI crash at strcpy len=16 to PASS across bcopy/memcmp/strcpy/
bzero x len 1..1024.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Regression-checked this change against the clib4 Build: all 400 buildable test programs compile cleanly against the rebuilt library — no build regression. Fix verification (all PASS on the emulated G4):
The rebuilt For transparency: 🤖 Generated with Claude Code |
Addresses the amigans.net 0.5.0 reports: - close-freeze (#1): native at-exit window teardown in libamigaawt, so a Swing JFrame with the default EXIT_ON_CLOSE (System.exit without dispose) no longer leaves an open Intuition window that hangs the machine. - keyboard in games (#4): AmigaEventPump dispatches a synthesized KeyEvent to a JComponent (the focused window's JRootPane) when the focus owner is null, so WHEN_IN_FOCUSED_WINDOW bindings fire (were lost to the bare java.awt.Frame). - class-version gate (#5): JamVM rejects class-file major > 52 with UnsupportedClassVersionError instead of a far-away BootstrapMethodError (carried in docs/jamvm-amiga-openjdk.patch). - clib4.library is now bundled and installed to LIBS: when absent (#2), and the `java` launcher gets the AmigaDOS script bit + a C: copy so it runs from any Shell; example jars and the VM test suite ship in the release (#3). (install.py / JavaOS4InstallerLocale.py / package.sh.) Tests + examples: VmSuite (broad VM coverage), KeyBindTest, CloseTest, VersionGateTest, HelloJava, SwingDemo, built by tools/build-tests.sh and bundled under examples/. The release now ships the dev clib4.library (incl. the AltiVec vec_strcpy page-overread fix, AmigaLabs/clib4#438) in lockstep with build/sobjs. Validated on QEMU (MPC 7447/7457 G4): #1/#4/#5 pass; VmSuite 26/29 (3 pre-existing OpenJDK-8-on-Amiga gaps: createTempFile->SecureRandom, GC weak-ref timing). VERSION -> 0.5.1. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Issue Description
On AltiVec-equipped PowerPC (G4 / MPC744x/745x),
strcpy()— which clib4 routesto the AltiVec
vec_strcpyat runtime — can read past the end of the sourcepage and take a DSI (Data Storage Interrupt) when the source string ends close
to a 4K page boundary and the next page is unmapped. The result is a hard crash
(Grim Reaper) inside
clib4.library.It is intermittent in normal code but reproduces reliably whenever short strings
are copied out of buffers that end near a page boundary — e.g. a VM interning
class-name strings. It was first hit by a JamVM-based Java runtime copying
class-name UTF-8 strings during class loading on a 7447/7457 G4:
Cause
vec_strcpy(library/cpu/altivec/vec_strcpy.sx, the Motorola/Chuck CorleyAltiVec reference routine) speculatively loads source vectors ahead of the NUL
terminator and is supposed to guard against crossing a 4K page so it never
faults on an unmapped page. The guard was wrong in two ways:
It was computed from the destination, not the source. The faulting
lvxloads read from the SOURCE (
DS), but the "QWs to next page" counter (PBC)was derived from the DESTINATION pointer (
DD):The destination is QW-aligned, so the count is exact for the dest — but it
has no relation to where the source sits in its own page. For the common case
strcpy(fresh_buffer, string_ending_near_a_page_end)the destination ismid-page, so the counter is far too large and the loop keeps loading source
vectors straight into the next (possibly unmapped) page.
The source pointer is not QW-aligned.
lvxignores the low 4 addressbits (it loads
DS & ~15). A naive source-based(page - DS) / 16roundsdown: when the source is fewer than 16 bytes from its page boundary the
count becomes 0, which falls through to the
New_page_0path that reloads awhole page and over-reads anyway.
Solution
Compute the page-crossing guard from the source, using the QW-aligned
source address (matching what
lvxactually loads):The loop now pauses exactly at the source's page boundary, checks the last
in-page vector for the NUL (which is present, because the string ends in that
page), and finishes the tail byte-by-byte — never reading into the next page.
AltiVec acceleration is preserved (no fallback to scalar).
The length-bounded AltiVec routines (
vec_memcpy/vec_memcmp/vec_bcopy/vec_bzero) cannot over-read this way and are verified clean (see Testing).The length-unbounded scanners
vec_strchr/vec_memchr(currently behindSLOWER_ALTIVEC_FUNCTIONS) deserve the same review if they are ever enabled.Testing
Adds two guard-page tests under
test_programs/memory/(auto-built bymake compile-tests, which compiles with-fno-builtinso the calls reachclib4's routines). Each
mmaps a[data page][PROT_NONE guard page]pair andplaces the buffer so it ends exactly at the guard, so any read or write one byte
past the requested length faults immediately. A
SIGSEGVhandler reports theoffending routine + length; if the fault is not delivered as a signal, a flushed
breadcrumb names the last call attempted.
altivec_guard.c— sweeps the AltiVec-routed routines (bcopy,memcmp,strcpy,bzero) over lengths 1..1024 with the buffer at the guard boundary.memcpy_guard.c— same technique formemcpy/memmove, plus acorrectness check for them.
How it was run: built with
make compile-tests, deployed the resulting ELFto the AmigaOS 4.1 guest, and launched from the Amiga Shell via
Run(so it getsa CLI process), with output captured to a file. The rebuilt
clib4.library(carrying this fix) was installed to
LIBS:and loaded with a reboot.Result on the emulated G4:
altivec_guardstrcpy len=16(lvxinto the guard page; confirmed in the Grim Reaper crash log —lvx v2,r0,r10withr10pointing at the guard)bcopy/memcmp/strcpy/bzero× len 1..1024memcpy_guardpasses before and after (clib4 routesmemcpy/memmoveto thescalar path). The Java runtime that originally surfaced the crash no longer
faults during class loading.
🤖 Generated with Claude Code