
CVE-2026-39259
Progetto: https://github.com/alexfru/SmallerC
L'implementazione di scanf di SmallerC non impone un limite superiore alle letture di stringhe quando %s o %[ viene usato senza una larghezza di campo esplicita nella stringa di formato. Il runtime continua a scrivere nel buffer di destinazione finché non incontra uno spazio bianco o EOF, indipendentemente dalla dimensione effettivamente allocata del buffer. Qualsiasi byte oltre il confine finisce direttamente sullo stack sovrascrivendo ciò che il compilatore ha posto sopra il buffer: variabili locali, registri salvati, indirizzo di ritorno.
Questa non è una classe di vulnerabilità nuova. Le letture di stringhe scanf senza limite sono documentate fin dagli albori del C e qualsiasi strumento di analisi statica competente le segnalerà. Ciò che rende questo problema degno di segnalazione nel contesto specifico di SmallerC è l'ambiente di destinazione: SmallerC è progettato per DOS e target embedded bare-metal, piattaforme che per definizione non forniscono stack canary, ASLR, bit NX o nessuna delle mitigazioni che rendono difficile lo sfruttamento sui sistemi moderni. La stessa primitiva che richiederebbe un notevole sforzo di ricerca per trasformarla in uno exploit funzionante su un binario Linux indurito diventa considerevolmente più gestibile su un programma DOS che gira su uno stack piatto e prevedibile.
Il buffer è di 16 byte. L'input è di 20 byte non di spaziatura. La conversione %s non ha uno specificatore di larghezza, quindi sscanf legge tutti i 20 byte più un terminatore nullo — 21 byte in totale — in un'allocazione da 16 byte. I 5 byte oltre il confine corrompono la memoria di stack adiacente. Ciò che viene corrotto esattamente dipende dalle decisioni di layout dello stack del compilatore per quella particolare funzione, ma la sovrascrittura in sé è deterministica e incondizionata ogni volta che questo percorso di codice viene eseguito con questo input.
Prova di concetto
#include <stdioh>
#include <stringh>
/*
* Build with SmallerC targeting DOS or bare-metal
* Demonstrates unbounded %s write past a fixed stack buffer
*
* buffer is 16 bytes payload is 20 non-whitespace bytes
* sscanf writes 21 bytes (20 + null terminator) into buffer
* corrupting 5 bytes of adjacent stack memory
*
* To observe the corruption inspect stack memory after the call:
* the 5 bytes immediately above buffer will contain 'A' (0x41)
*/
int main() {
char buffer[16];
char canary[8];
memset(buffer 0x00 sizeof(buffer));
memset(canary 0xCC sizeof(canary)); /* marker to detect overwrite */
printf("[*] canary before: ");
for (int i = 0; i < 8; i++) printf("%02x " (unsigned char)canary[i]);
printf("\n");
sscanf("AAAAAAAAAAAAAAAAAAAA" "%s" buffer); /* 20 bytes into 16-byte buffer */
printf("[*] canary after: ");
for (int i = 0; i < 8; i++) printf("%02x " (unsigned char)canary[i]);
printf("\n");
if (memcmp(canary "\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC" 8) != 0)
printf("[!] stack corruption confirmed canary overwritten\n");
else
printf("[-] canary intact (stack layout placed it elsewhere)\n");
return 0;
}
Output atteso su una build vulnerabile:
[*] canary before: cc cc cc cc cc cc cc cc
[*] canary after: 41 41 41 41 41 cc cc cc
[!] stack corruption confirmed canary overwritten
Il posizionamento del canary rispetto al buffer dipende dal layout dello stack del compilatore. Se l'output mostra il canary intatto, la sovrascrittura sta ancora avvenendo: sta colpendo qualcos'altro sopra il buffer. Regola il reproducer ispezionando il frame di stack effettivo con un debugger per individuare dove finiscono i 5 byte corrotti.
Una precisazione che vale la pena fare esplicitamente: alcuni report in questa classe di vulnerabilità tentano di dimostrare il controllo dell'indirizzo di ritorno aggiungendo un indirizzo di destinazione dopo un byte nullo nel payload, ad esempio "AAAAAAAAAAAAAAAAAAA\x00\x90\x04\x08". Questo non funziona. La conversione %s in sscanf tratta \x00 come terminatore di stringa e smette di leggere immediatamente quando lo incontra. I byte successivi al byte nullo non vengono mai elaborati. Dimostrare un reale controllo dell'indirizzo di ritorno richiede di consegnare la sovrascrittura senza un byte nullo nella parte critica del payload, il che a sua volta richiede di conoscere l'esatto layout dello stack del binario di destinazione: la distanza dal buffer all'indirizzo di ritorno salvato, se il compilatore ha inserito padding e quali vincoli di allineamento si applicano. Nessuno di questi elementi emerge automaticamente da questo reproducer.
Ciò che il reproducer dimostra con chiarezza è la primitiva di corruzione in sé. La scrittura fuori dai limiti è reale, riproducibile e non dipende da race condition o tempistiche. Su un target DOS o embedded dove il layout dello stack è statico e prevedibile tra build diverse, passare da questa primitiva a uno exploit funzionante è uno sforzo di ricerca realistico, non un esercizio teorico.
Lo scenario interessato è limitato ma non artificioso. Un programma deve essere compilato con SmallerC, usare il parsing della famiglia scanf con uno specificatore %s o %[ senza limite, scrivere in un buffer stack a dimensione fissa e accettare input da una fonte che l'attaccante può influenzare. Tutte e quattro le condizioni devono valere simultaneamente. I programmi che usano larghezze di campo corrette — %15s per un char[16] — non sono interessati. I programmi che non analizzano input controllati dall'attaccante non sono interessati. Il problema è un difetto nel modo in cui il runtime di SmallerC gestisce il vincolo di larghezza mancante, ma diventa una questione di sicurezza solo quando il codice applicativo espone quel difetto a input non fidati.
Sul lato applicativo la correzione è semplice: specificare una larghezza di campo che lasci spazio al terminatore nullo — %15s per un buffer da 16 byte, %63s per un buffer da 64 byte. Questa è pratica C standard ed è pienamente supportata dalla sintassi delle stringhe di formato. Sul lato del progetto SmallerC, il lavoro più duraturo consiste nell'aggiungere test di regressione che coprano sia il comportamento limitato sia quello non limitato di %s e %[ in scanf, sscanf e fscanf, verificando che le larghezze di campo esplicite siano effettivamente rispettate nell'implementazione e documentando in modo prominente il pattern non sicuro. Un diagnostico a livello di compilatore che avverta quando %s o %[ compare senza una larghezza di campo in un literal di stringa di formato prevenirebbe in modo proattivo questa classe di errori e sarebbe un'aggiunta significativa alla toolchain.
La gravità è Media quando un input esterno raggiunge il percorso di codice vulnerabile. Scende a Bassa quando l'input è locale o non privilegiato. L'ambiente di destinazione — in particolare l'assenza delle mitigazioni moderne contro lo sfruttamento sulle piattaforme previste da SmallerC — è ciò che distingue questo problema da un generico avviso "non usare scanf senza limiti" e lo rende degno di essere segnalato a livello di progetto, piuttosto che trattato puramente come uso improprio a livello applicativo.
Crediti: Yousif Wazni