From 9c5ab39b62b733c8746fced311ff64e9bbbba757 Mon Sep 17 00:00:00 2001 From: cdhdt Date: Mon, 20 Jul 2026 01:27:28 +0200 Subject: [PATCH] rss_guard: free the evicted expert slab under g_pilot_mx (use-after-free) rss_guard marked the victim slot eid=-1 under the lock, unlocked, and only then freed s->slab. In that window the slot reads as {eid=-1, slab still valid} -- exactly the state pilot_realload's victim scan reuses first -- so a pilot worker could claim it and pread into the slab while it was being freed: use-after-free, or a double-free if the loader took its own realloc path. Keep the free and the pointer/capacity NULLing inside the critical section, so 'slab valid' and 'slot reusable' are never simultaneously observable. The lock is held across a free() (microseconds); workers only take it briefly for scan+reserve. Reproduces on dev with a SINGLE pilot worker (rss_guard runs on the main thread while the pilot worker runs in the background), whenever the RAM guard is active (RSS_GUARD_GB, or any resolved g_ram_budget_gb). make check 83/83, native + portable builds 0 warnings. --- c/colibri.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/c/colibri.c b/c/colibri.c index 4b37fa8..8735a2e 100644 --- a/c/colibri.c +++ b/c/colibri.c @@ -4331,16 +4331,21 @@ static void rss_guard(Model *m){ if(lru<0){ pthread_mutex_unlock(&g_pilot_mx); continue; } ESlot *s=&m->ecache[l][lru]; s->eid=-1; /* nascosto: nessun hit/evict altrui */ - pthread_mutex_unlock(&g_pilot_mx); int64_t sb=s->slab_cap + s->fslab_cap*4; #ifdef COLI_METAL if(s->slab && g_metal_enabled) coli_metal_unregister(s->slab); #endif + /* La free resta SOTTO g_pilot_mx. Sbloccando prima, lo slot e' visibile come + * {eid=-1, slab ancora valido}: il pilota (pilot_realload) riusa per primo gli + * slot eid==-1, quindi puo' prenderlo e fare pread dentro lo slab MENTRE lo + * liberiamo -> use-after-free / double-free. Slab valido e slot riusabile + * devono restare mutuamente esclusivi finche' il puntatore non e' NULL. */ compat_aligned_free(s->slab); free(s->fslab); s->slab=NULL; s->fslab=NULL; s->slab_cap=s->fslab_cap=0; QT *q[3]={&s->g,&s->u,&s->d}; for(int k=0;k<3;k++){ q[k]->qf=NULL; q[k]->q8=NULL; q[k]->q4=NULL; q[k]->s=NULL; } s->used=0; /* primo candidato al riuso */ + pthread_mutex_unlock(&g_pilot_mx); freed += sb; dropped++; } if(m->ecap>2) m->ecap--; /* il tetto scende: niente ricrescita */