Wie sieht Code, der nen Pufferüberlauf als Sicherheitslücke hat, aus?



  • Gibt es so ne Art Quota für Prozesse?

    Das brauche ich nämlich:

    http://www.heise.de/security/artikel/Windows-Speicherverwuerfelung-ausgetrickst-1790963.html



  • RonjaRaeuber schrieb:

    Ich kenn mich nicht allzu gut damit aus, aber das sieht doch so aus, dass sich viele Sicherheitslücken dieser Art vermeiden lassen, wenn man dynamische Dinge wie std::string (in C++) statt char-Arrays nimmt?

    Nur kann man die eben nicht immer gebrauchen. An manchen Ecken ist man einfach auf char-Arrays ( egal ob fixed-size oder dynamisch ) angewiesen.

    Ich versuche überall wo es aus meiner Sicht Sinn macht, schon aus Gründen der Faulheit, STL-Strings zu verwenden. Wenns aber eine performancekritische Ecke ist oder ein Anwendungsfall, wo 0-Zeichen auch mal spontan auftauchen, dann verzichte ich leichten Herzens auf den STL-String.

    Wie schon jemand ansatzweise sagte: Das Problem ist nicht die Verwendung einer bestimmte Klasse, sondern das Problem ist schlampiger Code 😉


  • Mod

    It0101 schrieb:

    An manchen Ecken ist man einfach auf char-Arrays ( egal ob fixed-size oder dynamisch ) angewiesen.

    Ich versuche überall wo es aus meiner Sicht Sinn macht, schon aus Gründen der Faulheit, STL-Strings zu verwenden. Wenns aber eine performancekritische Ecke ist oder ein Anwendungsfall, wo 0-Zeichen auch mal spontan auftauchen, dann verzichte ich leichten Herzens auf den STL-String.

    Wenn du ein char-Array wegen Nullzeichen vorziehst, dann hast du std::string nicht verstanden (der hervorragend mit Nullzeichen klarkommt). Wenn du ein dynamisches char-Array einem std::string vorziehst, insbesondere aus Performancegründen, dann machst du etwas falsch, denn std::string ist ein dynamisches char-Array und somit zumindest nicht langsamer (eventuell ein bisschen speicherfressender), im Optimalfall sogar schneller (weil die Entwickler Zeit hatten für Optimierungen (kurze Strings), die du niemals selber implementieren würdest).

    Weitere Vorteile: Die im Thread genannten Gefahren entfallen, ebenso mögliche Fehler der dynamischen Speicherverwaltung
    Weitere Nachteile: Auch mit std::string kann man Mist machen, insbesondere unnötige Kopien. Aber das sind eher Anfängerfehler. Bereichsprüfungsfehler und Fehler bei manueller Speicherverwaltung machen auch erfahrene Programmierer noch.



  • Mir ist schon klar dass das geht, aber wenn ich mit 0-Zeichen arbeite, also z.B. mit Blöcken von Binärdaten, oder irgendwelchen Messages, die aus dem Socket rausfallen oder dort einwandern, dann arbeite ich viel mit den mem-Funktionen und da halte ich mich weitestgehend von den STL-Strings fern. Ist vermutlich eher eine Frage der persönlichen Vorliebe.

    Wenn man sich nicht den "C++-Only"-Dogmen unterwirft, kann man da relativ frei agieren.



  • SeppJ schrieb:

    (weil die Entwickler Zeit hatten für Optimierungen (kurze Strings), die du niemals selber implementieren würdest).

    Es gibt nur eine Implementation von std::string ?

    Also ich würde meine Hand nicht für jede STL Implementation eines Compileranbieters ins Feuer legen...

    Im Zweifelsfalle müsste man vor der Benutzung eines Compilers erstmal alle mitgelieferten Quellcodes studieren, wenn man nicht blind vertrauen will.

    Und ich persönlich traue einem Compiler nur soweit wie ich ihn werfen kann. Das Problem: Compiler kann man nicht werfen 😃


  • Mod

    It0101 schrieb:

    Wenn man sich nicht den "C++-Only"-Dogmen unterwirft, kann man da relativ frei agieren.

    Das ist nicht dogmatisch. Wenn du einerseits (richtig) argumentierst, dass schlampiger Code die Ursache für Sicherheitsprobleme ist, dann ist es verwunderlich, dass du Verfahren propagierst, die eben diese Schlampigkeit provozieren, obwohl doch Alternativen zur Verfügung stehen, die genau das gleiche machen, dabei aber (kostenlos!) vor vielen Fehlern schützen. std::string mag ein überladenes Interface haben (die STL-Container sind da viel besser), aber er ist nicht inhärent langsamer als ein ge-malloc-tes char-Array.

    Bei den IO-Streams kann man auch gute Gründe (insbesondere Performance) gegen ihre Benutzung finden. Dort ist dogmatisches Festhalten viel verbreiteter. Bei der Ablehnung der STL-Container habe ich hingegen eher das Gefühl, dass hier oft ehemalige C-Programmierer kategorisch alles ablehnen, was C++ ist, weil C++ von sich aus langsamer sein muss als C, ohne sich mal mit der Technik dahinter zu beschäftigen oder gar zu vergleichen.



  • SeppJ schrieb:

    dass du Verfahren propagierst, die eben diese Schlampigkeit provozieren, obwohl doch Alternativen zur Verfügung stehen

    Wie ich schon sagte: Wenn Alternativen zur Verfügung stehen, die in dem speziellen Fall besser/praktischer/handlicher sind, dann ist nur jedem anzuraten, diese auch zu verwenden.

    Z.B. im Umgang mit Blöcken von Binärdaten halte ich die STL für unbrauchbar. Es gibt zwar hier Freunde von vector<char> aber ich persönlich halte davon nix. vector<char> mag evtl. sogar an die Performance normaler char* Blöcke herankommen, aber es trifft einfach nicht meinen Geschmack. Es ist aus meiner Sicht sogar Ausdruck der absolut zwanghaften und kranken Mentalität die sich ausgebreitet hat, die STL um jeden Preis zu nutzen. Sieht man hier im C++-Forum relativ häufig. Ich behalte mir immer Alternativen vor und lasse mir so genug Freiraum in der Wahl meiner Waffen.
    Fällt aber vermutlich ebenfalls in die Kategorie des persönlichen Geschmacks, auch wenn natürlich, wie du zurecht feststellst, das Fehlerrisiko bei char* höher ist als bei z.b. vector<char> oder String.


  • Mod

    Wenn dein Argument persönlicher Geschmack ist, dann ist das kein überzeugendes Argument. Besonders wenn du das was nicht deinen Geschmack trifft auch noch als krankhaft abstempelst. Was soll denn an vector<char> für Blöcke auszusetzen sein, außer, dass du persönlich es nicht magst?



  • So ich bin mal über meinen Schatten gesprungen 🙂

    Hier mal die STL String-Version vs. die char* Version:

    unsigned iterationen = 10000000;
    {
        // Stringverkettung mit strncpy
        long t1 = GetTickCount();
    
        const char *TestString = "abcedfghijklmnopqrstuvwxyz";
        unsigned Len = (unsigned)strlen( TestString );
        char *Buffer = new char[ iterationen * sizeof( char ) * Len ];
        unsigned Size = 0;
        for ( unsigned i = 0; i < iterationen; ++i )
        {
            strncpy( Buffer + Size, TestString , Len );
            Size += Len;
        }
    
        long t2 = GetTickCount();
        delete [] Buffer;
        std::cout << t2 - t1 << std::endl;
    }
    {
        // Stringverkettung mit STL-Strings
        long t1 = GetTickCount();
    
        std::string S1( "abcedfghijklmnopqrstuvwxyz" );
        std::string Kette = "";
        Kette.reserve( iterationen * S1.length() );
        for ( unsigned i = 0; i < iterationen; ++i )
            Kette += S1;
    
        long t2 = GetTickCount();
        std::cout << t2 - t1 << std::endl;
    }
    

    Kann mir bitte jemand sagen, was hier an der STL-String-Variante noch nicht optimal programmiert ist? Ich habe reserve genutzt, da mir ja die Gesamtlänge des Strings vorher bekannt ist. Somit müsste eigentlich die STL-Variante fast genauso schnell sein wie die char*-Variante. Was hab ich falsch gemacht, dass die STL-Variante so spürbar langsamer ist?

    Anmerkung:
    gebaut mit Codeblocks 10.05, Compiler: GCC, mit Optimierungsflag -O3

    Laufzeit der strncpy-Variante: 400ms
    Laufzeit der STL-String-Variante: 500ms



  • SeppJ schrieb:

    Wenn dein Argument persönlicher Geschmack ist, dann ist das kein überzeugendes Argument. Besonders wenn du das was nicht deinen Geschmack trifft auch noch als krankhaft abstempelst. Was soll denn an vector<char> für Blöcke auszusetzen sein, außer, dass du persönlich es nicht magst?

    Das ist meine persönliche Ansicht für zwanghafte char*-Verweigerung 😉
    Rein Subjektiv. Ohne Anspruch auf realitätsnähe 😃

    Komm Sepp. Ich bin lernfähig. Es ist noch nicht zu spät 😉



  • Mit VS2012 und /O2 sogar noch schlimmer (203ms zu 141ms).

    EDIT: Mit VS2010 ist es interessanterweise performanter (std::string 172ms, strncpy 140ms).



  • It0101 schrieb:

    Kann mir bitte jemand sagen, was hier an der STL-String-Variante noch nicht optimal programmiert ist?

    Copy-On-Write?



  • Ich schaue mir das hier auch gerade an...
    In der VC++2012-STL wird kein COW oder so betrieben. Es gibt natürlich im Gegensatz zu Deiner strncpy-Variante pro Konkatenation Bereichsüberprüfungen, wobei ich aber auch denke, dass in diesem Fall die Branch-Prediction der CPU eigentlich nahe 100% erreichen sollte.
    Von daher bin ich mir nicht sicher, was da genau die Kosten hochtreibt. Zum einen testet std::string::append, ob die Quelle im eigenenen Bereich liegt (ist ja klar, wenn der Puffer umgeschoben wird, wäre beim Vergrößern auf einmal die Quelle weg) und fügt außerdem noch eine Nullterminierung an (aber auch wenn man reserve einen größer ansetzt, tut sich nicht viel, obwohl in Deinem Beispiel das letzte append sozusagen eine komplette Kopier erzeugen könnte). Laut Performance-Log ist die letztere Aktion recht kostspielig.

    Und vor allem: hier tritt eine C++-Funktion mit Bereichsüberprüfung und Nullterminierung gegen eine komplett in Assembler implementierte strncpy an 😉

    Edit: Ich habe mal den Quell-String verlängert auf ca. das dreifache und auf einmal sind beide gleichschnell. Wundert mich etwas, da ja immernoch die Tests da sind, wenn auch weniger.



  • Iteratorprüfungen sind auch aus?



  • Soweit ich weiß, sind die in VS2012 standardmäßig aus, im Release-Build? Habe nichts aktiv abgestellt... aber siehe mal meinen letzten Edit... Ich frage mich, ob es mit dieser Stringlänge im Originalbeispiel etwas auf sich hat.



  • volkard schrieb:

    It0101 schrieb:

    Kann mir bitte jemand sagen, was hier an der STL-String-Variante noch nicht optimal programmiert ist?

    Copy-On-Write?

    COW bei Strings kostet oft mehr als es bringt 😉



  • Also, ich habe das mal über die Stringlänge ausgetestet:

    #include <string>
    #include <Windows.h>
    #include <iostream>
    
    #pragma warning (disable:4996)
    
    void test_string( const char* src, unsigned int len, unsigned int iterations ) {
    	std::string Kette;
    	Kette.reserve( iterations * len + 5 );
    	for ( unsigned i = 0; i < iterations; ++i ) {
    		Kette.append( src, len );
    	}
    }
    
    void test_raw( const char* src, unsigned int len, unsigned int iterations ) {
    	char *Buffer = new char[ iterations * sizeof( char ) * len + 1 ];
    	unsigned Size = 0;
    	for ( unsigned i = 0; i < iterations; ++i )
    	{
    		strncpy( Buffer + Size, src , len );
    		Size += len;
    	}
    
    	delete[] Buffer;
    }
    
    int _tmain(int argc, _TCHAR* argv[])
    {	
    	const char *TestString = "abcedfghijklmnopqrstuvwxyzafgaewgaewgasdgweghaerhhadsgkljawekljthklwegtkuwegtrkewiugtrkweugrawugrkajwegrawkjehgrhjegwegwegweg";
    	unsigned int Len = strlen( TestString );
    	long t1, t2;
    
    	for( unsigned int i=1; i<Len; ++i ) {
    		unsigned int iterationen = 500000000/i;
    		t1 = GetTickCount();
    		test_string( TestString, i, iterationen );
    		test_string( TestString, i, iterationen );
    		test_string( TestString, i, iterationen );
    		t1 = GetTickCount() - t1;
    
    		t2 = GetTickCount();
    		test_raw( TestString, i, iterationen );
    		test_raw( TestString, i, iterationen );
    		test_raw( TestString, i, iterationen );
    		t2 = GetTickCount()-t2;
    
    		std::cout << i << ": " << double(t1)/t2*100-100 << "%\n";
    	}
    
    	return 0;
    }
    

    Ab einer Kopiergröße von 80 Zeichen ist tendenziell append schneller als strncpy bei mir. Da die Tests ja nun immernoch da sind, muss das memcpy, das append benutzt wohl dort ein bissl schneller sein als das strncpy.
    Für kurze Zeichenketten ist append aber irgendwie schon indiskutabel langsamer...

    Edit: Das einzige, was VC++2012 bei mir macht, ist Small-String-Optimierung (bis 16 Zeichen).

    Edit #2: Ich habe mal testweise strncpy durch memcpy ersetzt und dann sieht es so aus, dass append schneller bei kürzeren strings "besser" dasteht (also sich schneller dem memcpy annähert) aber dann halt auch abflacht und immer so 10-20% langsamer ist... Ich gewinne den Eindruck, dass man bei kurzen Kopierereien strncpy benutzen sollte und bei größeren Blöcken memcpy...
    Aber 10-20% overhead durch die Checks bei Strings 100+ in den sweet-cases, für die ein Kodierer ja manchmal gezielt sorgt, ist schon irgendwie überraschend.



  • Decimad schrieb:

    Ich gewinne den Eindruck, dass man bei kurzen Kopierereien strncpy benutzen sollte und bei größeren Blöcken memcpy...

    Kann gut sein, daß strncpy für kurze Bereiche optimiert ist, denn wo nimmt man es? Bei kurzen Usereingaben.
    Und daß memcpy für lange Sachen optimiert ist, denn wo nimmt man es? Beim Kopieren von 4096 großen Blöcken.

    So wie Compiler vorstellbar sind, die new/delete für kleine (C++-Objekte, Polymorphie) und malloc/free für große Bereiche (Buffer, C-Style) lieber haben.



  • Aber dann frage ich mich, warum append (denn wessen member ist es?) memcpy benutzt Oo

    Edit: Kannst du das mal auf deinem fetten i7 durchlaufen lassen? Vielleicht spinnt ja auch mein Rechner 😃



  • Decimad schrieb:

    Aber dann frage ich mich, warum append (denn wessen member ist es?) memcpy benutzt Oo

    basic_string&
          append(const _CharT* __s)
          {
     ;
     return this->append(__s, traits_type::length(__s));
          }
    

    Im Falle von append("bla")
    Hmm. Wohl eine Folge von den traits? Erst strlen machen, um dann memcpy machen zu dürfen, ist natürlich häßlich.

    Bei append(string) muß natürlich memcpy genommen werden, weil der string ja auch \0 enthalten kann, wo er mag.

    Und es ist vermutlich irrelevant. Wenn man auf den Daten dann irgendwas berechnet, dürfte die Zeit fürs Reinkopieren nicht mehr auffallen.

    Decimad schrieb:

    Edit: Kannst du dass mal auf deinem fetten i7 durchlaufen lassen? Vielleicht spinnt ja auch mein Rechner 😃

    Unter Windows hab ich nur so ein Visual Studio 2012 und weiß nicht, wie ich da Konsole-Anwendungen anlegen kann.
    Unter Linux hab ich wohl COW-Strings und kann bis zu 10-mal langsamer sein.

    const char TestString[] = "abcedfghijklmnopqrstuvwxyzabcedfghijklmnopqrstuvwxyzabcedfghijklmnopqrstuvwxyz";
    
            Kette.append(begin(TestString),end(TestString)-1);
    

    ist ca 83% lahmer als strncpy.

    const string TestString = "abcedfghijklmnopqrstuvwxyzabcedfghijklmnopqrstuvwxyzabcedfghijklmnopqrstuvwxyz";
    
            Kette.append(TestString);
    

    ist ca 31% lahmer.


Anmelden zum Antworten