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.
This commit is contained in:
cdhdt
2026-07-20 01:27:28 +02:00
parent 17fbdfa8f8
commit 9c5ab39b62
+6 -1
View File
@@ -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 */