Skip to content

programmazione

L'IA come collaboratore, non come scorciatoia

Commento del 2025 su un blog. Lo ripubblico perché la distinzione tra "AI come collaboratore" e "AI come copia-incolla" è ancora la più importante da fare.

Tempo fa avevo scritto una libreria che estende un progetto open source, si tratta di un prodotto di nicchia che ha pochi download, è abbastanza semplice ma mi serviva e non esisteva, e svilupparlo è stato molto interessante. La qualità del codice che avevo scritto era discreta, ed il prodotto funzionava abbastanza bene anche se con diverse limitazioni di cui ero ben conscio.

Di recente ho provato con AI a vedere dove poter intervenire: ho chiesto di analizzare la qualità del codice, se ci fossero incoerenze, codice duplicato etc. ed ho avuto diversi suggerimenti, alcuni molto utili, altri che invece non ho ritenuto interessanti. Mi è stato segnalato un bug che, in realtà, non esisteva. Ma effettivamente un caso particolare era gestito, a livello di codice, in maniera non ottimale. Ho sistemato il sistema di inizializzazione, l'ho reso configurabile, ho scritto la documentazione, ho scritto una suite di test per verificare il corretto funzionamento della procedura e per essere certo che, quando correggo il codice o aggiungo funzionalità, il prodotto continua a funzionare.

Tutte cose che avrei saputo fare anche da solo, ma che portavano via tempo e concentrazione. Sostanzialmente ho usato l'AI come motivatore, o più che altro come collaboratore, non ho fatto nessun copia ed incolla a caso.

Da un lato credo che uno sviluppatore discreto, utilizzando opportunamente l'AI, possa arrivare a risultati eccellenti. D'altra parte anche uno sviluppatore scarso, utilizzando impropriamente l'AI, può ottenere risultati che sembrano buoni - ma non necessariamente lo sono. Diventa quindi più difficile discriminare chi è bravo da chi non lo è.

Joel Spolsky sosteneva che per assumere uno sviluppatore devi vedere come scrive codice, non come parla. È una delle 12 domande del suo Joel Test: "I nuovi candidati scrivono codice durante il colloquio?".

I "take home test" sono un'evoluzione di questo principio. Per dire, non so in Italia ma all'estero le ditte che dovevano assumere sviluppatori facevano una selezione fin da subito tramite "take home test", dei piccoli progetti da sviluppare in qualche mezza giornata ma che davano una idea della qualità del codice che si sapeva produrre. Oggi come si fa? Con un po' di AI, ed evitando nel contempo di fare troppi copia+incolla indiscriminati, è molto facile far sembrare di essere molto bravi.

Probabilmente l'AI spazzerà via diverse professioni, sono cose che sono sempre successe con le varie innovazioni tecnologiche ma forse siamo di fronte ad eventi più estremi.

Grafici? Traduttori? Interpreti? Speaker? E non sono tranquillo nemmeno per il futuro degli sviluppatori, sì, la qualità dell'AI non è sempre ottimale, ci sono molti errori, aspetti da migliorare, e molte attività per le quali l'intervento umano è sempre indispensabile, ma sarà sempre così? D'altra parte anche molti sviluppatori pagati come tali non è che siano sempre all'altezza del loro ruolo! Forse ne serviranno sempre meno? Pochi ma di livello alto? Staremo a vedere!

"Dato un sufficiente numero di occhi, i bug tendono a zero" non funziona

Commento su un blog del 2022, in risposta a una discussione sulla Legge di Linus e sul ruolo del code review nell'open source.

L'idea che "dato un sufficiente numero di occhi, i bug tendono a zero" non funziona.

Trovare bug è un lavoro a tempo pieno, e spesso complesso. Non è che ti metti a leggere il codice sorgente sotto l'ombrellone e sei in grado di scoprire e risolvere i bug. Qualcuno forse, ma solo se il bug è banale o il codice è stato scritto da un principiante.

Il problema della Legge di Linus

La frase di Eric Raymond — "given enough eyeballs, all bugs are shallow" — è una semplificazione affascinante ma fuorviante. Funziona per i bug evidenti, quelli che si vedono leggendo il codice con attenzione. Non funziona per i bug che contano davvero.

I bug seri sono spesso:

  • di concorrenza, si manifestano solo in determinate condizioni di timing, e non si vedono leggendo il codice;
  • di stato, dipendono da una sequenza specifica di operazioni, che il lettore non può ricostruire senza eseguire il programma;
  • di casi limite, coinvolgono input che il lettore non ha motivo di considerare, perché non sono nel percorso principale;
  • di integrazione, emergono solo quando il codice interagisce con altri componenti, versioni diverse, ambienti diversi.

Questi bug non si trovano "guardando". Si trovano testando, strumentando, eseguendo. E il code review, da solo, non basta.

Il caso del codice offuscato

Se poi qualcuno vuole intenzionalmente offuscare del codice e nascondere delle funzionalità, il discorso cambia completamente.

Il code review diventa inefficace: il codice è scritto apposta per sembrare innocuo. Servono analisi dinamiche, reverse engineering, strumenti di tracing. E anche con tutto questo, dipende caso per caso: potrebbe essere molto difficile da scoprire.

Questo vale sia per il codice open source che per quello proprietario. Anzi, nel caso del codice proprietario la situazione è peggiore, perché non hai nemmeno la possibilità di leggere il sorgente.

Conclusione

La Legge di Linus ha un fondo di verità — più occhi possono trovare più bug — ma è una verità statistica, non una garanzia. E vale solo per i bug che si possono trovare leggendo. Gli altri richiedono lavoro, strumenti, e tempo.

Trovare bug è un mestiere. Non è una passeggiata sotto l'ombrellone.

Write once, read never

Commenti del 6 dicembre 2019 su un blog, in una discussione sui linguaggi di programmazione. Li ho riorganizzati in un unico testo.

Vero, non è il linguaggio quanto chi ci sviluppa.

Ho visto capolavori di stile scritti in Perl (cui è stato assegnato l'infame motto "write once, read never") ed ho visto geroglifici scritti in linguaggi che dovrebbero perlomeno facilitare la scrittura, es. Python.

Però non sono del tutto d'accordo che un linguaggio complesso sia vantaggioso. Da informatico mi piace risolvere problemi complessi in modi semplici. Risolvo problemi complicati perché odio le complicazioni.

Quindi se il linguaggio è complicato, rischi di attrarre gente che le complicazioni le amano! Lo dico perché nel mio ambito di lavoro vedo spesso dei framework complicati, ma lo sono perché sono stati progettati male e senza buon gusto estetico, non perché devono per forza essere così.

Questo il mio punto di vista ;)


C'è poi un problema più generale, che non dipende dal linguaggio: il copia-incolla.

Seguo (e quando ho tempo, partecipo attivamente) la community Stack Overflow, dove gli sviluppatori fanno domande ed altri sviluppatori rispondono. Ci sono dei veri e propri guru che partecipano, ma vi partecipano utenti di tutti i livelli (anche scarsi).

Copiare ed incollare una soluzione che poi finisce in produzione avviene regolarmente, il fatto è che non tutte le soluzioni sono "giuste": magari risolvono immediatamente un particolare problema ma ne introducono altri, sono obsolete, od hanno problemi gravi, anche di sicurezza. E non tutti vogliono approfondire.

Se ne parla ad esempio in questo articolo del blog di Stack Overflow (del novembre 2019):

https://stackoverflow.blog/2019/11/26/copying-code-from-stack-overflow-you-might-be-spreading-security-vulnerabilities/

ci si riferisce principalmente al C/C++ ma è utile per qualsiasi sviluppatore.

Come funziona un buffer overflow

Commenti del 25 ottobre 2019 su un blog, in una discussione sulla sicurezza dei file audio e sui malware. Li ho riorganizzati in un unico testo.

Il buffer overflow è stato uno dei vettori di attacco più comuni, e non è che sia del tutto sparito. I vari sistemi operativi recenti offrono protezioni aggiuntive che, in combinazione con funzionalità hardware, rendono sempre più difficile la cosa. Ma vale la pena capire come funziona.

Un errore classico

Storicamente funzionava così:

  • un programmatore dichiarava un buffer di una certa dimensione, es. 80 caratteri
  • un attaccante malevolo passava in input al programma un parametro troppo lungo, es. 800 caratteri
  • il programma, causa bug, non applicava tutti i controlli necessari e copiava l'input da 800 caratteri sul buffer da 80
  • i caratteri eccedenti andavano a sovrascrivere aree di memoria non previste, che con un po' di fortuna potevano essere associate ad esempio ad altre variabili - ecco che con questo trucco potremmo andare a cambiare dei valori cui non dovremmo avere accesso, e quindi cambiare le logiche di esecuzione del programma
  • meglio ancora, le aree di memoria non previste potevano essere associate allo stack. Quando richiamo una funzione (call) viene inserita nello stack la posizione dell'istruzione che segue la call, in modo da ritornare lì (ret) una volta terminata la funzione. Ecco che modificando i valori inseriti nello stack, tra cui l'elenco delle istruzioni cui riprendere l'esecuzione terminata una funzione, possiamo dire ad un programma di eseguire le istruzioni di una qualsiasi area di memoria, magari proprio all'interno di questi 800 caratteri.

Ecco, quanto descritto è un metodo "classico" causato da un errore "classico": buffer troppo corto, mancanza di adeguati controlli.

Perché funziona

Devi ragionare in termini di linguaggio macchina, e devi pensare a come il tuo codice C o C++ è compilato e a come viene poi eseguito. L'attacco che ho descritto è - era? - molto comune, ed è stato usato con successo diverse volte: la maggior parte dei worm che si diffondevano in rete sfruttavano proprio questa tecnica.

L'attacco consiste nell'inviare una stringa molto più lunga di quanto il programma ha previsto, che contiene:

  1. il codice macchina malevolo da eseguire
  2. l'indirizzo del codice macchina al punto 1

In particolare, l'indirizzo al punto 2 oltre a dover essere calcolato precisamente, è indispensabile che sia perfettamente allineato, in modo che si sovrapponga alla struttura dati che contiene le "informazioni di controllo dell'esecuzione" del programma.

Il buffer può trovarsi dove si vuole, ma la sua posizione relativa potrebbe essere sempre la stessa, ecco perché il sistema funziona. Vari sistemi, come l'Address Space Layout Randomization, cercano appunto di rendere le posizioni relative non prevedibili, per cui questo tipo di attacco è molto più difficile (ma non impossibile).

Un crash non è la fine

In sostanza, è vero che la maggior parte delle volte un bug di questo tipo produce un errore di segmentation fault, ma inviando dei dati malvagi, preparati con grande accuratezza, è possibile far eseguire al programma qualche cosa di non previsto.

Non sempre questi bug sono sfruttabili (exploitabili): si potrebbe riuscire a mandare in crash il programma ma potrebbe non essere possibile controllarne l'esecuzione. Comunque un bel crash promette sempre bene.

Un errore di segmentation fault è un punto di partenza, da solo serve poco se non per mettere in piedi attacchi DoS (denial of service), ma significa che il programma ha qualche bug che potrebbe essere sfruttato per un attacco.

Le protezioni moderne

Questo metodo è sempre più difficile da applicare:

  • i compilatori potrebbero segnalare dei warning
  • gli strumenti di analisi del codice dovrebbero essere in grado di segnalare probabili bug di questo tipo
  • i sistemi operativi nonché i processori offrono protezioni aggiuntive, come l'ASLR, il DEP, lo stack canary

Ci sono comunque anche diverse strategie di attacco.

Nota sui file audio e sulle immagini

Un'ultima precisazione: la frase "non è possibile infettarsi semplicemente scaricando e ascoltando un file audio WAV" andrebbe esplicitata meglio.

Nemmeno con i file JPG, HTML, o Flash sarebbe teoricamente stato possibile infettarsi, nemmeno con le e-mail (sono solo dei banali testi!) o con le chiavette USB, eppure causa programmi vulnerabili è successo più di qualche volta.