2013-08-29 17 views
21

Da "Effective C++ 3a edizione di Scott Meyers":Un comportamento non definito può cancellare il disco rigido?

Per sottolineare che i risultati di un comportamento indefinito non sono prevedibili e possono essere molto sgradevole, esperti programmatori C++ dicono spesso che i programmi con un comportamento indefinito può cancellare il disco guidare.

In quali circostanze può accadere?

Ad esempio, è possibile accedere e scrivere in posizioni fuori dalla memoria danneggiata dell'intervallo di array che non appartiene a questo programma o thread C++?

+0

di Scott Meyers? –

+2

@ ShumailMohy-ud-Din sì, corretto. Di Scott Meyers – Oleksiy

+12

Fai un esempio. C'è una funzione che cancella un disco rigido. Ora, hai appena assegnato un numero intero a un puntatore a funzione, che sfortunatamente capita di essere l'indirizzo della funzione che cancella il disco rigido. Se si chiama la funzione dereferenziando il puntatore della funzione, l'HDD viene cancellato. – phoxis

risposta

25

Può esso? Sicuro. Mi è successo, infatti.

Ho scritto il codice per eliminare una directory temporanea. Ciò comportava la creazione di un comando recursive delete <temp directory>\*.*. A causa di un bug, il campo <temp directory> non è stato sempre compilato. Il nostro codice di file system ha eseguito felicemente il comando recursive delete \*.*.

I miei colleghi hanno notato quando le icone sul desktop sono scomparse improvvisamente. Ho preso due macchine.

+0

ahi! ..... lezione appresa? : D – Oleksiy

+3

Sembra che si tratti in realtà di un comportamento ben definito (anche se indesiderato), perché si sa esattamente cosa farà se non si riempie quel campo. –

+4

Oh, la creazione esplicita del comando avrebbe comportato solo un comportamento ben definito. Il vero bug era UB, e la domanda era come UB poteva portare alla cancellazione di un disco fisso. Bene, in questo modo: una variabile viene corrotta, in questo caso una variabile path e un altro codice senza bug inizia a comportarsi in modi imprevisti. Questo è un problema generale con UB: rompe forti ipotesi su altri codici. – MSalters

2

Un semplice esempio potrebbe essere che ti capita di danneggiare il numero di blocco che stai scrivendo, o il nome del file che stai per eliminare.

6

Una violazione della memoria può comportare in teoria che il programma esegua il codice errato. Se sei molto sfortunato, potrebbe essere il codice che elimina le cose sul tuo disco rigido. Ho il sospetto che sia improbabile arrivare così lontano, a meno che tu non stia gestendo autonomamente le operazioni a basso livello del disco.

Penso che il punto dell'affermazione sia che è necessario prendere estremamente sul serio un comportamento indefinito e fare tutto ciò che è pratico per proteggersi da esso (cioè programmazione difensiva). Ho visto troppi programmatori maliziosi affidarsi ingenuamente a qualche comportamento indefinito, supponendo che funzionerà sempre allo stesso modo. In pratica, non è prevedibile e talvolta il risultato può essere catastrofico.

1

In Linux, qualsiasi operazione è valida quando si è utenti root. Anche distruggendo il tuo filesystem di root. rm -rf /

Ogni segmento di codice (con bug) viene eseguito felicemente quando si è root. Si presuppone che tutti gli UB siano destinati alle autorizzazioni sudo.

+0

Non sono sicuro che questo sia il modo migliore per spiegarlo. Non è come se un comportamento indefinito fosse intenzionalmente dato alla radice, è solo ... indefinito.In teoria, i comportamenti non definiti non-root non dovrebbero essere in grado di fare tutto ciò che il normale codice non-root non può, ma se sei già sfortunato ... beh, vedi la storia di @ Mat sopra. –

7

Dal C++ 11 standard (in realtà da Draft N3337), nella sezione 1.3 Termini e definizioni [intro.defs] (enfasi mio):

comportamento indefinito
comportamento per il quale questo International Standard impone senza requisiti
[Nota: Si può prevedere un comportamento indefinito quando questo International Standard omette qualsiasi definizione esplicita del comportamento di o quando un programma utilizza un costrutto errato o dati errati.comportamento indefinito ammissibile varia da ignorando la situazione completamente con risultati imprevedibili, a comportarsi durante la traduzione o esecuzione del programma in modo caratteristico documentato dell'ambiente (con o senza l'emissione di un messaggio diagnostico), per terminare una traduzione o esecuzione (con l'emissione di un messaggio diagnostico). Molti costrutti di programma errati non generano un comportamento indefinito; devono essere diagnosticati. - fine nota]

Da "Non sono richiesti requisiti" + "risultati imprevedibili" possiamo concludere che (in teoria) tutto può succedere.

Ora, senza compilatore "ragionevole" sarebbe volutamente emettere il codice per cancellare il disco rigido per, ad esempio, una divisione per 0, ma potrebbe accadere se sbagli con il file system o addirittura, come hai detto , se si danneggia la memoria (modifica:, vedere MSalters' comment on their own answer).

Il punto è: essere sempre attenti a mai mai mai invocare un comportamento indefinito. "Here Be Dragons."

(In pratica può essere difficile essere certi che il programma sia ben definito.) Alcuni consigli. Conoscere bene la lingua e stare lontano dagli angoli polverosi. Se un pezzo di codice sembra sospetto o troppo complesso, prova a riscriverlo per renderlo più semplice e chiaro Compilare sempre con il livello più alto di avvertenze e non ignorarle.Esistono anche bandiere del compilatore come -fcatch-undefined-behavior e strumenti come lint. E, ovviamente, testare, ma questo è un un po 'in ritardo.)

+0

Qualcosa come la divisione per zero non è così innocuo come potrebbe sembrare. Molti processori hanno una trap "divide per zero" e alcuni sistemi operativi permetteranno al codice utente di installare un gestore per questo. Se una routine installa un gestore di divisione per zero che modifica una delle variabili e restituzioni automatiche di quella routine e quella routine termina con il gestore ancora installato, allora una divisione per zero che si verifica in un'altra routine completamente diversa potrebbe eliminare alcuni arbitrari valore sullo stack prima di continuare l'esecuzione, portando a cattiva sorte. – supercat

+0

Non riesco a trovare ulteriori informazioni su questi flag del compilatore che hai menzionato -fcatch-undefined-behavior e lint. Puoi approfondire un po 'su di loro? Come posso abilitarli sul mio compilatore? –

11

Se si tiene conto che UB è disponibile non solo per il codice in modalità utente, ma anche per il programmatore di sistema. In altre parole, se stai scrivendo il codice del driver con UB (o altri bug!) In esso, potresti finire per scrivere su un pezzo di memoria che in seguito verrà riscritto come "la radice dell'intera struttura dei dati del disco" .

In effetti, ho riscontrato un errore in un driver su cui ho lavorato, che ha causato il danneggiamento del disco, poiché il driver utilizzava puntatori non aggiornati (l'uso del puntatore è gratuito). Se tu fossi UNLUCKY, la memoria non utilizzata era un blocco di proprietà del filesystem, quindi scriverebbe sul disco un po 'di spazzatura casuale. Fortunatamente, non era troppo difficile determinare quale fosse il problema, e avevo solo bisogno di riformattare il disco una volta sul mio sistema di test (quando si lavora sui driver, in genere si usano due computer, uno per compilare il codice e uno per testare il codice su - la macchina di prova ha tipicamente un set di installazione minimo, e spesso viene riformattato e riconfigurato relativamente spesso comunque).

Non penso che la menzione di Scott significhi necessariamente questo tipo di situazione, ma è del tutto possibile che se si dispone di un codice abbastanza selvaggio, può causare quasi tutto. Compresa la ricerca di buchi nel sistema di sicurezza (vedi tutti gli exploit che distruggono lo stack che hanno avuto successo). Probabilmente devi essere MOLTO sfortunato per riuscirci, ma le persone vincono anche quelle mega-lotterie di tanto in tanto, quindi se riesci a ottenere qualcosa che ha una possibilità in diversi milioni una volta alla settimana o una volta al mese, allora un computer che può eseguire operazioni molte milioni di volte al secondo può raggiungere cose molto meno probabili ...

2

Sì.

Considerare un'applicazione che elabora un input esterno (ad esempio un componente di un'applicazione Web) e che ha un overflow del buffer, che è un tipo piuttosto comune di comportamento non definito.

Un utente malintenzionato lo rileva e inserisce intenzionalmente un input che cancella tutti i dati. (La maggior parte degli attaccanti in realtà non lo fa: quello che vogliono è recuperare i dati o impiantare contenuti sul tuo sito, ma a volte alcuni vogliono cancellare i file.)

L'estensione massima del danno dipende da quali livelli di sicurezza l'utente malintenzionato è in grado di ignorare. Se il server non è stato configurato in modo sicuro o se sono presenti altre vulnerabilità che l'utente malintenzionato è in grado di sfruttare, l'utente malintenzionato potrebbe ottenere i privilegi di amministratore sulla macchina o utilizzarlo come relay per attaccare altre macchine. Tutto questo da un singolo overflow del buffer.

La lezione da tenere presente è che il comportamento non definito non riguarda solo le cose che possono accadere. Non solo le cose possono accadere che non ti aspetteresti (alcuni compilatori sono molto bravi a raccogliere strane ottimizzazioni che sono corrette solo quando una variabile non viene modificata due volte tra i punti di sequenza e fare qualcosa di molto sorprendente altrimenti), ma possono accadere cose che sono matematicamente estremamente improbabili perché qualcuno ha deliberatamente fatto di tutto per farlo accadere.

0

[Questa risposta arriva con quattro anni di ritardo. Chi lo leggerà? Vedremo.]

Er, senza offesa, ma molte delle altre risposte sono errate o comunque fuorvianti, secondo la mia esperienza.

Lo standard C++ non limita il comportamento non definito. Il sistema operativo tuttavia lo vincola normalmente. Motivo: il comportamento non è definito rispetto a C++.

... fare sempre attenzione a non invocare mai un comportamento indefinito.

Sciocchezze. L'esperienza smentisce questo suggerimento. I programmatori C++ invocano spesso inavvertitamente comportamenti non definiti durante i test. A volte lo faccio apposta, solo per vedere cosa succede.

Ora, mi rendo conto che qualcuno pensa che io stia ostentando sciocchezza qui, ma in realtà, il tuo laptop non ha quasi più probabilità di prendere fuoco con un comportamento indefinito rispetto a quello definito. Il comportamento non definito in C++ emette il codice assembly con il comportamento definito. Pensaci. Il comportamento dell'assieme rimane definito. È solo che lo standard C++ non comprende più i meccanismi.

Ci sono volte in cui si desidera provocare un comportamento indefinito solo per vedere cosa sta succedendo in pila.

Se ci si trova in un ambiente in cui è possibile scrivere un definito programma C++ che incendia il laptop, quindi si dovrà fare attenzione in ogni caso; ma il problema principale in questo caso è la mancanza di protezione basata su hardware e/o kernel.

In conclusione, non lasciare che lo standard C++ ti confonda. Ti sta solo informando sui propri limiti di competenza.

+0

La filosofia che descrivi potrebbe essere appropriata per qualcuno che scrive un compilatore di qualità generale, ma i moderni ottimizzatori consentono a UB di gettare le leggi del tempo e della causalità fuori dalla finestra. Se un compilatore può capire che qualsiasi stato in cui x non è 42 darebbe UB, potrebbe (e da quello che posso dire, secondo la filosofia di alcune persone, dovrebbe) generare codice che si comporta come se x fosse 42. Se un programma farebbe qualcosa di pericoloso quando x è 42, e fa affidamento sul codice di sicurezza che per evitare che x diventi 42 quando non dovrebbe, tale ottimizzazione può bypassare completamente tutte le misure di sicurezza. – supercat

Problemi correlati