Clear Sky Science · it

Frazionamento statico basato sullo stack e mutazione di binari WebAssembly per generare sotto-binari validi

· Torna all'indice

Perché ha importanza suddividere il codice in pezzi più piccoli

Le moderne applicazioni web si basano sempre più su WebAssembly, un linguaggio compatto simile a codice macchina che permette a programmi scritti in C, C++ o Rust di girare a velocità quasi nativa nei browser, nel cloud e perfino su piccoli dispositivi embedded. Con l’aumento dei sistemi che eseguono WebAssembly, servono modi sicuri e completi per testare questi motori di esecuzione alla ricerca di bug e vulnerabilità. Tuttavia i programmi WebAssembly reali sono grandi, complessi e difficili da raccogliere in varietà sufficiente. Questo articolo presenta un nuovo metodo per ritagliare programmi WebAssembly esistenti in molti mini-programmi più piccoli e autonomi e poi modificarli in modo intelligente. Il risultato è una ricca fornitura di casi di test validi e di dimensioni ridotte che possono mettere a dura prova i motori WebAssembly in modo molto più efficace degli strumenti attuali.

Figure 1
Figure 1.

Da un grande programma a molti più piccoli

Gli autori partono dall’osservazione che generare semplicemente codice WebAssembly casuale quasi mai funziona: il linguaggio è strettamente verificato, in particolare per l’uso dello stack che contiene valori temporanei. Invece di creare codice da zero, iniziano con un binario WebAssembly “base” esistente e applicano una tecnica chiamata program slicing. Una slice è una versione ridotta di un programma che conserva solo i frammenti di codice che influenzano un punto di interesse scelto, come una specifica istruzione all’interno di una funzione. Seguendo a ritroso il flusso di dati, l’uso della memoria e il controllo, il metodo estrae un frammento compatto che continua ad avere un “significato” computazionale, piuttosto che essere rumore casuale.

Mantenere lo stack invisibile in equilibrio

I programmi WebAssembly dipendono da una rigida disciplina dello stack: ogni istruzione poppa e pushpa valori, e blocchi strutturati e branch devono lasciare lo stack nella forma esatta prevista alla loro conclusione. Tagliare parti di un programma in modo ingenuo quasi sempre rompe questa disciplina, rendendo il risultato invalido. Lavori precedenti riparavano le slice riempiendole con schemi fissi di istruzioni dummy, che le rendevano eseguibili ma ne limitavano la varietà. Questo articolo invece introduce un algoritmo dedicato di correzione dell’equilibrio dello stack. Analizza il flusso di controllo dentro ciascuna slice, individua i punti in cui branch o terminazioni di blocco disturbano la forma attesa dello stack e poi inserisce sequenze di istruzioni su misura che scartano valori non necessari e sintetizzano nuovi valori dei tipi corretti. Questi piccoli “gadget” ripristinano la correttezza permettendo schemi di istruzioni molto più ricchi rispetto al semplice padding.

Costruire mini‑programmi completi con complessità controllata

Una slice corretta è comunque solo il corpo di una funzione, e quella funzione può chiamare altre funzioni o dipendere da caratteristiche a livello di modulo come variabili globali e memoria lineare. Gli autori estendono quindi il loro metodo per assemblare mini‑moduli eseguibili completi. Partendo da una funzione di ingresso scelta casualmente, eseguono iterativamente slicing e correzione su ogni funzione chiamata, fino a una “profondità di chiamata” scelta dall’utente che controlla quante livelli di chiamate vengono preservati. Oltre quella profondità, le chiamate vengono sostituite con brevi segnaposto che mimano il numero corretto di argomenti e risultati senza includere ulteriore codice. Il sistema ricostruisce inoltre le sezioni circostanti di un modulo WebAssembly—tipi, tabelle, memoria e così via—cosicché ogni binario generato sia autonomo e possa essere eseguito direttamente da strumenti e runtime standard.

Figure 2
Figure 2.

Aggiungere varietà tramite mutazione accurata

Anche con lo slicing, molti binari generati somigliano ancora al programma padre. Per ampliare la copertura, gli autori introducono una fase di mutazione che lavora a ritroso all’interno di ogni funzione. Per le istruzioni che consumano input, l’algoritmo sceglie casualmente un nuovo tipo di risultato, individua istruzioni sostitutive che producano quel tipo e poi muta ricorsivamente le istruzioni precedenti che forniscono i suoi input in modo che l’intera catena combaci ancora. Questo preserva la correttezza dello stack ristrutturando la computazione. Rispetto a un insieme di riferimento di programmi WebAssembly reali, i binari mutati contengono molte più sequenze di istruzioni distinte e fanno un uso molto maggiore di funzionalità raramente viste come le operazioni vettoriali, importanti per testare a fondo l’implementazione dei motori WebAssembly.

Cosa rivelano gli esperimenti

Per valutare l’approccio, gli autori hanno implementato circa 17.000 righe di codice Python coprendo quasi l’intero standard WebAssembly 2.0. Utilizzando un ampio dataset pubblico di migliaia di binari reali, hanno mostrato che la loro tecnica può ridurre di routine i programmi a una frazione del conteggio originale di istruzioni—spesso sotto il 40% delle dimensioni massime di riferimento—mantenendoli validi in oltre il 99% dei casi quando la mutazione è abilitata. Il metodo è veloce, tipicamente ben al di sotto di un secondo per ogni binario generato di dimensione moderata, e aumenta in modo significativo sia la gamma dei tipi di istruzioni sia il numero di sequenze uniche di istruzioni osservate. In termini pratici, questo significa che tester e ricercatori di sicurezza possono trasformare un corpus modesto di programmi WebAssembly in una grande e diversificata suite di binari piccoli e validi, ideali per fuzzing, test differenziali e test di regressione dei runtime WebAssembly.

Come questo aiuta a mantenere WebAssembly sicuro

In parole semplici, l’articolo descrive un “affinatore di codice” intelligente per WebAssembly: taglia grandi programmi in molti pezzi più piccoli e significativi, li ripara automaticamente affinché rispettino ancora le rigide regole del linguaggio e poi li rimodella per esplorare angoli insoliti dell’insieme di istruzioni. Poiché questi mini‑programmi restano realistici ma compatti, possono essere eseguiti rapidamente e in gran numero, aumentando le probabilità di scovare bug sottili o problemi di sicurezza nel software che esegue WebAssembly. Man mano che WebAssembly si diffonde dai browser al cloud e ai dispositivi, una generazione sistematica di binari di test validi e diversificati offre uno strumento importante per mantenere questo ecosistema robusto e sicuro.

Citazione: Choi, G., Jeon, S. Stack-based static WebAssembly binary slicing and mutation for generating valid sub-binaries. Sci Rep 16, 10910 (2026). https://doi.org/10.1038/s41598-026-45837-y

Parole chiave: Test di WebAssembly, program slicing, mutazione binaria, sicurezza del software, verifica a runtime