Gli ingegneri di Apple hanno annunciato la disponibilità per testare la modalità «-fbounds-safety» per il compilatore Clang, che fornisce garanzie di sicurezza nella gestione dei buffer nel codice C. Questa modalità è inclusa nel fork LLVM supportato da Apple per il progetto Swift. È prevista una graduale integrazione della funzionalità «-fbounds-safety» nella base di codice principale di LLVM/Clang.
Si segnala che il meccanismo di protezione proposto è già attivamente utilizzato nei prodotti Apple, come il kernel XNU, i firmware, le librerie audio e i decodificatori di immagini. L'attivazione della modalità «-fbounds-safety» comporta una riduzione delle prestazioni delle applicazioni in media del 5% (con un intervallo da -1% a 29%), un aumento delle dimensioni del codice del 9.1% (con un intervallo da -1.4% a 38%) e un rallentamento della compilazione dell'11%.
L'uso della modalità «-fbounds-safety» per rilevare automaticamente le uscite dai limiti della memoria associate a un puntatore richiede l'aggiunta di annotazioni speciali nel codice e l'inclusione del file di intestazione «ptrcheck.h». L'essenza del metodo di protezione proposto è l'aggiunta automatica di controlli per garantire il rispetto dei limiti consentiti, basati su annotazioni fornite manualmente o sulle dimensioni note al compilatore.
A differenza dell'uso degli puntatori estesi (wide pointer) nel codice, che forniscono informazioni sui limiti superiori e inferiori del buffer oltre all'indirizzo, l'uso della modalità «-fbounds-safety» non viola l'ABI (Application Binary Interface), non modifica il formato degli puntatori esportati e non richiede una revisione immediata di tutto il progetto. In modalità «-fbounds-safety», gli puntatori estesi vengono applicati solo in aree che non si sovrappongono all'ABI, mentre per gli puntatori che influenzano l'ABI vengono utilizzati gli puntatori normali con l'inserimento di controlli generati in base alle annotazioni con informazioni sui limiti.
Le annotazioni devono essere collegate ai puntatori nei campi strutturati e ai parametri delle funzioni che indicano un array di oggetti, così come alle variabili globali con puntatori. Non è necessario aggiungere annotazioni per i puntatori nelle variabili locali, poiché vengono trattati automaticamente come puntatori estesi, già inclusivi di informazioni sui limiti consentiti. Suggerimenti sulle strutture nel codice che richiedono l'aggiunta di annotazioni vengono mostrati dal compilatore quando viene eseguito con il flag "-fbounds-safety".
Il modello di protezione basato su "-fbounds-safety" può essere implementato gradualmente, file per file, senza interrompere lo sviluppo dell'intero progetto. L'aggiunta di protezione al progetto si riduce all'indicazione delle annotazioni in un determinato file di codice, all'eliminazione degli avvisi del compilatore e alla conduzione di test sul funzionamento del programma, dopo di che queste fasi vengono ripetute per il file successivo. Il codice con annotazioni aggiunte rimane compatibile con il codice C standard e i compilatori che non supportano "-fbounds-safety" (quando compilato con altri compilatori o senza il flag "-fbounds-safety", semplicemente non verranno aggiunti controlli aggiuntivi sui limiti).
Durante l'esecuzione del programma, se viene rilevato un accesso oltre i limiti consentiti, viene generata un'eccezione e il programma termina la sua esecuzione. Anche una conclusione anomala può verificarsi in caso di annotazioni non corrette, quindi quando si utilizza «-fbounds-safety» è necessario eseguire ulteriori test sul funzionamento del programma.
Nell'esempio seguente, nell'argomento «int *p» è stata inserita l'annotazione «__counted_by(n)», che aggiunge un ulteriore controllo sui limiti consentiti, attivo durante l'esecuzione. Se si tenta di compilare il codice in modalità «-fbounds-safety» senza specificare questa annotazione, il compilatore emetterà un avviso sull'assenza di informazioni sui limiti dell'array durante l'elaborazione dell'espressione «p[i] = 0». #include void init_buf(int *__counted_by(n) p, int n) { for (int i = 0; i < n; ++i) p[i] = 0; // in modalità «-fbounds-safety», il compilatore inserirà automaticamente un controllo simile al codice «if (i = n) trap();» }
Per le variabili locali con puntatori, le verifiche vengono collegate automaticamente, ad esempio: void foo(int i){ char *buf = (char *)malloc(10); // per il puntatore buf verranno salvate informazioni sui limiti buf[i] = 0xff; // verrà automaticamente inserito il controllo «if (buf + i = buf + 10) trap();» }
Quando possibile, il compilatore esegue ottimizzazioni e evita di aggiungere codice superfluo, se nel codice sono già presenti i controlli necessari. Ad esempio: for (size_t i = 0; i < count; ++i) { buf[i] = i; // il controllo "if (i = count) trap()" non verrà aggiunto, poiché sopra è già presente la condizione «i < count" e i non può essere minore di 0. }
Annotazioni principali:
- «__counted_by(N)» — definisce la dimensione del buffer in elementi del tipo target.
- «__sized_by(N)» — definisce la dimensione del buffer in byte.
- «__ended_by(P)» — imposta il limite superiore del buffer.
- «__null_terminated» — considera il carattere nullo come fine del buffer.
- «__single» — associa il puntatore a un singolo oggetto. Utilizzato per impostazione predefinita per i puntatori che influenzano l'ABI, se l'annotazione non è esplicitamente specificata.
- «__bidi_indexable» — puntatore avanzato con informazioni sui limiti superiori e inferiori. Utilizzato per impostazione predefinita per puntatori che non influenzano l'ABI.
- «__indexable» — puntatore avanzato con informazioni sul limite superiore.
- «__unsafe_indexable» — puntatore senza verifica dei limiti (per la portabilità con codice non sicuro, ad esempio per ottenere puntatori da codice esterno).
Fonte: opennet.ru
