Ungeprüfter Rückgabewert

Beschreibung

Ungeprüfter Rückgabewert tritt auf, wenn Software den Rückgabewert einer Methode oder Funktion nicht prüft, was das Erkennen unerwarteter Zustände und Bedingungen verhindern kann. Viele sicherheitskritische Funktionen signalisieren Fehler durch Rückgabewerte, und das Versäumnis, diese zu prüfen, kann zu Sicherheitsschwachstellen führen. Beispiele umfassen Speicherallokationsfunktionen, die bei Fehlschlag NULL zurückgeben, Authentifizierungsfunktionen, die Fehlercodes zurückgeben, Dateioperationen, die stillschweigend fehlschlagen, und kryptografische Funktionen, die Fehler signalisieren. Das Ignorieren dieser Rückgabewerte kann Nullzeiger-Dereferenzierungen, Verwendung nicht-initialisierter Daten oder Sicherheitsumgehungen verursachen.

Risiko

Das Ignorieren von Rückgabewerten sicherheitskritischer Funktionen erzeugt erhebliche Risiken. Fehlgeschlagene Speicherallokationen führen zu Nullzeiger-Dereferenzierungen und Abstürzen. Ignorierte Authentifizierungsfehler können unbefugten Zugriff ermöglichen. Ungeprüfte Dateioperationen können auf falschen oder fehlenden Dateien operieren. Fehler kryptografischer Funktionen können zur Verwendung schwacher oder keiner Verschlüsselung führen. Datenbankoperationen können stillschweigend fehlschlagen und die Datenintegrität beschädigen. Die Schwere hängt davon ab, welcher Rückgabewert welcher Funktion ignoriert wird und was passiert, wenn die implizite Fehlerbedingung eintritt.

Lösung

Prüfen Sie immer Rückgabewerte von Funktionen, die fehlschlagen können, besonders sicherheitskritische Funktionen. Behandeln Sie Fehler angemessen—loggen, wiederherstellen oder sicher fehlschlagen. Verwenden Sie Compiler-Warnungen zur Erkennung ungeprüfter Rückgabewerte (-Wunused-result in GCC). Verwenden Sie das [[nodiscard]]-Attribut in C++17 oder attribute((warn_unused_result)) in GCC. Erwägen Sie die Verwendung von Result/Either-Typen in Sprachen, die sie unterstützen. Implementieren Sie konsistente Fehlerbehandlungsmuster in der gesamten Codebasis. Verwenden Sie statische Analysetools zur Erkennung ungeprüfter Rückgabewerte.

Häufige Auswirkungen

AuswirkungDetails
VerfügbarkeitBereich: Denial of Service

Ungeprüfte Allokationsfehler führen zu Nullzeiger-Dereferenzierungen und Abstürzen.
IntegritätBereich: Datenbeschädigung

Ungeprüfte Datei- oder Datenbankoperationsfehler können Daten beschädigen.
ZugriffskontrolleBereich: Sicherheitsumgehung

Das Ignorieren von Authentifizierungs- oder Autorisierungsrückgabewerten kann unbefugten Zugriff ermöglichen.

Beispielcode + Lösungscode

Anfälliger Code

// ANFÄLLIG: Ungeprüfter malloc-Rückgabewert
void process_data(size_t size) {
    char *buffer = malloc(size);
    // Wenn malloc fehlschlägt, ist buffer NULL - Absturz!
    memset(buffer, 0, size);
    // ... buffer verwenden ...
    free(buffer);
}

// ANFÄLLIG: Ungeprüfte Dateioperationen
void read_config(const char *filename) {
    FILE *fp = fopen(filename, "r");
    // Wenn fopen fehlschlägt, ist fp NULL
    char line[256];
    while (fgets(line, sizeof(line), fp)) {
        process_line(line);
    }
    fclose(fp);  // Absturz wenn fp NULL ist
}

// ANFÄLLIG: Ungeprüftes Authentifizierungsergebnis
void authenticate_user(const char *username, const char *password) {
    int result = verify_credentials(username, password);
    // Ergebnis ignoriert - Benutzer immer authentifiziert!
    grant_access(username);
}

// ANFÄLLIG: Ungeprüftes setuid
void drop_privileges(uid_t new_uid) {
    setuid(new_uid);  // Kann fehlschlagen aber nicht geprüft
    // Läuft noch als root wenn setuid fehlgeschlagen!
    execute_user_code();
}
// ANFÄLLIG: Ungeprüftes chdir
void process_in_directory(const char *dir) {
    chdir(dir);  // Kann fehlschlagen
    // Operiert im falschen Verzeichnis!
    unlink("temp.dat");  // Löscht falsche Datei
}

// ANFÄLLIG: Ungeprüfte kryptografische Operationen
void encrypt_data(const char *key, char *data, size_t len) {
    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
    EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key, iv);
    // Nicht geprüft ob Verschlüsselung erfolgreich war
    int outlen;
    EVP_EncryptUpdate(ctx, data, &outlen, data, len);
    EVP_EncryptFinal_ex(ctx, data + outlen, &outlen);
    // Daten können unverschlüsselt oder beschädigt sein!
}

// ANFÄLLIG: scanf-Rückgabewert ignoriert
void read_input() {
    int value;
    scanf("%d", &value);  // Kann Parsing fehlschlagen
    // value ist uninitialisiert wenn scanf fehlgeschlagen!
    process_value(value);
}
// ANFÄLLIG: Ignorierter Rückgabewert in Java
public void deleteFile(String path) {
    File file = new File(path);
    file.delete();  // Gibt boolean zurück, ignoriert!
    // Annahme Datei ist gelöscht obwohl sie noch existieren kann
    createNewFile(path);
}

// ANFÄLLIG: Ungeprüfte I/O-Operationen
public void writeData(OutputStream out, byte[] data) throws IOException {
    out.write(data);  // Kann nicht alle Bytes schreiben
    // Kurzes Schreiben nicht erkannt!
}

// ANFÄLLIG: Ignorieren der Security-Manager-Prüfung
public void accessResource(String resource) {
    SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        sm.checkRead(resource);  // Wirft bei Fehler, aber Muster oft missbraucht
    }
    // Code fährt fort...
}

Korrigierter Code

// SICHER: malloc-Rückgabewert prüfen
void process_data_safe(size_t size) {
    char *buffer = malloc(size);
    if (buffer == NULL) {
        fprintf(stderr, "Speicherallokation fehlgeschlagen\n");
        return;  // Oder Fehler angemessen behandeln
    }
    memset(buffer, 0, size);
    // ... buffer verwenden ...
    free(buffer);
}

// SICHER: Alle Dateioperations-Rückgaben prüfen
void read_config_safe(const char *filename) {
    FILE *fp = fopen(filename, "r");
    if (fp == NULL) {
        perror("Öffnen der Konfigurationsdatei fehlgeschlagen");
        return;
    }

    char line[256];
    while (fgets(line, sizeof(line), fp) != NULL) {
        process_line(line);
    }

    if (ferror(fp)) {
        perror("Fehler beim Lesen der Konfigurationsdatei");
    }

    fclose(fp);
}

// SICHER: Authentifizierungsergebnis prüfen
int authenticate_user_safe(const char *username, const char *password) {
    int result = verify_credentials(username, password);
    if (result != AUTH_SUCCESS) {
        log_auth_failure(username);
        return AUTH_FAILED;
    }
    grant_access(username);
    return AUTH_SUCCESS;
}

// SICHER: Privilegienoperationen prüfen
int drop_privileges_safe(uid_t new_uid) {
    if (setuid(new_uid) != 0) {
        perror("Ablegen der Privilegien fehlgeschlagen");
        // Kritischer Fehler - muss beenden
        exit(EXIT_FAILURE);
    }
    // Läuft jetzt sicher als new_uid
    return execute_user_code();
}
// SICHER: chdir-Ergebnis prüfen
int process_in_directory_safe(const char *dir) {
    if (chdir(dir) != 0) {
        perror("Verzeichniswechsel fehlgeschlagen");
        return -1;
    }

    if (unlink("temp.dat") != 0 && errno != ENOENT) {
        perror("Löschen der temporären Datei fehlgeschlagen");
        return -1;
    }

    return 0;
}

// SICHER: Alle kryptografischen Operationen prüfen
int encrypt_data_safe(const unsigned char *key, unsigned char *data,
                      size_t len, unsigned char *out, size_t *out_len) {
    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
    if (ctx == NULL) {
        return -1;
    }

    if (EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key, iv) != 1) {
        EVP_CIPHER_CTX_free(ctx);
        return -1;
    }

    int outlen;
    if (EVP_EncryptUpdate(ctx, out, &outlen, data, len) != 1) {
        EVP_CIPHER_CTX_free(ctx);
        return -1;
    }
    *out_len = outlen;

    int final_len;
    if (EVP_EncryptFinal_ex(ctx, out + outlen, &final_len) != 1) {
        EVP_CIPHER_CTX_free(ctx);
        return -1;
    }
    *out_len += final_len;

    EVP_CIPHER_CTX_free(ctx);
    return 0;  // Erfolg
}

// SICHER: scanf-Rückgabewert prüfen
int read_input_safe() {
    int value;
    if (scanf("%d", &value) != 1) {
        fprintf(stderr, "Ungültige Eingabe\n");
        return -1;
    }
    return process_value(value);
}

// GCC-Attribut verwenden um Prüfung zu erzwingen
__attribute__((warn_unused_result))
int critical_operation(void);

// C++17 [[nodiscard]]-Attribut
[[nodiscard]] int must_check_result();
// SICHER: delete-Rückgabewert prüfen
public boolean deleteFile(String path) throws IOException {
    File file = new File(path);
    if (!file.delete()) {
        if (file.exists()) {
            throw new IOException("Löschen der Datei fehlgeschlagen: " + path);
        }
        // Datei existierte nicht - je nach Anforderungen OK
    }
    return true;
}

// SICHER: Files-API mit Exceptions verwenden
public void deleteFileSafe(Path path) throws IOException {
    // Files.delete wirft IOException bei Fehler
    Files.delete(path);
}

// SICHER: Geschriebene Bytes prüfen
public void writeDataSafe(OutputStream out, byte[] data) throws IOException {
    // DataOutputStream für garantiertes Schreiben verwenden
    DataOutputStream dos = new DataOutputStream(out);
    dos.write(data);
    dos.flush();

    // Oder explizit mit nio prüfen
    // ByteBuffer buffer = ByteBuffer.wrap(data);
    // while (buffer.hasRemaining()) {
    //     channel.write(buffer);
    // }
}

// SICHER: Optional für nullable Rückgaben verwenden
public Optional<User> findUser(String id) {
    User user = userRepository.find(id);
    return Optional.ofNullable(user);
}

// Aufrufer muss behandeln:
// findUser(id).orElseThrow(() -> new UserNotFoundException(id));

Ausgenutzt in der Praxis

OpenSSL Heartbleed Kontext

Während Heartbleed (CVE-2014-0160) ein Bounds-Check-Problem war, stammen viele verwandte Schwachstellen in kryptografischen Bibliotheken von ungeprüften Rückgabewerten, die zur Verwendung fehlgeschlagener Operationen führen.

Linux-Kernel Privilegieneskalation

Mehrere Privilegieneskalations-Schwachstellen im Linux-Kernel resultierten aus ungeprüften Rückgabewerten bei setuid/setgid-Operationen, wodurch Prozesse erhöhte Privilegien behalten könnten.

glibc realpath() Schwachstelle (2018)

CVE-2018-1000001 in glibc beinhaltete unsachgemäße Behandlung von Rückgabewerten bei der Pfadkanonisierung, was zu Buffer-Underflow und potenzieller Codeausführung führte.


Tools zum Testen/Ausnutzen

  • GCC/Clang Warnungen — -Wunused-result Flag zur Erkennung ungeprüfter Rückgaben.

  • Coverity — Statische Analyse zur Erkennung ungeprüfter Rückgaben.

  • PVS-Studio — Statischer Analyzer mit Rückgabewertprüfung.

  • Cppcheck — Open-Source C/C++ statischer Analyzer.


CVE-Beispiele


Referenzen

  1. MITRE. "CWE-252: Unchecked Return Value." https://cwe.mitre.org/data/definitions/252.html

  2. CERT. "ERR33-C: Detect and handle standard library errors." https://wiki.sei.cmu.edu/confluence/display/c/ERR33-C.+Detect+and+handle+standard+library+errors