From 1d78e0fa7075a3bd8814a11fa4a4b8b98e628243 Mon Sep 17 00:00:00 2001 From: "shoney.arickathil" Date: Fri, 4 Sep 2026 13:27:20 +0200 Subject: [PATCH] fix(runtime): don't deref a poisoned class's NULL fmap during migration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - wo_schema_diff poisons a class (fmap=NULL, new_cid=NONE) when a referenced/nested type changed or a field type is incompatible — the field-level map does not apply and wo_wal_migrate transcodes it instead. - main.c's pre-migration "migrating `X`: +/-fields" print loop dereferenced fmap unconditionally, so a poisoned-but-field-added class (e.g. a wmux Window whose nested Vte gained fields) was a NULL read → SIGSEGV at boot, before the migrate call could refuse or transcode. - guard the field detail on fmap != NULL; for a poisoned class print "(a referenced type changed — cannot migrate in place)" and let wo_wal_migrate proceed. It then transcodes cleanly when no record blocks it — so an additive nested change (Vte +oscbuf +title) now migrates and the session replays, instead of crashing serve. - test_wal 5966/0; verified against the real WAL that crashed (recovers session `main`); wmux gate 52/0. Co-Authored-By: Claude Opus 4.8 (cherry picked from commit 35efa214d20dd7b055910ae66e4bfcc6201821c9) --- runtime/src/main.c | 35 ++++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/runtime/src/main.c b/runtime/src/main.c index f74425b..17cb855 100644 --- a/runtime/src/main.c +++ b/runtime/src/main.c @@ -338,19 +338,28 @@ int main(int argc, char **argv) { const wo_schema_class *ok = &stored->classes[c]; fprintf(stderr, "wovm: %s: migrating `%.*s`:", wal_path, (int)ok->name_len, (const char *)ok->name); - for (uint32_t f = 0; f < ok->field_cnt; f++) - if (plan.classes[c].fmap[f] == -1) - fprintf(stderr, " -%.*s", (int)ok->fields[f].name_len, - (const char *)ok->fields[f].name); - const wo_schema_class *nk = - &compiled_schema.classes[plan.classes[c].new_cid]; - for (uint32_t f = 0; f < nk->field_cnt; f++) { - int found = 0; - for (uint32_t g = 0; g < ok->field_cnt && !found; g++) - found = plan.classes[c].fmap[g] == (int32_t)f; - if (!found) - fprintf(stderr, " +%.*s", (int)nk->fields[f].name_len, - (const char *)nk->fields[f].name); + /* A poisoned class (a referenced/nested type changed, or a + * field's type is incompatible) has fmap == NULL and + * new_cid == NONE — the field-level diff does not apply. + * Dereferencing fmap here was a NULL read (SEGV); name the + * situation and let wo_wal_migrate below refuse cleanly. */ + if (plan.classes[c].fmap) { + for (uint32_t f = 0; f < ok->field_cnt; f++) + if (plan.classes[c].fmap[f] == -1) + fprintf(stderr, " -%.*s", (int)ok->fields[f].name_len, + (const char *)ok->fields[f].name); + const wo_schema_class *nk = + &compiled_schema.classes[plan.classes[c].new_cid]; + for (uint32_t f = 0; f < nk->field_cnt; f++) { + int found = 0; + for (uint32_t g = 0; g < ok->field_cnt && !found; g++) + found = plan.classes[c].fmap[g] == (int32_t)f; + if (!found) + fprintf(stderr, " +%.*s", (int)nk->fields[f].name_len, + (const char *)nk->fields[f].name); + } + } else { + fprintf(stderr, " (a referenced type changed — cannot migrate in place)"); } fprintf(stderr, "\n"); }