lunedì 16 marzo 2015

PiFS, e non preoccupiamoci più dello spazio di archiviazione!

Eccoci tornati con la nostra consueta rubrica riguardante la simpatia dei programmatori e le loro divertentissime trovate!
Cosa? Non esiste una rubrica del genere sul nostro blog? Beh, sarà proprio il caso di crearne una allora!

Partiamo dal principio, ossia dal nome che apre il titolo: PiFS. “Pi” è il noto Pi greco, quel numerino che ci si trova sempre in mezzo alle scatole e che quindi non dovrebbe aver bisogno di ulteriori presentazioni.
FS sta invece per FileSystem, quella parte del sistema operativo che permette di compiere operazioni di scrittura e lettura su qualunque dispositivo di memorizzazione (un hard disk, una chiavetta usb ecc ecc) in maniera trasparente all'utente. I nostri dati infatti non sono organizzati in cartelle, ma piuttosto scritti a casaccio, e non sempre in maniera contigua (ad esempio quel film da 4Gb che avete illegalmente scaricato sarà scritto “dove c'è spazio”, e se necessario spezzato più volte: difficile infatti trovare tutto quello spazio libero contiguo sul vostro hard disk!). Compito del filesystem quindi è, mentre navighiamo tra le cartelle del nostro pc e apriamo un file, far ruotare il disco di modo che la testina legga esattamente tutte le parti di quel file. Le cartelle sono semplicemente dei file speciali che contengono la lista dei file presenti al loro interno.
E questo condensa il what; l'how  è molto complesso e ve lo lascio cercare su google, se siete interessati.
Trovo piuttosto affascinante che all'utente tutto ciò sia nascosto, non è fantastico?
Ma bando ai sentimentalismi, andiamo avanti!
Cerchiamo di capire perché asserisco che grazie a PiFS potremo scordarci dello spazio di archiviazione. Esiste una congettura, per ora mai provata ma nemmeno smentita, che afferma che Pi sia un numero normale; cosa sia un numero normale, lo lascio alla chiara definizione data da wikipedia:
a number of infinite length is called normal when all possible sequences of digits (of any given length) appear equally often.
Il fatto che sia normale, implica anche che sia una sequenza disgiuntiva, ossia una sequenza infinita di cifre all'interno della quale compare ogni altra possibile finita sequenza. Proviamo a ragionare un po'…se Pi contiene ogni finita sequenza di numeri...allora, scrivendolo ovviamente in binario, esso conterrà anche ogni dato di tutto ciò che è stato, è, e sarà!
Detto in un'altra maniera: tutti i possibili file, da questo che sto scrivendo ora, a quel file che avete cancellato per errore anni fa, alla vostra tesi di laurea salvata prontamente sul vostro PC, e pure quel progetto di software che avete in mente di scrivere, tutto ciò è già presente in Pi!!
E da qui l'idea di PiFS: celebriamo la grandezza di questo numero, prostriamoci d'innanzi alla sua infinita potenza, e creiamo un FS che sfrutti questa sua proprietà!
L'idea dello sviluppatore è stata quindi quella di creare un filesystem che semplicemente cerchi byte per byte di ciascun file (per questioni di performance non cerca la sequenza del file intero, ma lo spezza in “sotto-file” di un byte) dove inizia la sequenza di quel dato byte di quel dato file, e segni quest'indice come metadato sullo spazio di archiviazione (che comunque è necessario).
Beh...che dire? Geniale!!! Abbiamo sconfitto la fame nel mondo il problema dello spazio di archiviazione!

Ma c'è un enorme limite, anzi due: le performance sono scarsissime e soprattutto non ci è dato sapere quanto tempo ci vorrà a cercare la sequenza corrispondente (in scrittura), potrebbero volerci giorni anche sui pc più potenti. Vale lo stesso problema anche in lettura: pur avendo l'indice dal quale la sequenza corrispondente all'inizio dell' i-esimo byte del file, dobbiamo comunque scorrere Pi fino a quell'indice (e quindi il tempo di accesso può essere molto lungo); inoltre, se spezzare i file in sotto-file da 1 byte ci permette in scrittura di essere “più efficienti”, in lettura si è penalizzati dal fatto che si avranno molti indici da cercare in fila per leggere un file interamente.
L'altro problema, e qua arriva la trollata finale, è che i metadati riguardanti gli indici generati da PiFS, hanno in media dimensione maggiore rispetto ai file stessi!
Insomma, non solo le operazioni di lettura e scrittura sarebbero lentissime, in più alla fine sprecheremmo più spazio di quanto se ne utilizzi ora!
Ovviamente lo sviluppatore è consapevole di questa contraddizione, infatti il filesystem è nato come scherzo che, devo essere sincero, gli è proprio ben riuscito! Mi ha strappato più di qualche risata, oltre a lasciarmi a bocca aperta per la genialità ovviamente!
Lascio il link al github del genio: https://github.com/philipl/pifs.

Gloria, gloria al nostro Pi!
Al prossimo numero di questa nostra splendida rubr... no, non sbattetemi fuori! Nooooooooooooo!

lunedì 2 marzo 2015

GHOST: un fantasma nelle glibc

Torniamo a discutere di sicurezza informatica e torniamo a parlare di bug in librerie che non dovrebbero contenerne. Spirito polemico a parte, il 2015 si è aperto in bellezza con la vulnerabilità di sicurezza etichettata CVE-2015-0235 e nota al pubblico come GHOST.

Cos'ha di speciale questa vulnerabilità? In primo luogo riguarda l'equivalente GNU/Linux di uno dei componenti base di ogni sistema *NIX: la libreria standard del C. Per quanto bello ed interessante il pippone sulla storia di UNIX e del linguaggio C è già stato oggetto di numerosi articoli e discussioni: i più pigri possono risparmiarsi una ricerca su Google e leggere l'articolo di Wikipedia su UNIX.

Se non siete stati risucchiati dal gorgo degli articoli correlati e ce l'avete fatta a tornare tra noi ora sapete che, senza la libreria standard del C, scrivere un qualsiasi programma diventa un compito per guru del linguaggio assembly. Per non parlare della possibilità di avere codice (più o meno) portabile...

Faccio anche notare che il kernel Linux ha una sua versione ridotta della libreria standard del C (la klibc) data la necessità di massima indipendenza del kernel da librerie esterne (e anche qui ce ne sarebbe da discutere, ma mi sono imposto di ridurre le divagazioni al minimo).

Appurato che la libreria standard del C è importante per lo userland tanto quanto lo sono il kernel e le syscall che questo espone, vediamo di capire più in dettaglio cosa è andato storto stavolta e quali implicazioni il bug si porta dietro.

Il Diavolo nei dettagli

Se avete cliccato sul link in cima all'articolo sarete arrivati ad un file di testo scritto in informatichese stretto che spiega con squisito dettaglio quali sono le cause e quali gli effetti del bug, oltre a dare un piccolo programma in C che (una volta compilato) servirà a testare i propri computer.

Potrei cercare di tradurre alla meglio l'articolo in questione e dichiarare festa finita, ma chi mi legge abitualmente sa che non è mia consuetudine scrivere simili articoli.

Cominciamo quindi la nostra maratona di brutalità informatica per iniziati! :-D

La colpevole è una funzione interna delle glibc (ovvero una funzione che non è possibile chiamare dall'esterno ma che viene chiamata da altre funzioni delle glibc): __nss_hostname_digits_dots

Leggendo questa funzione, mi è subito sembrato chiaro che chi l'ha scritta non si fidasse del compilatore: invece di creare una struttura con 4 puntatori ed allocare un buffer per contenerne l'input, ha deciso di allocare un unico buffer e di calcolare a priori lo spazio per contenere 2 puntatori e il testo di input, dimenticando per strada un puntatore.

"Ma così si risparmia spazio!". Certo, se devi far girare il tuo codice su un microcontrollore con 16 kilobyte di RAM lo spazio è vitale. Ma qui si parla di PC che hanno memoria a strafottere e risparmiare lo spazio per un puntatore compromettendo la leggibilità del codice è una falsa economia.

Qual è il risultato di quel puntatore dimenticato? Che si può andare a scrivere roba oltre i limiti del buffer (buffer overflow) e ciò è MALE.

Per chi fosse curioso di scoprire quanto può essere dannoso un buffer overflow consiglio caldamente la lettura di Smashing The Stack For Fun and Profit storico articolo di Aleph One pubblicato su Phrack 49 nel lontano 1996. Per chi non avesse tempo/voglia di leggere l'articolo la versione TL:DR si riduce a "un valore sbagliato nel posto giusto e il mio computer è alla mercé di chi ha scritto quel valore in quel posto".

Vista la natura così pericolosa dei buffer overflow gli sviluppatori di compilatori, linguaggi di programmazione e sistemi operativi hanno passato gli ultimi 20 anni a studiare tecniche atte a mitigare le conseguenze degli errori di programmazione. La letteratura in merito è ampia e variegata e le tecniche più quotate al momento in cui scrivo sono tre:

  1. Canarini nello stack e/o bound checks a runtime.
  2. NX bit (reale o emulato).
  3. Address Space Layout Randomization (ASLR).

Spero che mi perdonerete la brevità ma per capire come hanno fatto ad aggirare questi meccanismi occorre prima capire come questi operano.

Canarini e bound checks

I canarini devono il loro nome a quelli utilizzati nelle miniere per rilevare le fughe di gas tossici nelle gallerie: se il canarino sveniva o moriva l'aria era tossica anche per i minatori.

I canarini nello stack sono porzioni di dati randomizzati inserite dopo il buffer. La procedura di chiamata delle funzioni si complica un po' perché occorre verificare il canarino in fase di uscita dalla funzione se il canarino è stato modificato in qualche modo la routine di uscita dalla funzione interrompe l'esecuzione e uccide il programma. Questo comportamento è giustificato dall'idea che è preferibile una caduta di servizio che una compromissione del computer.

Il valore del canarino di solito è inserito in una posizione particolare di memoria circondata da aree di memoria non valida che causa un segmentation fault nel caso in cui un attaccante cerchi di leggere sequenzialmente la memoria per cercare il canarino.

I canarini richiedono un cambiamento del compilatore e spezzano la compatibilità binaria perché modificano l'ABI (Application Binary Interface) dei programmi e delle librerie.

Un altro metodo per aumentare la sicurezza consiste nel bound checking (controllo dei limiti). Quando la dimensione del buffer è nota durante la compilazione (cioè scritta direttamente nel codice o definita da una costante nota al compilatore) il controllo è facile da implementare automaticamente. Quando la dimensione del buffer non può essere nota a priori il compilatore deve aggiungere del codice che tenga traccia delle dimensioni di tutti i buffer allocati dinamicamente, così da consentire il controllo dei limiti.

La suite di compilatori GCC supporta i canarini, ma non li abilita di default. Per abilitarli occorre aggiungere il flag -fstack-protector (o -fstack-protector-all per proteggere tutte le chiamate a funzione).

Per maggiori informazioni potete consultare la pagina di Wikipedia sulla Buffer Overflow Protection.

NX bit

Il bit NX è una caratteristica di alcune architetture (AMD64/x86_64, Alpha, UltraSPARC) che consente di segnare una pagina di memoria come non eseguibile.

Se la CPU prova ad eseguire istruzioni presenti in una pagina marcata dal bit NX un interrupt hardware viene eseguito e il sistema operativo può interrompere il processo in corso o (se il problema avviene in kernel space) lanciare un kernel panic.

A differenza dei canarini, la protezione data dal bit NX non incide sulle performance dei programmi ma richiede, oltre al supporto da parte dell'hardware, che il compilatore indichi nei file di output quali sono le aree di memoria eseguibili e le consolidi in modo da facilitare il lavoro del loader (la porzione del sistema operativo che si occupa di caricare in memoria i programmi). Inoltre il compilatore non deve emettere codice che dipenda da uno stack o da uno heap eseguibile altrimenti quel codice farà scattare la protezione.

Nelle architetture in cui il bit NX non è presente (le più rilevanti delle quali sono x86 e ARM) si possono utilizzare dei meccanismi alternativi come il SEGMEXEC di PaX che sfrutta il registro Code Segment e gli interrupt di page fault per emulare il bit NX al prezzo di dimezzare la memoria allocabile disponibile per i programmi.

Ovviamente questa emulazione richiede una modifica rispetto al kernel standard ("vanilla"). Solo alcune distro hanno deciso di applicare queste patch, a causa delle ripercussioni sul resto del sistema (minori performance e, in alcuni casi, programmi che smettono di funzionare). Il kernel Linux vanilla per x86_64 (e per alcuni processori x86 che lo supportano) sfrutta il bit NX già dalla versione 2.6.8.

Address Space Layout Randomization (ASLR)

Veniamo ora al più complicato (e secondo alcuni più efficace) mezzo di protezione della memoria: la randomizzazione dello schema di disposizione di dati e istruzioni nella memoria.

Per prima cosa mi scuso per l'orribile termine "randomizzazione" ma non ho trovato un equivalente meno brutto ma altrettanto calzante.

Per seconda cosa sappiate che se non avete ancora letto "Smashing The Stack For Fun and Profit" siete delle brutte persone che non capiranno molto di quello che seguirà.

L'ASLR consiste in una serie di accorgimenti atti a rendere la vita difficile a chi cerca di sfruttare i buffer overflow. Ma quali buffer overflow?

La tecnica canonica è lo stack overflow: una scrittura di dati che eccedono in quantità la dimensione di un buffer allocato nello stack e che vanno a sovrascrivere l'indirizzo di ritorno della funzione con un valore arbitrario. Gli effetti variano dal crash dell'applicazione fino all'esecuzione di una shell con i privilegi dell'utente che ha lanciato l'applicazione.

Contro gli stack overflow si può adottare una tecnica che consiste nel creare uno spazio vuoto (gap) tra l'indirizzo di ritorno e i parametri della funzione. Ovviamente se questo spazio vuoto fosse fisso sarebbe facile compensare per cui il gap dev'essere casuale. Uniamo questo trucco ad un canarino piazzato prima dell'indirizzo di ritorno e abbiamo già cominciato a creare una bella gatta da pelare per chi scrive malware.

La randomizzazione però non si ferma qui: c'è l'intera categoria degli heap overflow che, pur essendo più complessi da sfruttare degli stack overflow, rappresentano una bella fetta delle minacce a cui si va incontro.

Come facciamo a proteggerci dagli heap overflow? Randomizzando la memoria ulteriormente. Per capire come dobbiamo prima capire cos'è uno heap overflow e come si può sfruttare.

Heap Overflows

Se avete tempo trovate un'eccellente spiegazione di come sfruttare un overflow dello heap su Hackers Hut e aggiungo anche l'ottimo (seppur datato) articolo di Michel "MaXX" Kaempf su Phrack 57 che spiega in dettaglio il funzionamento dell'allocazione della memoria nelle glibc. La lettura è decisamente impegnativa e richiede alcune conoscenze approfondite del funzionamento del kernel Linux, del linguaggio C e un'infarinatura su come funziona un linker dinamico per eseguibili in formato ELF.

La versione TL:DR è la seguente: se siete sopravvissuti ad un corso di C saprete già che malloc riserva un'area di memoria nello heap mentre free la libera. Quello che fa la versione di malloc implementata nelle glibc è chiedere al kernel di riservare un po' di memoria e, se tutto va bene, usa parte di quella memoria per scrivere la seguente struttura dati:

struct malloc_chunk {

  INTERNAL_SIZE_T      prev_size;  /*Size of previous chunk (if free).*/
  INTERNAL_SIZE_T      size;       /*Size in bytes, including overhead.*/

  struct malloc_chunk* fd;         /*double links -- used only if free.*/
  struct malloc_chunk* bk;

  /*Only used for large blocks: pointer to next larger size.*/
  struct malloc_chunk* fd_nextsize; /*double links -- used only if free.*/
  struct malloc_chunk* bk_nextsize;
};

Sempre se avete completato il corso di C, saprete che quella struct, al netto degli ultimi due campi (che ignoreremo), è una lista concatenata doppia. Per chi non avesse seguito con attenzione: la lista concatenata doppia è una struttura dati dinamica i cui elementi puntano tutti all'elemento precedente e all'elemento successivo nella lista. Quella struttura dati è fondamentale per il funzionamento di free e per il riciclo della memoria allocata da parte di malloc perché consente di sapere dove si trovano e quanto grandi sono le aree già riservate ma non più utilizzate della memoria.

Tutto molto bello, ma che implicazioni ha? Quando si fa una chiamata a free questa funzione prende il puntatore, ricava da esso la posizione della malloc_chunk e la utilizza per consolidare la memoria liberata in un chunk più grande usando il puntatore bk per trovare il chunk precedente e quindi riaggiustando i puntatori fd e bk.

Ma come fa a sapere se il chunk precedente è libero o è in uso? Con un astuto trucchetto che sfrutta l'allineamento dei dati nella memoria. Per ragioni prestazionali è sempre bene che la memoria sia allineata secondo la dimensione della parola utilizzata internamente dall'harware: 32 bit (o 4 byte) per le architetture a 32 bit e 64 bit (8 byte) per quelle a 64 bit. Nulla vieta di allineare la memoria secondo i multipli delle parole, per cui un allineamento a 8 byte va bene sia per x86 (32 bit) che per AMD64 (64 bit).

Siccome il campo size indica la dimensione in bytes se imponiamo l'allineamento a 8 byte i due bit meno significativi saranno sempre pari a 0 (8 si scrive 100 in binario). Quei due bit sono utilizzati come flag per segnalare se il chunk di memoria è stato ottenuto con mmap (e quindi richiede di essere liberato con munmap) e se il precedente chunk è in uso o meno.

Se il precedente chunk non è in uso allora posso utilizzare il campo prev_size per raggiungere la struttura malloc_chunk ed utilizzare i dati ivi contenuti per aggiornare i campi della mia malloc_chunk e consolidare la memoria liberata.

Se il chunk precendente è in uso posso usare il campo size per saltare al campo successivo e quindi al successivo ancora e controllare se il mio vicino è in uso e, se non lo è, consolidare lo spazio libero in avanti.

Confusi? Forse un schema aiuterà la comprensione:

Nel grafico i nodi A B e C rappresentano dei chunk di memoria contigui. Mentre nodi A e C sono liberi il nodo B è un chuck in uso che è stato selezionato per essere liberato con free.

B sa che A non è in uso grazie al bit meno significativo del suo campo size e sa dove si trova grazie al suo campo prev_size. Va a guardare A e scopre la posizione di C grazie al puntatore fd di A che indica la posizione di C.

free a questo punto può consolidare lo spazio libero fondendo insieme i chunk A, B e C in un unico chunk la cui dimensione è pari alla somma delle dimensioni dei tre chunk. Nel fare questo free deve aggiornare il valore del puntatore fd di A facendolo puntare a fd di C oltre ovviamente ad aggiornare il campo size di A.

Tutto molto bello, ma in concreto cosa significa? Significa che, se non vengono fatti controlli adeguati, possiamo utilizzare i campi della malloc_chunk per scrivere valori arbitrari in indirizzi di memoria scelti da noi. Se avete letto "Smashing The Stack For Fun and Profit" (non smetterò mai di ripeterlo per cui leggetelo!) sapete già cosa significa, se non l'avete fatto: "Sciagura a voi!".

Address Space Layout Randomization (secondo giro)

Ovvero: "Come faccio ad evitare che un errore di programmazione possa causare la sistematica compromissione delle macchine che si suppone siano sotto il mio controllo?".

Prima di tutto: se siete arrivati fin qui vi faccio i miei complimenti, condensare 20 anni di ricerca sulla sicurezza informatica in queste poche righe è stato un compito arduo ed il testo che ne risulta è inevitabilmente frammentario e pesante da digerire.

Un ultimo sforzo e poi vi giuro che vi spiegherò come si fa a capire in cosa consiste GHOST.

Se avete seguito lo sproloquio sugli heap overflow (o se l'avete saltato perché sapevate già tutto) ora sapete che un mancato controllo su quanto viene scritto in memoria può causare grossi guai. I gap di dimensione casuale sono poco utili contro gli heap overflow e quindi che fare?

Una delle regole della strategia militare recita: "Se non puoi affrontarli direttamente aggirali!". Invece di cercare di prevenire futilmente una sovrascrittura della struttura malloc_chunk facciamo sì che l'attaccante non possa sapere a priori dove si trovano le porzioni di codice che vuole richiamare spargendo casualmente le parti eseguibili di programmi e librerie nello spazio di memoria associato al processo (pur tenendo vicini i moduli funzionali che le compongono).

Da una trentina d'anni i programmi sono compilati in modo da collegarsi a runtime alle librerie di cui hanno bisogno, tutto quello che dobbiamo fare è rendere il processo di linking non deterministico inserendo le librerie e il codice del programma in locazioni di memoria casuali ad ogni avvio del programma. Non solo! Possiamo addirittura fare in modo che ogni nuovo processo creato abbia una disposizione differente rispetto al processo padre che l'ha generato (per maggiori informazioni leggete la pagina di Wikipedia dedicata alla chiamata di sistema fork).

Così facendo complichiamo parecchio la vita a chi scrive malware: ora devono attivamente andare a cercare dove si trova il codice che intendevano eseguire.

Sfruttare GHOST e exim per bucare una macchina

Finalmente dopo tanto sproloquiare arriviamo al succo del discorso. Come hanno fatto i ricercatori di Qualsys a superare lo stack protector, il bit NX e l'ASLR?

Primo punto: la vulnerabilità si attiva solo in gethostbyname e solo per indirizzi IPv4.

Secondo punto: perché si possa sfruttare questa vulnerabilità occorre che il programma chiami gethostbyname direttamente senza prima chiamare inet_aton. Molti programmi provano prima a vedere se la stringa contiene un indirizzo IP e inet_aton fa questo controllo e ritorna automaticamente un int che contiene il valore dell'indirizzo IP come sarà utilizzato internamente dallo stack TCP/IP. gethostbyname chiama internamente inet_aton per verificare che non si tratti di un nome host e quindi la stringa che passiamo DEVE passare il controllo di inet_aton.

Per passare questo controllo occorre che si verifichino le seguenti condizioni:

  • La stringa deve contenere solamente cifre decimali o punti.
  • La stringa deve cominciare con una cifra.
  • L'ultimo carattere non può essere un punto.

Ovviamente la stringa deve anche essere abbastanza lunga da causare l'overflow del buffer.

Going de[er]per

Punti bonus a chi ha riconosciuto il gioco di parole realizzato con una regex (sì, non ho una vita sociale degna di nota).

Abbiamo i prerequisiti per far scattare la nostra trappola, ma come dobbiamo metterli assieme per avere successo? Analizziamo ulteriormente il codice vulnerabile alla riga 157:

resbuf->h_name = strcpy (hostname, name);

La cara buona vecchia strcpy, anche nota come "fottitene dei limiti e copia tutto quello che puoi"! In questo caso copia in hostname tutto quello che trova tra name e il primo carattere NUL (Valore ASCII: 0) che incontra. E siccome name lo forniamo noi... :-D

Se ancora ricordate quanto scritto all'inizio sapete che lo spazio allocato è sufficiente per 2 puntatori e per la stringa contenuta in name ma il codice di __nss_hostname_digits_dots (funzione di help chiamata da gethostbyname e sede della vulnerabilità) ha bisogno di 3 puntatori e dello spazio per la stringa name. Quindi possiamo sovrascrivere il numero di bit corrispondenti ad un puntatore: 32 se siamo su x86 e 64 se siamo su AMD64.

La domanda successiva è: quanto danno possiamo fare con i bit a nostra disposizione?

Riscrivere la grandezza di un chunk con un valore maggiore e, quando il programma richiederà altra memoria, potremo riscrivere altre parti della memoria che non avremmo dovuto riscrivere.

Exim o non exim?

Arrivati a questo punto dell'articolo vi aspettate una lunga disquisizione tecnica su come si faccia a bypassare le difese. Non ci sarà. Primo perché questo articolo sta già raggiungendo una considerevole lunghezza, secondo perché i dettagli sono spiegati nell'advisory e presto l'exploit sarà aggiunto a Metasploit pronto per l'uso degli script kiddies, terzo perché a questo punto all'autore interessa di più portare il lettore ad alcune conclusioni.

Sopprimete la vostra delusione e cercate di seguirmi per questi ultimi paragrafi.

L'exploit di exim tramite GHOST è particolarmente brutto perché exim stesso adotta due pratiche che si stanno rivelando sempre più pericolose:

  1. exim ha un allocatore interno della memoria. Se questo non vi fa suonare un campanello di allarme posso permettermi di ricordare una certa libreria dedicata all'implementazione delle specifiche SSL e TLS e un suo certo bug.
  2. exim ha la possibilità di lanciare comandi arbitrari tramite una modifica del file di configurazione, file che viene copiato nello heap.

Ovviamente non possiamo dare tutta la colpa agli sviluppatori di exim: quando hanno operato le loro scelte gestire in proprio l'allocazione della memoria era una buona idea per migliorare le prestazioni, mentre la possibilità di configurare il server di posta in modo che possa lanciare comandi arbitrari in base a certe condizioni aumenta di molto la flessibilità del sistema. Inoltre mappare il file di configurazione in memoria consente un accesso più veloce al medesimo: tutte ragioni legittime insomma ma, come già scritto, il Diavolo è nei dettagli.

Spero che, dopo aver letto questo articolo, siate giunti alla mie medesime conclusioni:

  • Programmare in C vuol dire essere coscienti di quello che si fa e curare maniacalmente il proprio lavoro.
  • La memoria che contiene i dati in lettura/scrittura dovrebbe essere marcata come non eseguibile e la memoria eseguibile non dovrebbe essere scrivibile.
  • Si può scrivere codice intelligente (semplice da capire e ben documentato) oppure codice furbo (difficile da capire perché pieno di trucchi per migliorare le prestazioni).

Il problema nelle glibc è stato risolto, ma ci sono voluti anni prima che fosse scoperto e corretto. Non stiamo parlando di un'oscura libreria piena di codice crittografico che richiede un Dottorato in Matematica con specializzazione in Teoria dei Numeri, stiamo parlando di una libreria fondamentale che ha decine di sviluppatori e centinaia di occhi che la scrutano.

Simili problemi non dovrebbero esserci perché minano alla base la fiducia di chi basa sistemi critici su quel codice.

Se è vero che "Dato un numero sufficiente di occhi, tutti i bug vengono a galla («given enough eyeballs, all bugs are shallow»)" (legge di Linus citata da Eric Steven Raymond nel saggio La cattedrale e il bazaar - fonte Wikipedia) è anche vero che le glibc non godono di molti occhi rispetto ad altri progetti (come il kernel Linux).

Purtroppo GNU/Linux non è più un giocattolo per appassionati ed entusiasti ma uno strumento da cui dipendono numerose infrastrutture di rete in tutto il Mondo.