Domanda:
Perché i programmi scritti in C e C ++ sono così spesso vulnerabili agli attacchi di overflow?
Nzall
2016-02-23 20:37:56 UTC
view on stackexchange narkive permalink

Quando guardo gli exploit degli ultimi anni relativi alle implementazioni, vedo che molti di essi provengono da C o C ++ e molti di loro sono attacchi di overflow.

  • Heartbleed era un buffer overflow in OpenSSL;
  • Recentemente, è stato scoperto un bug in glibc che consentiva il buffer overflow durante la risoluzione DNS;

sono solo quelli che posso pensare off in questo momento, ma dubito che questi siano gli unici che A) sono per software scritto in C o C ++ e B) sono basati su un buffer overflow.

Soprattutto riguardo al bug glibc, ho letto un commento che afferma che se ciò fosse accaduto in JavaScript invece che in C, non ci sarebbe stato un problema. Anche se il codice fosse stato compilato in Javascript, non sarebbe stato un problema.

Perché C e C ++ sono così vulnerabili agli attacchi di overflow?

Con un grande potere viene una grande responsabilità
[Questa risposta] (http://security.stackexchange.com/questions/95245/security-implications-of-neglecting-the-extra-byte-for-null-termination-in-cc/95248#95248) e [questo answer] (http://security.stackexchange.com/questions/82750/why-are-buffer-overflows-executed-in-the-direction-they-are/82846#82846) potrebbero essere letture interessanti. Fondamentalmente si riduce al design del linguaggio e al livello in cui è stato implementato.
@RoraΖ ci sono strumenti per compilare C in javascript, però, come emscripten. http://dankaminsky.com/2016/02/20/skeleton/, vicino al fondo è ciò a cui mi riferisco.
La tua domanda è un po 'come "Perché solo i computer Windows ottengono virus Windows?". Perché i virus Windows sono possibili solo su computer Windows. C e C ++ ottengono vulnerabilità di overflow del buffer dalla loro capacità di eseguire operazioni aritmetiche sui puntatori non controllate. La maggior parte delle altre lingue non ha questa capacità e quindi non può avere buffer overflow. La tua domanda inoltre non considera la popolarità di queste lingue. (Forse altre lingue sono PIÙ problematiche, ma non vengono utilizzate così tanto, quindi hanno meno vulnerabilità totali).
I commenti non sono per discussioni estese; questa conversazione è stata [spostata in chat] (http://chat.stackexchange.com/rooms/36311/discussion-on-question-by-nate-kerkhofs-why-are-programs-written-in-c-and- c-così).
In C ++ uno dei motivi per l'overflow del buffer è di fallire nel moderno C ++ e ignorare concetti più sicuri come STL. Se usi C ++ come C otterrai ciò che meriti.
Prima c'era un commento brillante che sembra essere stato cancellato: "È perché un bisturi taglia più di una forbice di sicurezza"
È inoltre più probabile che C / C ++ venga utilizzato per il software esposto a rischi e attacchi maggiori.
Heartbleed è stato per me il risultato di due cattive pratiche di programmazione: 1. Uso dell'istruzione goto e 2. mancanza di standard di programmazione come "Proibisci l'uso dell'istruzione" if "senza parentesi graffe".Con questo, non sarebbe successo.Dopo la mancanza di unit test, probabilmente è anche un'altra ragione ... Sono d'accordo che C e C ++ sono più inclini a questo attacco in quanto sono linguaggi di livello piuttosto basso ed è spesso responsabilità dello sviluppatore prevenire un cattivo utilizzo.
Otto risposte:
Thomas Pornin
2016-02-23 20:48:25 UTC
view on stackexchange narkive permalink

C e C ++, contrariamente alla maggior parte degli altri linguaggi, tradizionalmente non controllano gli overflow. Se il codice sorgente dice di mettere 120 byte in un buffer di 85 byte, la CPU lo farà felicemente. Ciò è correlato al fatto che mentre C e C ++ hanno una nozione di array , questa nozione è solo in fase di compilazione. In fase di esecuzione, ci sono solo puntatori, quindi non esiste un metodo runtime per verificare l'accesso a un array per quanto riguarda la lunghezza concettuale di tale array.

Al contrario, la maggior parte degli altri linguaggi ha una nozione di array che sopravvive a runtime, in modo che tutti gli accessi agli array possano essere sistematicamente controllati dal sistema runtime. Ciò non elimina gli overflow: se il codice sorgente richiede qualcosa di insensato come scrivere 120 byte in un array di lunghezza 85, non ha ancora senso. Tuttavia, questo attiva automaticamente una condizione di errore interno (spesso una "eccezione", ad esempio un ArrayIndexOutOfBoundException in Java) che interrompe la normale esecuzione e non lascia che il codice proceda. Questo interrompe l'esecuzione e spesso implica la cessazione dell'elaborazione completa (il thread muore), ma normalmente impedisce lo sfruttamento oltre un semplice denial-of-service.

Fondamentalmente, gli exploit del buffer overflow richiedono che il codice l'overflow (leggere o scrivere oltre i limiti del buffer a cui si accede) e per continuare a fare cose oltre tale overflow. La maggior parte dei linguaggi moderni, contrariamente a C e C ++ (e pochi altri come Forth o Assembly), non consente che l'overflow si verifichi davvero e invece spara all'autore del reato. Da un punto di vista della sicurezza questo è molto meglio.

* "Da un punto di vista della sicurezza questo è molto meglio." * Sebbene questo sia certamente vero, rende anche alcuni tipi di programmazione, in particolare la programmazione del sistema operativo, significativamente più difficile. Ricorda che l'eredità di C risale all'essere un linguaggio di programmazione progettato per implementare Unix in modo portabile; per una buona ragione, C è a volte indicato come ** assemblatore portatile **.
I commenti non sono per discussioni estese; questa conversazione è stata [spostata in chat] (http://chat.stackexchange.com/rooms/36312/discussion-on-answer-by-thomas-pornin-why-are-programs-written-in-c-and- c-so-f).
@MichaelKjörling Vero, ma poi di nuovo, ci sono molti sistemi operativi Microsoft Research che si basano su codice interamente gestito (e in questo modo completamente sicuro), inclusa la verifica statica. Microsoft spende molti soldi per risolvere sistematicamente questo problema, piuttosto che aspettare che le persone si risolvano. Come sempre: D Le prestazioni sono sempre complicate, ma ancora una volta, hai molte opportunità di ottimizzazione con codice gestito e riflettibile rispetto a quanto non avresti mai ottenuto con l'assemblaggio: per molti software server, sono persino riusciti a ottenere un notevole aumento delle prestazioni grazie a quella.
@Luaan Dubito che la differenza principale sia stata il passaggio da "assembly" a "codice gestito". Semmai, sembra più probabile che sia stato dovuto al passaggio da codice non JITed a JITed. Con le ottimizzazioni del tempo di compilazione, devi scegliere una linea di base più bassa che sei disposto a supportare. Con il codice JITed, puoi ottimizzare per la macchina specifica su cui stai eseguendo. In linea di principio, probabilmente potresti scrivere codice JIT in C; Non sono sicuro che qualcuno ci abbia provato, però ...
@MichaelKjörling In realtà, molti di loro non sono compilati JIT. Tuttavia, sono ancora compilati per la configurazione hardware specifica. Ma per ottenere un risultato efficace nella compilazione JITted, sono necessarie molte informazioni extra e molti limiti: il C è semplicemente troppo libero per consentire molte ottimizzazioni significative anche dal codice sorgente, molto meno il codice compilato. Anche qualcosa di semplice come quei controlli dei limiti: non c'è modo per un compilatore C di eseguire il controllo dei limiti per te, dal momento che stai solo manipolando alcuni puntatori casuali, per quanto ne sa il compilatore. Lo stesso vale per ometterli in modo sicuro.
DevSolar
2016-02-23 22:12:13 UTC
view on stackexchange narkive permalink

Nota che è coinvolto un certo numero di ragionamenti circolari: i problemi di sicurezza sono spesso collegati a C e C ++. Ma quanto di questo è dovuto alle debolezze intrinseche di questi linguaggi, e quanto è dovuto al fatto che quelli sono semplicemente i linguaggi in cui è scritta la maggior parte dell'infrastruttura informatica?


C è inteso per essere "un passo avanti rispetto all'assembler". Non ci sono limiti che controllano oltre a quello che hai implementato, per spremere l'ultimo ciclo di clock dal tuo sistema.

C ++ offre vari miglioramenti rispetto a C, il più rilevante per la sicurezza sono le sue classi contenitore (ad esempio <vector> e <string> ) e dal C ++ 11, puntatori intelligenti, che consentono di gestire i dati senza dover gestire manualmente anche la memoria. Tuttavia, essendo un ' evoluzione di C invece di un linguaggio completamente nuovo, fornisce ancora anche la meccanica di gestione manuale della memoria di C, quindi se insisti a spararti in il piede, il C ++ non fa nulla per impedirti di farlo.


Allora perché cose come SSL, bind o kernel del sistema operativo sono ancora scritte in questi linguaggi?

Perché questi linguaggi possono modificare la memoria direttamente, il che le rende particolarmente adatte per un certo tipo di applicazioni ad alte prestazioni e di basso livello (come crittografia, ricerche di tabelle DNS, driver hardware ... o Java VM, se è per questo ;-)).

Quindi, se un software rilevante per la sicurezza viene violato, la possibilità che venga scritto in C o C ++ è alta, semplicemente perché la maggior parte del software rilevante per la sicurezza è scritto in C o C ++, solitamente per motivi storici e / o di prestazioni. Se è scritto in C / C ++, il vettore di attacco principale è il sovraccarico del buffer.

Se è se fosse una lingua diversa, sarebbe un diverso attacco vecto r, ma sono sicuro che ci sarebbero anche violazioni della sicurezza.


Sfruttare il software C / C ++ è più facile che sfruttare, ad esempio, il software Java. Allo stesso modo in cui sfruttare un sistema Windows è più facile che sfruttare un sistema Linux: il primo è onnipresente, ben compreso (cioè noti vettori di attacco, come trovarli e come sfruttarli), e un molte persone cercano exploit in cui il rapporto ricompensa / impegno è alto.

Ciò non significa che quest'ultimo sia intrinsecamente sicuro (più sicuro er , forse, ma non sicuro ). Significa che, essendo l'obiettivo più difficile con minori benefici, i Bad Boys non ci stanno ancora perdendo molto tempo.

gnasher729
2016-02-23 21:46:35 UTC
view on stackexchange narkive permalink

In realtà, "heartbleed" non era proprio un buffer overflow. Per rendere le cose più "efficienti", mettono molti buffer più piccoli in un unico grande buffer. Il grande buffer conteneva dati da vari client. Il bug leggeva byte che non avrebbe dovuto leggere, ma in realtà non leggeva dati al di fuori di quel grande buffer. Un linguaggio che controllava i buffer overflow non lo avrebbe impedito, perché qualcuno si è fatto in quattro o ha impedito a tali controlli di trovare il problema.

IIRC, l'allocazione della memoria BSD * avrebbe * impedito a quel bug di passare inosservato, ma gli implementatori hanno aggirato attivamente quel sistema perché lo consideravano "troppo lento". In un certo senso, questo è il tipo di scelta su cui si basano C / C ++, solo che questa volta è stata una decisione * davvero * sbagliata. ;-)
Infatti. Se lo facessi in C # potresti facilmente introdurre l'attacco equivalente.
Hanno profilato il loro codice prima di prendere questa decisione progettuale? Trovo davvero difficile credere che stessero colando di bottiglia su `malloc (3)`.
Le allocazioni di memoria @Kevin sono operazioni relativamente lente, in particolare rispetto all'allocazione di un buffer una volta e al suo riutilizzo. Se stai scrivendo codice veloce (e le cose per i server web devono essere veloci mentre le persone si lamentano), allora sì, potrebbe facilmente essere il collo di bottiglia dopo aver rimosso tutti gli altri colli di bottiglia! Questo è assolutamente vero se si assegnano molti piccoli buffer.
@Kevin: L'utilizzo di malloc () in risposta ai dati ricevuti da fonti non attendibili renderà il codice suscettibile a un utente malintenzionato che attiva schemi di allocazione / rilascio che causeranno la frammentazione. Il codice che utilizza e ricicla i pool di memoria può proteggersi da tali problemi in modi che il codice che utilizza malloc () non può.
@gbjbaanb: dipende davvero da cosa stiamo confrontando. Ricordo di aver letto che il "percorso d'oro" di jemalloc era di circa 25 cicli, per esempio. Dato che stiamo parlando di una libreria crittografica e la crittografia non è particolarmente veloce (a meno che non sia assistita dall'hardware), penso che varrebbe la pena di profilare. Detto questo, le cose sono cambiate molto da quando questo codice è stato scritto, e mi sembra di ricordare che si siano lamentati del fatto che piattaforme specifiche fossero lente (ma ha introdotto il buffer per tutte le piattaforme).
@MatthieuM.considera EASTL, un'implementazione di STL per i giochi perché il solito STL ha un sistema di allocazione che non è abbastanza ottimizzato per l'uso nei giochi. C'è un esempio del mondo reale in cui l'allocazione della memoria era un collo di bottiglia, quindi non è così "difficile da credere" come pensava Kevin. OpenSSL potrebbe avere gli stessi requisiti "il più velocemente possibile" o potrebbe essere una memoria WRT mal progettata.
Viktor Toth
2016-02-24 07:49:51 UTC
view on stackexchange narkive permalink

In primo luogo, come altri hanno già detto, C / C ++ è talvolta caratterizzato come un glorificato macro assembler: è pensato per essere "vicino al ferro", come linguaggio per la programmazione a livello di sistema.

Quindi, ad esempio, il linguaggio mi consente di dichiarare un array di lunghezza zero come segnaposto quando, in realtà, può rappresentare una sezione di lunghezza variabile in un pacchetto di dati o l'inizio di una regione di lunghezza variabile nella memoria che viene utilizzata per comunicare con un componente hardware.

Sfortunatamente significa anche che C / C ++ è pericoloso nelle mani sbagliate; se un programmatore dichiara un array di 10 elementi e poi scrive nell'elemento 101, il compilatore lo compilerà felicemente, il codice verrà eseguito felicemente, eliminando qualunque cosa accada in quella posizione di memoria (codice, dati, stack, chi lo sa.)

In secondo luogo, C / C ++ è idiosincratico. Un buon esempio sono le stringhe, che sono fondamentalmente array di caratteri. Ma ogni costante di stringa porta un carattere di terminazione invisibile aggiuntivo. Questa è stata la causa di innumerevoli errori in quanto (soprattutto, ma non esclusivamente) i programmatori inesperti spesso non riescono ad allocare quel byte aggiuntivo necessario per il null di terminazione.

Terzo, C / C ++ è in realtà piuttosto vecchio. Il linguaggio è nato in un momento in cui gli attacchi esterni a un sistema software erano praticamente inesistenti. Gli utenti dovevano essere affidabili e collaborativi, non ostili, poiché il loro obiettivo era far funzionare il programma, non bloccarlo.

Ecco perché la libreria C / C ++ standard contiene molte funzioni che sono intrinsecamente non sicure. Prendi strcpy (), per esempio. Copierà felicemente qualsiasi cosa fino a quando un carattere nullo terminante. Se non trova un carattere nullo di terminazione, continuerà a copiare finché l'inferno non si congela, o più probabilmente, finché non sovrascrive qualcosa di vitale e il programma si blocca. Questo non era un problema ai bei vecchi tempi, quando non ci si aspettava che un utente entrasse in un campo riservato, ad esempio, a un codice postale, 16000 caratteri spazzatura seguiti da un insieme di byte appositamente costruito che dovevano essere eseguiti dopo che lo stack è stato eliminato e il processore ha ripreso l'esecuzione all'indirizzo sbagliato.

Giusto per essere sicuri, C / C ++ non è l'unico linguaggio idiosincratico là fuori. Altri sistemi hanno un comportamento idiosincratico diverso, ma può essere altrettanto negativo. Prendi i linguaggi di programmazione back-end come PHP e quanto è facile scrivere codice che consenta l'iniezione SQL.

Alla fine, se diamo ai programmatori i potenti strumenti di cui hanno bisogno per fare il loro lavoro, ma senza una formazione adeguata e consapevolezza dell'ambiente di sicurezza, le cose brutte accadranno indipendentemente dal linguaggio di programmazione utilizzato.

I potenti strumenti necessari per programmare * in modo efficiente *. In generale, l'accesso diretto alla memoria non è * necessario *; vedere quasi tutti gli altri linguaggi di alto livello.
"Alla fine, se diamo ai programmatori gli strumenti potenti di cui hanno bisogno per svolgere il loro lavoro, ma senza un'adeguata formazione e consapevolezza dell'ambiente di sicurezza, le cose brutte accadranno indipendentemente dal linguaggio di programmazione utilizzato". Quando si utilizza qualsiasi linguaggio di programmazione possono accadere cose brutte. Ma, per ragioni come quelle che descrivi superbamente riguardo a C e C ++, tendono anche (* tendono *) ad accadere più facilmente e frequentemente quando alcuni vengono usati rispetto ad altri.
A peggiorare le cose, gli standard C in realtà non consentono ai programmatori di scrivere codice "vicino al metallo". Se un compilatore può determinare che una certa combinazione di input porterebbe a situazioni in cui lo Standard non imporrebbe requisiti, può omettere il codice che altrimenti avrebbe gestito tali input, anche se non ci sono quasi altre conseguenze plausibili per il essere quasi tanto grave quanto l'omissione di codice basata su inferenze sui possibili input.
Non esiste una cosa come "C / C ++". La maggior parte di ciò di cui parli qui è specifico per C.
** Tutto ** è specifico per C, e anche allora per implementazioni specifiche. Non esiste una regola in C che dica che un compilatore deve anche ** accettare ** un tentativo di accedere al 101 ° elemento di un array di 10 elementi. Potrebbe interrompere la compilazione se il cosiddetto comportamento indefinito è inevitabile. Più realisticamente, può semplicemente presumere che il codice rilevante sia irraggiungibile da "main" e semplicemente omettere l'intera funzione offensiva.
@MSalters Non è specifico per C, perché si applica anche a C ++. Non riesco a capire come si potrebbe pensare che il codice C ++ non possa avere buffer overflow. Anche `std :: vector :: operator []` non controlla i limiti.
idiosincratico - _adj._ Hack su hack su hack e un generoso aiuto di eredità: D
@immibis: A differenza degli array C, std :: vector porta sempre la propria dimensione. _Può_ controllare i limiti, e infatti con `.at (i)` lo fa. Tuttavia, proprio perché le dimensioni sono così comodamente disponibili, il controllo dei limiti di solito è inutile.
@MSalters "Può * può * controllare i limiti" - Sì, e `operator []` (il modo più naturale per indicizzare un vettore) no.
@supercat, Ho letto il tuo commento tre volte e ancora non lo capisco. Ti dispiace spiegare con meno di dieci clausole separate in una frase? : D
@Wildcard: Se determinati input inducono un programma a richiamare un comportamento indefinito, lo standard non impone vincoli riguardo a ciò che il codice generato potrebbe fare se forniti tali input. Ad esempio, dato `int * p, * q`, se un programma verifica` if (p> q) ... `un compilatore avrebbe il diritto di dedurre che il codice non riceverà mai input che provocherebbe l'esecuzione di quel test a meno che "p" e "q" non facciano parte dello stesso oggetto. Anche se le istruzioni che una piattaforma utilizzerebbe per i normali confronti dei puntatori definissero una classifica coerente a livello globale per tutti i puntatori, non esiste un modo definito dallo standard ...
... per un programma per utilizzarlo. Durante il 1990, molti compilatori avrebbero prodotto comportamenti coerenti e utili in molte circostanze in cui lo Standard non imponeva requisiti; anche se lo Standard non ha mai riconosciuto tali comportamenti, i programmatori non hanno visto la necessità di avere il mandato Standard che i compilatori devono fare le cose che stavano già facendo. Sfortunatamente, una bizzarra forma di revisionismo storico ha infestato lo sviluppo del compilatore C, promuovendo la convinzione che i comportamenti che i compilatori non documentassero espressamente perché erano così ampiamente universali da non valere la pena menzionarli ...
... non sono mai stati veramente importanti. I fautori di quella filosofia suggeriscono che se ci fosse la necessità di direttive per consentire i puntatori ad alias cose di diverso tipo ci sarebbe una richiesta per questo, ignorando il fatto che i programmatori stavano scrivendo codice che richiedeva l'aliasing e compilatori stavano accettando tale codice e eseguendolo correttamente. L'idea che non ci sia richiesta per le cose che programmatori e compilatori fanno / fanno abitualmente è bizzarra.
C. M.
2016-02-26 05:53:14 UTC
view on stackexchange narkive permalink

Probabilmente toccherò alcune cose che alcune delle altre risposte hanno già affermato .. ma .. trovo che la domanda stessa sia errata e "vulnerabile".

Come chiesto, la domanda è per scontata molto senza capire i problemi di fondo. C / C ++ non sono "più vulnerabili" di altri linguaggi. Piuttosto, affidano la maggior parte della potenza dei dispositivi informatici e la responsabilità dell'uso di tale potenza direttamente nelle mani del programmatore. Quindi, la realtà della situazione è che molti programmatori scrivono codice che è vulnerabile allo sfruttamento e poiché C / C ++ non fa di tutto per proteggere il programmatore da se stesso come fanno alcuni linguaggi, il loro codice è più vulnerabile. Questo non è un problema C / C ++, poiché i programmi scritti in linguaggio assembly avrebbero gli stessi problemi, ad esempio.

Il motivo per cui una programmazione di basso livello può essere così vulnerabile è perché fare cose come il controllo dei limiti di array / buffer può diventare costoso in termini di calcolo e molto spesso non è necessario quando si programma in modo difensivo. Immagina, ad esempio, di scrivere codice per un importante motore di ricerca, che deve elaborare trilioni di record di database in un batter d'occhio, in modo che l'utente finale non si annoi o non si annoi durante il "caricamento della pagina ..." È visualizzato. Non si desidera che il codice continui a controllare i limiti di array / buffer ogni singola volta nel ciclo; Sebbene possano essere necessari nanosecondi per eseguire tale controllo, il che è banale se stai elaborando solo dieci record, può aggiungere fino a molti secondi o minuti quando esegui il ciclo di miliardi o trilioni di record.

Quindi, invece, "ti fidi" che l'origine dati (ad esempio, il "web bot" che analizza i siti web e inserisce i dati nel database) abbia già controllato i dati. Questo non dovrebbe essere un presupposto irragionevole; Per un programma tipico, vuoi controllare i dati in input , in modo che il codice che elabora i dati possa funzionare alla massima velocità. Anche molte librerie di codice adottano questo approccio. Alcuni documentano addirittura che si aspettano che il programmatore abbia già controllato i dati prima di chiamare le funzioni di libreria per agire sui dati.

Sfortunatamente, tuttavia, molti programmatori non programmano in modo difensivo e presumono semplicemente che i dati debbano essere valido e entro limiti / parametri sicuri. E questo è ciò che viene sfruttato dagli aggressori.

Alcuni linguaggi di programmazione sono progettati in modo tale da cercare di proteggere il programmatore da pratiche di programmazione così scadenti inserendo automaticamente controlli aggiuntivi nel programma generato, cosa che il programmatore non ha fatto scrivere esplicitamente nel loro codice. Ancora una volta, questo va bene quando eseguirai il ciclo del codice solo poche centinaia di volte o meno. Ma quando stai attraversando miliardi o trilioni di iterazioni, si verificano lunghi ritardi nell'elaborazione dei dati, che potrebbero diventare inaccettabili. Quindi è un compromesso quando si sceglie quale lingua utilizzare per un particolare pezzo di codice e quanto spesso e dove si verificano condizioni potenzialmente pericolose / sfruttabili all'interno dei dati.

tl; dr: C'è un compromesso tra controlli di sicurezza potenzialmente non necessari e velocità.
"Ma quando stai attraversando miliardi o trilioni di iterazioni, si aggiunge a" - quando stai iterando attraverso un array, si aggiunge esattamente * un * singolo controllo prima del ciclo, perché i compilatori moderni sono piuttosto intelligenti. L'unica volta che paghi per un controllo dei limiti è se il compilatore non riesce a capire se è sicuro, il che generalmente significa che è un accesso casuale. In questo caso si paga circa 1 ciclo in più, che sì in alcune situazioni può sommarsi (ad es. Operazioni con matrice), ma per il 99,9% di tutto il codice questo è completamente trascurabile.
Questo non è necessariamente vero. Sì, i compilatori moderni sono davvero molto intelligenti e possono ottimizzare molto codice. Ma è * ancora solo un programma per computer *, non un essere intelligente che può guardare il tuo codice e sapere con assoluta certezza esattamente cosa intendeva fare il programmatore. Ci sono ancora casi in cui il compilatore non può effettuare le ottimizzazioni della "scelta perfetta" e ricade su quelle più sicure, che potrebbero essere troppo lente per un certo scopo, i programmatori le disattivano. La tendenza delle persone a fare affidamento su "compilatori intelligenti" per svolgere il proprio lavoro è parte del motivo per cui questo tipo di problema persiste.
Per aggiungere a questo, ci sono diverse ipotesi che sono state fatte. Innanzitutto, "99,9%": da dove viene questo numero? A me sembra che "l'80% delle statistiche è composto in loco". In secondo luogo, i dati in fase di elaborazione sono in una bella, ordinata matrice .. che non è sempre vero. In effetti, il concetto di rendere i dati conformi alle operazioni "sicure" fa parte della manipolazione dei dati costosa dal punto di vista computazionale che i programmatori cercano di evitare e presumono semplicemente che i dati siano "sicuri" o che il compilatore "li aggiusterà in modo è." E così via.
Bing Bang
2016-02-23 23:37:48 UTC
view on stackexchange narkive permalink

Fondamentalmente i programmatori sono persone pigre (me compreso). Fanno cose come usare gets () invece di fgets () e definire i buffer di i / o sullo stack e non cercare abbastanza i modi in cui la memoria potrebbe essere sovrascritta involontariamente (beh, involontariamente per il programmatore, intenzionalmente per l'hacker :).

Avere un buffer I / O sullo stack non è male, basta non chiamare "gets" con esso.
È difficile immaginare un programmatore che utilizzi C / C ++ per ** pigrizia **!
@DmitryGrigoryev mi hanno fatto imparare C / C ++ a scuola e sono troppo pigro per imparare altre lingue :)
@JOW Nella mia esperienza, la codifica di un frontend ragionevolmente piccolo (un paio di pulsanti, richieste HTTP, analisi XML, file IO) in C # senza alcuna conoscenza preliminare di quel linguaggio era ancora più veloce di quanto mi aspettassi codificare la stessa app in C ++ / MFC .
@JOW Ovviamente non fai il tipo di programmazione che posso fare io. Ho circa 10.000 righe di codice C intenso in produzione ...
@BingBang Mi sentivo a disagio solo a leggerlo.
@Gusdor Niente coraggio, niente gloria, amico.
Yakk
2016-02-27 03:41:50 UTC
view on stackexchange narkive permalink

Esiste una grande quantità di codice C esistente che esegue la scrittura non controllata nei buffer. Alcuni di questi sono nelle biblioteche. Questo codice è potenzialmente pericoloso se uno stato esterno può cambiare la lunghezza scritta, e solo molto pericoloso altrimenti.

C'è una quantità maggiore di codice C esistente che esegue la scrittura limitata nei buffer. Se l'utente di detto codice commette un errore di matematica e lascia che sia scritto più di quanto dovrebbe, questo è sfruttabile come sopra. Non vi è alcuna garanzia in fase di compilazione che i calcoli siano eseguiti correttamente.

C'è anche una grande quantità di codice C esistente che legge in base agli offset in memoria. Se l'offset non viene verificato come valido, ciò può far perdere informazioni.

Il codice C ++ viene spesso utilizzato come linguaggio di alto livello per l'interoperabilità con C, quindi vengono seguite molte idee di C e bug dalla comunicazione con C Le API sono comuni.

Esistono stili di programmazione C ++ che impediscono tali sovraccarichi, ma basta un solo errore per consentirli.

Inoltre, il problema dei puntatori penzolanti, dove la memoria le risorse vengono riciclate e il puntatore ora punta alla memoria con una durata / struttura diversa da quella originaria, consente alcuni tipi di exploit e fughe di informazioni.

Questi tipi di errori - errori "fencepost", "penzoloni gli errori "pointer" - sono così comuni, e così difficili da eliminare completamente, che molti linguaggi sono stati sviluppati con sistemi progettati esplicitamente per impedire che si verifichino.

Non sorprende che nei linguaggi progettato per eliminare questi errori, questi errori non si verificano così spesso. A volte si verificano ancora: o il motore che esegue il linguaggio ha il problema, o viene impostata una situazione manuale che corrisponde all'ambiente del caso C / C ++ (riutilizzo di oggetti in un pool, utilizzando comune un grande buffer comune suddiviso per consumatore, ecc. ). Ma poiché questi usi sono più rari, il problema si verifica meno spesso.

Ogni allocazione dinamica, ogni utilizzo del buffer, in C / C ++ corre questi rischi. Ed essere perfetti non è raggiungibile.

Rich
2016-02-27 05:07:40 UTC
view on stackexchange narkive permalink

I linguaggi più comunemente usati (Java e Ruby, ad esempio) vengono compilati in codice che viene eseguito in una VM. La VM è progettata per separare il codice macchina, i dati e solitamente lo stack. Ciò significa che le normali operazioni del linguaggio non possono modificare il codice o reindirizzare il flusso di controllo (a volte ci sono API speciali che possono farlo, ad esempio per il debug).

C e C ++ vengono solitamente compilati direttamente nel linguaggio macchina nativo della CPU: questo offre vantaggi in termini di prestazioni e flessibilità, ma significa che un codice errato può sovrascrivere la memoria del programma o lo stack e quindi eseguire istruzioni non nel programma originale.

Ciò si verifica in genere quando un buffer viene sovraccaricato (forse deliberatamente) in C ++. In Java o Ruby, al contrario, un sovraccarico del buffer causerà immediatamente un'eccezione e non può (ad eccezione dei bug della VM) sovrascrivere il codice o modificare il flusso di controllo.

Questo non ha nulla a che fare con l'esecuzione su una VM o meno. Potresti avere una VM con lo stesso comportamento di c, proprio come puoi avere programmi che si compilano direttamente in codice macchina che sono sicuri come dire java (ad esempio ADA)
In teoria In quasi tutti i casi pratici, Java viene eseguito su una VM che impedisce la sovrascrittura del codice e C / C ++ viene eseguito su una macchina nuda che non lo fa.
Sì. E ADA non viene eseguito su una VM e impedisce anche la maggior parte di questi exploit proprio come Java. Avere una VM o meno è completamente irrilevante per questo (cosa pensi sia fare di speciale su una VM che non può essere fatto altrimenti? Diamine in realtà introduce solo una possibile vulnerabilità di sicurezza perché il JIT ha bisogno di memoria scrivibile ed eseguibile!)


Questa domanda e risposta è stata tradotta automaticamente dalla lingua inglese. Il contenuto originale è disponibile su stackexchange, che ringraziamo per la licenza cc by-sa 3.0 con cui è distribuito.
Loading...