Loading
Commits on Source 2
-
cznic authored
VaList takes anything outside int/uint/int32/.../float64/uintptr through a default case that memcpy's reflect.TypeOf(v).Size() bytes of the value. For a Go string that copies the 16 byte {ptr, len} header into the va_list. C reads the first word as a char* and, Go strings not being NUL terminated, vsnprintf runs past the end of the string, appending whatever sits next to it on the Go heap. The header is also wider than the 8 bytes a caller sizes a va_list slot at, so the write itself overruns the allocation. That is how modernc.org/quickjs leaked Go runtime internals into a script-visible Error.message, see quickjs#14. C has no string type, so ccgo never emits one here; every occurrence is hand written and always a bug. Panic instead of corrupting silently, in both the ccgo/v3 (etc.go) and ccgo/v4 (rtl.go) copies. The default case stays as it is, that is what carries a C struct or array passed by value. Callers must pass CString(s). Note that a modernc.org/quickjs older than v0.22.0 trips this panic on any host function error. Co-Authored-By:Claude Opus 5 (1M context) <noreply@anthropic.com>
-
cznic authored
852ba3a8 moved musl lock state out of the C lock word *p and into a process-global map keyed on the word's address. musl's freeaddrinfo deliberately frees the buffer holding its lock word while still holding that lock and never unlocks: LOCK(b->lock); if (!(b->ref -= cnt)) free(b); else UNLOCK(b->lock); That is correct in C, because the word is inside the block and dies with it. With the state in a global map the entry outlived the free, still locked, so when the allocator handed the same address to a later getaddrinfo the next freeaddrinfo blocked forever in ___lock at zero CPU, with no error. Deterministic on linux/386 and linux/arm, where the allocator recycles the address; latent on linux/amd64, which escapes on allocator behaviour rather than by design. Reported as libtcl9.0's 386 and arm builders no longer completing test runs. Put all lock state back in *p, as musl does: 0 free, 1 held, 2 held with a waiter possibly parked, driven by a_cas/swap. Freeing the memory then discards the state exactly as C expects, and a recycled calloc'd block starts out unlocked. Blocking uses a parking lot keyed by address in place of musl's futex: lockWait re-checks *p under lockParkMu and ___unlock stores to *p before lockWake takes that mutex, so a release landing before the waiter parks is seen as a value change rather than lost, which is what made #51 reachable. Parking lot entries exist only while a goroutine is really parked, so an abandoned lock leaves nothing behind and the hazard is gone generically, not just for freeaddrinfo. The global mutex is off the fast path, and an unbalanced ___unlock is now inert instead of a nil dereference. Add TestMuslLockLifetime: a recycled lock word, parking lot reclamation over the uncontended, contended and abandoned paths, an unbalanced unlock, and a bounded getaddrinfo/freeaddrinfo loop. TestIssue51 keeps its lock word in a package-level Go var; the race detector ignores atomics outside the Go arena and data segments, so a word in libc-allocated memory would carry no happens-before edge to the Go state a critical section guards. The real static musl lock words are package-level [1]int32, so this also matches them. Verified on linux/386 and linux/arm, where the reproducer completes and the unfixed ___lock hangs on the second iteration: go test -race, TestLibc (473 files, 0 build and 0 exec failures), build_all_targets across all targets and tags, and modernc.org/libtcl9.0 both for httpProxy.test and for its full suite (170 test files, 68391 assertions, 0 failed on each). Retract v1.74.2 and v1.74.3, which carry the defect. Co-Authored-By:
Claude Opus 5 (1M context) <noreply@anthropic.com>