2009-10-23 12 views
6

quello che succede è che sto leggendo pacchetti di crittografia e incontro un pacchetto corrotto che restituisce un numero casuale molto grande per la lunghezza.vector.resize funzione che danneggia la memoria quando la dimensione è troppo grande

size_t nLengthRemaining = packet.nLength - (packet.m_pSource->GetPosition() - packet.nDataOffset); 

seckey.SecretValues.m_data.resize(nLengthRemaining); 

In questo codice m_data è un std::vector<unsigned char>. nLengthRemaining è troppo grande a causa di un pacchetto di dati danneggiato, pertanto la funzione di ridimensionamento viene generata. Il problema non risiede nel ridimensionamento (gestiamo le eccezioni), ma il ridimensionamento ha già danneggiato la memoria e questo porta a più eccezioni in seguito.

Quello che voglio fare è sapere se la lunghezza è troppo grande prima chiamo ridimensionamento, quindi chiamare ridimensionare se è ok. Ho provato a mettere questo codice prima della chiamata a ridimensionare:

std::vector<unsigned char>::size_type nMaxSize = seckey.SecretValues.m_data.max_size(); 
if(seckey.SecretValues.m_data.size() + nLengthRemaining >= nMaxSize) { 
    throw IHPGP::PgpException("corrupted packet: length too big."); 
} 
seckey.SecretValues.m_data.resize(nLengthRemaining); 

Questo codice utilizza la funzione di std :: vector membro max_size per verificare se il nLengthRemaining è più grande. Tuttavia, ciò non deve essere affidabile, poiché nLengthRemaining è ancora inferiore a nMaxSize, ma apparentemente abbastanza grande da causare il ridimensionamento di un problema (nMaxSize era 4xxxxxxxxx e nLengthRemaining è 3xxxxxxxxx).

Inoltre, non ho determinato quale ridimensionamento dell'eccezione viene lanciato. Non è uno std :: length_error e non è uno std :: bad_alloc. Quel che fa eccezione non è davvero troppo importante per me, ma sono curioso di sapere.

btw, solo così si sa, questo codice funziona correttamente in casi normali. Questo caso di un pacchetto di dati corrotto è l'unico posto dove impazzisce. Per favore aiuto! Grazie.

UPDATE:

@ Michael. Per ora ignorerò semplicemente il pacchetto se è più grande di 5 MB. Discuterò con altri membri del team sulla possibilità di convalidare i pacchetti (potrebbe già esserci e io non lo so). Sto iniziando a pensare che sia davvero un bug nella nostra versione di STL, l'eccezione che getta non è nemmeno un'eccezione std ::, che mi ha sorpreso. Cercherò di scoprire dal mio supervisore quale versione di STL stiamo eseguendo anche (come potrei controllare?).

UN ALTRO AGGIORNAMENTO: Ho appena provato che si tratta di un bug nella versione STL che sto utilizzando sul mio computer di sviluppo di Visual Studio 6. Ho scritto questa app di esempio:

// VectorMaxSize.cpp: definisce il punto di ingresso per l'applicazione della console. //

#include "stdafx.h" 
#include <vector> 
#include <iostream> 
#include <math.h> 
#include <typeinfo> 

typedef std::vector<unsigned char> vector_unsigned_char; 

void fill(vector_unsigned_char& v) { 
    for (int i=0; i<100; i++) v.push_back(i); 
} 


void oput(vector_unsigned_char& v) { 
    std::cout << "size: " << v.size() << std::endl; 
    std::cout << "capacity: " << v.capacity() << std::endl; 
    std::cout << "max_size: " << v.max_size() << std::endl << std::endl; 
} 

void main(int argc, char* argv[]) { 
    { 
     vector_unsigned_char v; 

     fill(v); 

     try{ 
      v.resize(static_cast<size_t>(3555555555)); 
     }catch(std::bad_alloc&) { 
      std::cout << "caught bad alloc exception" << std::endl; 
     }catch(const std::exception& x) { 
      std::cerr << typeid(x).name() << std::endl; 
     }catch(...) { 
      std::cerr << "unknown exception" << std::endl; 
     } 

     oput(v);  
     v.reserve(500); 
     oput(v); 
     v.resize(500); 
     oput(v); 
    } 

    std::cout << "done" << std::endl; 
} 

Sulla mia macchina dev VS6 ha lo stesso comportamento ha il progetto di crittografia, che provoca ogni sorta di caos. Quando lo costruisco ed eseguo sul mio computer di Visual Studio 2008, ridimensionamento genererà un'eccezione std :: bad_alloc e il vettore non sarà corrotto, proprio come ci saremmo aspettati! È tempo di giocare a EA Sport con il calcio NCAA!

+0

Quale piattaforma sei? – sbi

+0

Sono curioso di sapere quale versione del compilatore/stl stai usando. Le implementazioni a cui ho accesso non corromperanno l'oggetto vettoriale se l'allocazione fallisce. – jmucchiello

+0

@cchampion: "Sulla mia macchina dev VS6 ..." Avevi detto prima, non avremmo perso così tanto tempo su questo. Questa è più di 10 anni di tecnologia! Certo, è buggy. Vedi la mia risposta. – sbi

risposta

5

Penso che vector::max_size() sia praticamente sempre una cosa "codificata": è indipendente dalla quantità di memoria che il sistema/libreria è pronta ad allocare dinamicamente. Il tuo problema sembra essere un bug nell'implementazione del vettore che corrompe le cose quando un'allocazione fallisce.

"Bug" potrebbe essere una parola troppo forte.vector::resize() è definito in termini di vector::insert() e lo standard dice questo circa vector::insert():

Se viene generata un'eccezione diversa da parte del costruttore di copia o di operatore di assegnazione di T non ci sono effetti

così sembra come ci possono essere momenti in cui l'operazione resize() è in grado di corrompere un vettore, ma sarebbe comunque bello se l'operazione fosse eccezionalmente sicura (e penso che non sarebbe fuori linea aspettarsi che la libreria lo faccia, ma forse è più difficile di quanto immagino).

Lei sembra avere un paio di opzioni ragionevoli: (? Quale compilatore/versione della libreria stai usando)

  • modifica o aggiornamento di una libreria che non hai il bug corruzione
  • invece di controllare Contro vector::max_size() imposta il nMaxSize al tuo massimo ragionevole e fai ciò che hai sopra ma usando quella soglia.

Edit:

vedo che si sta utilizzando VC6 - c'è sicuramente un bug in vector::resize() che potrebbe avere qualcosa a che fare con il problema, anche se guardando la patch onestamente non vedere come (in realtà si tratta di un bug in vector::insert(), ma come già detto, resize() chiamate insert()). Direi che varrebbe la pena visitare Dinkumwares' page for bug fixes to VC6 e applicare le correzioni.

Il problema potrebbe anche avere a che fare con la patch <xmemory> in quella pagina - non è chiaro ciò che il bug è che è discusso lì, ma vector::insert() fa chiamare _Destroy() e vector<> fa definire il nome _Ty per cui si potrebbe essere in esecuzione in quel problema . Una cosa carina - non dovrai preoccuparti di gestire le modifiche alle intestazioni, dato che Microsoft non le toccherà mai più. Assicurati che le patch facciano il controllo della versione e vengano documentate.

Si noti che Scott Meyers in "STL efficace" suggerisce di utilizzare la libreria SGI's o STLPort's per ottenere un supporto STL migliore rispetto a VC6. Non l'ho fatto quindi non sono sicuro di quanto bene funzionino queste librerie (ma non ho usato molto VC6 con STL). Naturalmente, se si ha la possibilità di passare a una versione più recente di VC, farlo assolutamente.


Un altro Edit:

Grazie per il programma di test ...

_Allocate() implementazione di VC6 per l'allocatore di default (in <xmemory>) utilizza un int firmato per specificare il numero di elementi di allocare e se la dimensione passata è negativa (che apparentemente è ciò che stai facendo - certamente nel programma di test che sei) la funzione _Allocate() impone a zero la dimensione dell'allocazione richiesta e procede. Si noti che una richiesta di allocazione a dimensione zero avrà quasi sempre successo (non che i controlli vector abbiano comunque esito negativo), quindi la funzione vector::resize() tenta allegramente di spostare il suo contenuto nel nuovo blocco, che non è abbastanza grande per dire il minimo . In questo modo l'heap viene danneggiato, probabilmente colpirà una pagina di memoria non valida e, a prescindere, il tuo programma verrà hosed.

Quindi la linea di fondo è non chiedere mai a VC6 di allocare più di INT_MAX oggetti in un colpo solo. Probabilmente non è una grande idea nella maggior parte delle circostanze (VC6 o altro).

Inoltre, è necessario tenere presente che VC6 utilizza un linguaggio pre-standard per la restituzione di 0 da new quando un'allocazione fallisce piuttosto che il lancio di bad_alloc.

+0

"Se viene lanciata un'eccezione diversa dal costruttore di copie o dall'operatore di assegnazione di T non ci sono effetti" IRTA ", è come se" resize() "non sia stato chiamato." E sono abbastanza sicuro che nessuna operazione su un vettore dovrebbe danneggiare la memoria. – sbi

+0

L'operazione insert() potrebbe risultare in operazioni di copia/assegnazione (quando i contenuti vettoriali vengono copiati su una nuova allocazione) - a quelli è consentito 'avere un effetto'. Ad esempio, non dovrebbe fare nulla di male come corrompere l'heap, ma non è chiaro se questo è ciò che sta accadendo all'OP. Un'eccezione in queste condizioni può provocare un vettore modificato (forse non tutti gli elementi nel vettore arrivano a quello nuovo). Il suo codice potrebbe scoprire che il vettore non ha più senso. In entrambi i casi non è un grande comportamento, e sono d'accordo sul fatto che un'implementazione STL potrebbe gestire meglio la situazione. –

+0

Hai ragione anche se copiare/assegnare elementi in un 'vector ' non dovrebbe comportare alcuna eccezione - mi sembra di puntare a un'implementazione STL bacata che non gestisce bene la situazione di memoria insufficiente . Sarei interessato ai dettagli sulla piattaforma/compilatore/libreria in uso. –

5

Suggerisco FORTEMENTE di controllare i dati per la corruzione PRIMA di chiamare le funzioni della libreria con argomenti magari errati!

Utilizzare una sorta di codice hash o algoritmo di checksum sui pacchetti. Non si può fare affidamento sulla libreria per aiutarti poiché non è in grado di fare: Può darsi che tu gli dia una dimensione corrotta ma ancora valida (dal punto di vista della libreria) che è molto grande quindi assegna per esempio 768 MB di RAM. Questo può funzionare se c'è abbastanza memoria libera nel sistema, ma potrebbe fallire se ci sono altri programmi in esecuzione che consumano troppa memoria sulla macchina da 1024 MB.

Come detto sopra: controllare prima!

+0

Sono d'accordo. Penso che la radice del tuo problema sia che ti stai affidando all'algoritmo di crittografia per dirti la dimensione che "pretende" di essere. Hai davvero bisogno di un'applicazione a dimensione di blocco con padding (come fa MD5) o hai un altro modo fuori banda per fornire informazioni sulle dimensioni. –

+0

Sono d'accordo, abbiamo bisogno di un metodo per convalidare i pacchetti. Ne parlerò ad altri programmatori di questo progetto lunedì.Per ora salterò il pacchetto se è più grande di 5 MB. – cchampion

4

Non ho idea di cosa intendi quando dici "ridimensiona ha danneggiato la memoria". Come lo determini?

FWIW, non sono d'accordo con Michael's answer. Se std::vector<>::resize() tiri sull'espansione vettore, vedo due possibilità:

  1. O uno dei costruttori utilizzati per riempire il nuovo spazio (o copiare gli elementi) fa arrivare o
  2. l'allocatore utilizzato per far crescere il vettore ha fatto
  3. o il vettore determinato prima mano che la dimensione richiesta è troppo e getta.

Con std::vector<unsigned char> possiamo tranquillamente respingere # 1, in modo che lascia # 2. Se non si utilizza alcun allocatore speciale, è necessario utilizzare std::allocator e, AFAIK, che chiamerà new per allocare memoria. E new getterebbe std::bad_alloc. Tuttavia, tu dici che non puoi prenderlo, quindi non so cosa succede.

Qualunque cosa sia, si dovrebbe essere derivato dal std::exception, così si potrebbe fare questo per scoprire:

try { 
    my_vec.resize(static_cast<std::size_t>(-1)); 
} catch(const std::exception& x) { 
    std::cerr << typeid(x).name() << '\n'; 
} 

Qual è il risultato di questo?

In ogni caso, qualunque sia, sono abbastanza sicuro che non dovrebbe corrompere la memoria. O quello è un bug nella tua implementazione di lib di std (improbabile, se me lo chiedi, a meno che non ne usi uno molto vecchio) o hai fatto qualcosa di sbagliato altrove.


Modifica ora che hai detto che si sta utilizzando VS6 ...

si dovrebbe avere detto questo in precedenza. VC6 è stato rilasciato più di un decennio fa, dopo che la SM aveva perso il proprio voto nel comitato std perché non si erano presentati agli incontri per troppo tempo.L'implementazione della lib di std spedita proveniva da Dinkumware (buona), ma a causa di problemi legali era quella per VC5 (pessima), che aveva molti bug sempre più grandi e non aveva nemmeno il supporto per i template dei membri, anche se il compilatore VC6 lo supportava. Onestamente, cosa ti aspetti da un prodotto così vecchio?

Se non è possibile passare a una versione VC decente (mi piacerebbe almeno VC7.1 aka VS.NET 2003 come questo è stato quello che ha fatto il grande salto verso la conformità standard), almeno vedere se Dinkumware ancora vendere una versione VC6t della loro eccellente libreria. (In realtà, sarei sorpreso, ma ne avevano uno e non si sa mai ...)

Come per le eccezioni: nella precedente versione VC (questo include VC6 e non include VC8 aka VS.NET 2005 , Non sono sicuro di VC7.1, però) per impostazione predefinita le violazioni di accesso potrebbero essere rilevate da catch(...). Quindi, se un blocco di cattura catturato qualcosa, non sapresti se questa è stata anche un'eccezione C++. Il mio consiglio sarebbe quello di usare solo catch(...) in confidenza con throw; per far passare quell'eccezione. Se lo fai, ottieni un vero crash su AV e sei in grado di eseguire lo stack-trace nel debugger. Se non lo fai, gli AV verranno inghiottiti e poi rimarrai bloccato con un'applicazione impazzita senza che tu nemmeno lo sappia. Ma fare qualsiasi cosa tranne l'abortire con un'applicazione AV non ha senso. Un AV è un risultato di comportamento non definito e dopo di che, tutte le scommesse sono disattivate.

+0

La ragione per cui dico che la memoria è danneggiata è perché semplici istruzioni di registro iniziano a generare eccezioni e anche un sacco di asserzioni relative alla memoria continuano a spuntare. Dopo la funzione di ridimensionamento, l'intero programma lo perde. Questo non succede se si passa un ridimensionamento di una lunghezza ragionevole. Non succede se salto quel pacchetto. Proverò quel codice per determinare il tipo di eccezione, non sapevo di typeid! Grazie. – cchampion

+0

Se non lo sapevi, devi "#includere " per quello. – sbi

+0

Che ci crediate o no, const std :: exception & handler NON lo ha catturato. Non ho idea di cosa sia questa eccezione. Ora sto davvero iniziando a credere che la versione dell'STL che stiamo usando abbia un bug in esso. Discuterò di questo con il responsabile del programma lunedì. – cchampion

Problemi correlati