Sicherheit für Dummies: Was darf man nicht machen



  • Hallo, ich habe mich am Wochenende zum allerersten mal intensiv mit reinem C (im Gegensatz zu C++) befasst. Man liest ja immer wieder, das Programme in C ein Sicherheitsproblem sind - aber so ganz nachvollziehen konnte ich das nicht.

    Ist es gefährlich sich auf strlen zu verlassen? Wenn ja, wie soll ich dann die Länge eines Strings feststellen - ich kann ja nicht fragen "wo fängt der reservierte Speicherblock in dem der String liegt an und wo hört er auf"?

    Muss ich bei Funktionseintritt von rein internen Funktionen, die von außen nicht aufgerufen werden, wirklich immer alle Parameter prüfen? Also, zum Beispiel, ob ein int vielleicht negativ ist, obwohl das vom Programmfluss her nicht sein kann.

    Kann man sich auf die Ergebnisse von Systemaufrufen verlassen? Also wenn das Feld d_namlen in einem struct dirent sagt "5", dass der Name auch wirklich fünf Zeichen lang ist?

    Ein paar Codebeispiele, so wie sie im Programm stehen:

    // Teil einer rekursiven Funktion getDirSize, berechnet rekursiv die Größe eines
    // Verzeichnisses, und nimmt für Verzeichnisse selbst eine Größe von einem Block
    // an
    name_len = strlen(name);
    if (current->d_name[0] == '.' && (current->d_namlen == 1 || ( current->d_namlen == 2 && current->d_name[1] == '.'))) continue;
    nextname = malloc(name_len + current->d_namlen + 2);
    memcpy(nextname, name, name_len);
    memcpy(nextname + name_len + 1, current->d_name, current->d_namlen);
    *(nextname + name_len) = '/';
    *(nextname + name_len + current->d_namlen + 1) = '\0';
    result += getDirSize(nextname, block_size);
    free(nextname);
    
    // Sowas ähnliches, da war ich aber fauler und habe asprintf benutzt. Gehört
    // zu einer Funktion die ähnlich wie readdir, ein Verzeichnis ausgibt,
    // allerdings rekursiv in Post-Order
    
    // if directory found
    else if (nextentry->d_type == DT_DIR && (nextentry->d_name[0] != '.' || (nextentry->d_namlen != 1 && (nextentry->d_namlen != 2 || nextentry->d_name[1] != '.')))) {
    
    	asprintf(&newname, "%s/%s", stack[depth]->name, nextentry->d_name);
    	newrecord = malloc(sizeof(struct generatorRecord));
    	newrecord->state = 0;
    	newrecord->dir = opendir(newname);
    	newrecord->name = newname;
    	if (newrecord->dir == NULL) {
    		free(newrecord);
    		free(newname);
    		continue;
    	}
    	stack[++depth] = newrecord;
    }
    


  • Man sagt, dass C gefährlich ist, da sich Sicherheitslücken durch fehlerhafte Programmierung öffnen können. Das ist zwar bei anderen Programmiersprachen auch möglich, aber je nach Sprache ist das einfacher bzw. schwieriger.

    Dabei handelt es sich bei den Fehlern in C häufig um Buffer Overflows und Buffer Underflows.

    Das passiert dann, wenn man einen Buffer hat, der z.B. 64 Zeichen halten kann, aber man versehentlich 65 Zeichen einliest.

    Außerdem muss man in C sehr viel mit Zeigern rumhantieren und sehr genau aufpassen, dass man den Speicher korrekt freigibt.

    Oder schau dir die Funktion sprintf an. Was passiert wenn der Buffer zu klein ist? Die Funktion stellt keine Mechanismen zur Verfügung, dass das nicht passiert.

    Das wird insbesondere dann kritisch, wenn es sich um Eingaben des Benutzers handelt. Hier muss man immer davon ausgehen, dass diese Schaden anrichten können und muss genausten aufpassen.

    Ansonsten musst du dir keine Sorgen darum machen, dass deine Funktion plötzlich einen Parameter erhält, der laut Programmfluss nicht sein kann. Vorausgesetzt du produzierst kein undefiniertes Verhalten durch Sachen wie fflush(stdin), etc.
    Da muss man einfach aufpassen und zur Not die Dokumentation ansehen.

    Natürlich kann jemand dein Programm mit einem Hexeditor manipulieren, aber bei einem Server der sicher sein soll hat man dann eh verloren. Dazu muss der Angreifer schon Zugriff auf das System haben und dann ist irgendwo anders der Fehler. (Serverraum nicht abgeschlossen, schlechtes Passwort für root, etc......)



  • Ist es gefährlich sich auf strlen zu verlassen?

    Nur, wenn du die Nullterminierung vergessen hast.
    Lösung: Nullterminierung nicht vergessen.

    Muss ich bei Funktionseintritt von rein internen Funktionen, die von außen nicht aufgerufen werden, wirklich immer alle Parameter prüfen?

    Natürlich nicht. Ein assert kann im Debugmodus aber trotzdem nie was schaden.

    Kann man sich auf die Ergebnisse von Systemaufrufen verlassen? Also wenn das Feld d_namlen in einem struct dirent sagt "5", dass der Name auch wirklich fünf Zeichen lang ist?

    Wie sollte auch nur das simpelste Programm funktionieren, wenn man sich auf so etwas nicht verlassen könnte?


Anmelden zum Antworten