Scudo

Scudo è un allocatore di memoria in modalità utente dinamico, o allocatore heap, progettato per essere resiliente alle vulnerabilità correlate all'heap (come overflow del buffer basato sull'heap, use after free e double free) mantenendo le prestazioni. Fornisce le primitive di allocazione e deallocazione C standard (come malloc e free), nonché le primitive C++ (come new e delete).

Scudo è più una mitigazione che un rilevatore di errori di memoria vero e proprio come AddressSanitizer (ASan).

A partire dalla release di Android 11, scudo viene utilizzato per tutto il codice nativo (tranne che sui dispositivi con poca memoria, dove viene ancora utilizzato jemalloc). In fase di runtime, tutte le allocazioni e deallocazioni dell'heap nativo vengono gestite da Scudo per tutti gli eseguibili e le relative dipendenze della libreria e il processo viene interrotto se viene rilevato un danneggiamento o un comportamento sospetto nell'heap.

Scudo è open source e fa parte del progetto compiler-rt di LLVM. La documentazione è disponibile all'indirizzo https://llvm.org/docs/ScudoHardenedAllocator.html. Il runtime di Scudo viene fornito come parte della toolchain Android e il supporto è stato aggiunto a Soong e Make per consentire l'attivazione semplice dell'allocatore in un binario.

Puoi attivare o disattivare la mitigazione aggiuntiva all'interno dell'allocatore utilizzando le opzioni descritte di seguito.

Personalizzazione

Alcuni parametri dell'allocatore possono essere definiti in base al processo in diversi modi:

  • Staticamente:definisci una funzione __scudo_default_options nel programma che restituisce la stringa di opzioni da analizzare. Questa funzione deve avere il seguente prototipo: extern "C" const char *__scudo_default_options().
  • In modo dinamico:utilizza la variabile di ambiente SCUDO_OPTIONS contenente la stringa di opzioni da analizzare. Le opzioni definite in questo modo ignorano qualsiasi definizione effettuata tramite __scudo_default_options.

Sono disponibili le seguenti opzioni.

Opzione Valore predefinito a 64 bit Valore predefinito a 32 bit Descrizione
QuarantineSizeKb 256 64 La dimensione (in KB) della quarantena utilizzata per ritardare la deallocazione effettiva dei chunk. Un valore inferiore potrebbe ridurre l'utilizzo della memoria, ma diminuire l'efficacia della mitigazione; un valore negativo ripristina i valori predefiniti. Se imposti sia questo valore che ThreadLocalQuarantineSizeKb su zero, la quarantena viene disattivata completamente.
QuarantineChunksUpToSize 2048 512 Le dimensioni (in byte) fino alle quali i blocchi possono essere messi in quarantena.
ThreadLocalQuarantineSizeKb 64 16 Le dimensioni (in KB) della cache per thread utilizzata per scaricare la quarantena globale. Un valore inferiore può ridurre l'utilizzo della memoria, ma potrebbe aumentare la contesa nella quarantena globale. Se imposti sia questa opzione sia QuarantineSizeKb su zero, la quarantena viene disattivata completamente.
DeallocationTypeMismatch false false Attiva la segnalazione degli errori su malloc/delete, new/free, new/delete[]
DeleteSizeMismatch true true Attiva la segnalazione di errori in caso di mancata corrispondenza tra le dimensioni di new e delete.
ZeroContents false false Consente di abilitare i contenuti di zero chunk in fase di allocazione e deallocazione.
allocator_may_return_null false false Specifica che l'allocatore può restituire null quando si verifica un errore recuperabile anziché terminare il processo.
hard_rss_limit_mb 0 0 Quando l'RSS del processo raggiunge questo limite, il processo termina.
soft_rss_limit_mb 0 0 Quando l'RSS del processo raggiunge questo limite, le allocazioni successive non vanno a buon fine o restituiscono null (a seconda del valore di allocator_may_return_null), finché l'RSS non torna a un livello che consente nuove allocazioni.
allocator_release_to_os_interval_ms 5000 N/D Interessa solo un allocatore a 64 bit. Se impostato, tenta di liberare la memoria inutilizzata per il sistema operativo, ma non più spesso di questo intervallo (in millisecondi). Se il valore è negativo, la memoria non viene rilasciata al sistema operativo.
abort_on_error true true Se impostato, lo strumento chiama abort() anziché _exit() dopo aver stampato il messaggio di errore.

Convalida

Al momento non sono disponibili test CTS specifici per Scudo. Assicurati invece che i test CTS vengano superati con o senza Scudo abilitato per un determinato binario per verificare che non influisca sul dispositivo.

Risoluzione dei problemi

Se viene rilevato un problema non recuperabile, l'allocatore visualizza un messaggio di errore nel descrittore di errore standard e poi termina il processo. Le analisi dello stack che portano alla terminazione vengono aggiunte nel log di sistema. L'output di solito inizia con Scudo ERROR: seguito da un breve riepilogo del problema e da eventuali suggerimenti.

Di seguito è riportato un elenco dei messaggi di errore attuali e delle loro potenziali cause:

  • corrupted chunk header: La verifica della checksum dell'intestazione del chunk non è riuscita. Ciò è probabilmente dovuto a uno dei due motivi seguenti: l'intestazione è stata sovrascritta (parzialmente o totalmente) oppure il puntatore passato alla funzione non è un blocco.
  • race on chunk header: Due thread diversi tentano di manipolare la stessa intestazione contemporaneamente. Questo di solito è sintomatico di una condizione di competizione o di una mancanza generale di blocco durante l'esecuzione di operazioni su quel blocco.
  • invalid chunk state: il blocco non si trova nello stato previsto per una determinata operazione, ad esempio non è allocato quando si tenta di liberarlo o non è in quarantena quando si tenta di riciclarlo. Un doppio free è il motivo tipico di questo errore.
  • misaligned pointer: i requisiti di allineamento di base sono applicati rigorosamente: 8 byte sulle piattaforme a 32 bit e 16 byte su quelle a 64 bit. Se un puntatore passato alle nostre funzioni non corrisponde a questi, il puntatore passato a una delle funzioni è disallineato.
  • allocation type mismatch: se questa opzione è attivata, una funzione di deallocazione chiamata su un blocco deve corrispondere al tipo di funzione chiamata per allocarlo. Questo tipo di mancata corrispondenza può introdurre problemi di sicurezza.
  • invalid sized delete: Quando viene utilizzato l'operatore di eliminazione dimensionato C++14 e il controllo facoltativo è abilitato, si verifica una mancata corrispondenza tra le dimensioni passate durante la deallocazione di un blocco e le dimensioni richieste durante l'allocazione. In genere si tratta di un problema del compilatore o di una confusione di tipi nell'oggetto in fase di deallocazione.
  • RSS limit exhausted: Il feed RSS massimo specificato facoltativamente è stato superato.

Se stai eseguendo il debug di un arresto anomalo nel sistema operativo stesso, puoi utilizzare una build del sistema operativo HWASan. Se stai eseguendo il debug di un arresto anomalo in un'app, puoi utilizzare anche una build dell'app HWASan.