Three player-visible notify sites still computed English morphology in C
and passed it as %s — the #1622 shape:
%d queue entr%s removed. y / ies (cque, two sites)
%d orphaned attribute name%s purged. "" / s (@dbclean)
MN_(singular, plural, n) sends the count to the catalogue. Regenerates
the pot and fills xx.po; ko.po leaves the new entries untranslated.
Loading a corrupt/malicious DB with an out-of-range user-attribute number
corrupted memory or OOMed: the number indexes anum_table via anum_set (a bare
`anum_table[x]=v` macro) and anum_extend (allocates a dense (x+1) table), with
no validation. The read path atr_num validates anum<0||>top; the write path
(vattr_define_LEN -> anum_extend/anum_set) did not (incomplete hardening,
family of #805/#806/#807).
Live-verified via flatfile import (+A record, novel attr name):
- +A-10000000 -> anum_table[-10000000] write -> SIGSEGV.
- +A-5 -> anum_table[-5] write in valid heap -> silent corruption.
- +A999999999 -> anum_extend allocs ~8GB dense table -> OOM kill.
File-derived numbers reach this from the flatfile +A handler (getref) and the
SQLite attr-name load (db.cpp:3626). A huge number also seeds attr_next enormous
via g_max_nam_atr/max_attrnum_loaded -> later runtime OOM.
Fix:
- New A_USER_MAX (0x01000000 = 16M) constant in attrs.h beside A_USER_START.
- vattr_define_LEN rejects number < A_USER_START || > A_USER_MAX (central backstop).
- The flatfile +A handler and the SQLite attr-name callback validate and skip
the bad record (graceful degradation; flatfile path logs it).
Verified bite-then-not-bite: malformed +A flatfiles SIGSEGV/silent-corrupt/
OOM-kill on the old build, load cleanly (bad record skipped) on the fixed build;
valid DB still imports; smoke 1115/1115.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Eliminate the ISOUTOFMEMORY macro that unconditionally aborted on allocation
failure. Each of the 23 call sites now handles OOM appropriately:
- Fatal sites (buffer pools, db array, anum table): mux_assert or OutOfMemory
- Recoverable sites (queue, mail, commands, guests, vattrs, config, restart,
forward lists): log the failure and return gracefully
Also fix g_dump_child_pid portability: volatile pid_t -> volatile sig_atomic_t
with explicit casts in ganl_adapter.cpp.
Close integer overflow issue as false alarm (getstring_noalloc uses a bounded
static buffer, so nBuffer+1 cannot wrap).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1. @dbclean: purge SQLite before mutating in-memory state, so a
failed purge leaves the process and database consistent.
2. host_strtod: use mux_atof() instead of raw libc strtod() to
match engine numeric parsing semantics (trailing junk handling,
special float strings, input clamp).
3. JIT FCALL1/FCALL2: on non-IEEE-SNAN systems, skip native FP
fast path for domain-guarded functions (ASIN, ACOS, LOG, LOG10,
SQRT, POWER, FMOD, FDIV) so they fall through to ECALL and
use the interpreter's "Ind" domain checks.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@dbclean now finds user-defined attribute names (attrnum >= 256) that
no object references, removes them from both SQLite and the in-memory
vattr maps, then runs ANALYZE to refresh query planner statistics.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>