Undefiniertes Verhalten für API-Eingaben

Beschreibung

Undefiniertes Verhalten für API-Eingaben ist eine Schwachstelle, bei der das Verhalten einer Funktion undefiniert ist, es sei denn, ihr Kontrollparameter ist auf einen bestimmten Wert gesetzt. Diese Schwäche tritt auf, wenn eine API-Funktion aufgerufen wird, ohne dass die erforderlichen Parameter in ihrem erwarteten Zustand bereitgestellt werden, was zu unvorhersehbarem Verhalten führt. Viele Bibliotheksfunktionen haben spezifische Vorbedingungen, die für definiertes Verhalten erfüllt sein müssen, und die Verletzung dieser Vorbedingungen führt zu undefinierten Ergebnissen, die je nach Implementierung variieren können.

Risiko

Das Aufrufen von APIs mit Eingaben, die undefiniertes Verhalten auslösen, erzeugt unvorhersehbare Sicherheitsbedingungen. Das Programm kann abstürzen, stillschweigend falsche Ergebnisse produzieren oder ausnutzbare Speicherkorruption aufweisen. Angreifer können undefiniertes Verhalten potenziell nutzen, um beliebige Codeausführung zu erreichen, Sicherheitsprüfungen zu umgehen oder Denial of Service zu verursachen. Das Risiko ist verstärkt, weil undefiniertes Verhalten nicht konsistent sein muss - es kann während des Testens funktionieren aber in der Produktion versagen, oder auf einigen Plattformen ausnutzbar sein aber auf anderen nicht.

Lösung

Überprüfen Sie die API-Dokumentation sorgfältig, um alle Vorbedingungen und Anforderungen zu verstehen. Validieren Sie alle Eingaben, bevor Sie sie an APIs mit strengen Anforderungen übergeben. Verwenden Sie Wrapper-Funktionen, die Vorbedingungen erzwingen und definierte Fehlerbehandlung bieten. Bevorzugen Sie APIs mit wohldefiniertem Verhalten für alle möglichen Eingaben. Wenn Vorbedingungen nicht garantiert werden können, implementieren Sie Laufzeitprüfungen, die Verletzungen erkennen, bevor die zugrunde liegende API aufgerufen wird. Verwenden Sie statische Analysewerkzeuge, um potenzielle Aufrufe mit undefiniertem Verhalten zu erkennen.

Häufige Auswirkungen

AuswirkungDetails
AndereUmfang: Ändere

Qualitätsverschlechterung - Code kann sich unvorhersehbar verhalten, wenn APIs mit Eingaben aufgerufen werden, die undefiniertes Verhalten auslösen.
AndereUmfang: Ändere

Variiert nach Kontext - Sicherheitsauswirkungen hängen davon ab, wie sich undefiniertes Verhalten manifestiert, einschließlich potenzieller Abstürze, Datenkorruption oder ausnutzbarer Bedingungen.

Beispielcode

Anfälliger Code

// Anfällig: Aufruf von Funktionen mit undefiniertem Verhalten für bestimmte Eingaben
#include <stdlib.h>
#include <string.h>
#include <math.h>

void vulnerable_abs_usage(int value) {
    // Anfällig: abs(INT_MIN) ist undefiniertes Verhalten im Zweierkomplementarithmetic
    // INT_MIN kann in Zweierkomplementdarstellung nicht negiert werden
    int positive = abs(value);  // UB wenn value == INT_MIN

    printf("Absolutwert: %d\n", positive);
}

void vulnerable_memcpy_overlap(char *buffer, size_t offset) {
    // Anfällig: memcpy mit überlappenden Regionen ist undefiniert
    char *src = buffer;
    char *dest = buffer + offset;

    // Wenn src und dest überlappen, ist das Verhalten undefiniert
    memcpy(dest, src, 100);  // UB wenn offset < 100
}

void vulnerable_shift_operations(int value, int shift_amount) {
    // Anfällig: Verschieben um negativen Betrag oder >= Bitbreite ist UB
    int result = value << shift_amount;  // UB wenn shift_amount >= 32 oder < 0

    // Anfällig: Links-Verschieben einer negativen Zahl ist UB (vor C++20)
    int negative_shift = (-1) << 2;  // UB in C und älterem C++
}

void vulnerable_division(int numerator, int denominator) {
    // Anfällig: Division durch Null ist undefiniert
    int result = numerator / denominator;  // UB wenn denominator == 0

    // Anfällig: INT_MIN / -1 überläuft (undefiniert)
    if (numerator == INT_MIN && denominator == -1) {
        // Immer noch UB selbst nach der Prüfung weil Ergebnis zuerst berechnet wird
    }
}

// Linux Standard Base spezifisches Beispiel
void vulnerable_lsb_call() {
    // Anfällig: __xmknod erfordert Versionsparameter == 1
    // Aufruf mit anderen Werten hat undefiniertes Verhalten
    __xmknod(0, path, mode, dev);  // UB: Version sollte 1 sein

    // Anfällig: Wide-Character-Funktionen erfordern spezifisches Gruppenargument
    // Einige Funktionen erfordern Gruppenargument == 2
    __wcscpy_chk(dest, src, 0);  // UB: sollte spezifischer Wert sein
}
# Anfällig: Python-APIs mit undefiniertem Verhalten für Grenzfälle
import re
import math

def vulnerable_regex_usage(pattern):
    # Anfällig: re.compile mit ungültigem Muster
    # Verhalten variiert: kann Exception werfen, None zurückgeben oder anderes
    try:
        compiled = re.compile(pattern)
    except re.error:
        pass
    # Verwendung von compiled außerhalb des try-Blocks: kann undefiniert sein

def vulnerable_math_operations(value):
    # Anfällig: math.sqrt mit negativer Zahl
    # Definiert um ValueError zu werfen, aber...
    result = math.sqrt(value)  # Wirft wenn negativ

    # Anfällig: math.log mit Null oder negativ
    log_result = math.log(value)  # Domain-Fehler

    return result, log_result

def vulnerable_list_operations(lst, index):
    # Anfällig: Zugriff auf Index der möglicherweise nicht existiert
    # Python wirft IndexError, aber Abfangen und Weitermachen
    # kann Programm in inkonsistentem Zustand lassen
    try:
        value = lst[index]
    except IndexError:
        value = None  # Hängt jetzt davon ab ob Index gültig war

    # Späterer Code nimmt an dass value erwarteten Typ hat
    return value.upper()  # Fehler wenn None
// Anfällig: Java-APIs mit undefiniertem/unspezifiziertem Verhalten
import java.util.concurrent.atomic.*;
import java.util.*;

public class VulnerableAPIUsage {

    public void vulnerableComparator() {
        List<Integer> list = Arrays.asList(3, 1, 2, null);

        // Anfällig: Sortieren mit Null-Elementen
        // Comparator.naturalOrder() behandelt null nicht
        Collections.sort(list);  // NullPointerException oder undefinierte Reihenfolge
    }

    public void vulnerableHashCode() {
        Map<MutableKey, String> map = new HashMap<>();
        MutableKey key = new MutableKey("original");

        map.put(key, "value");

        // Anfällig: Schlüssel nach Einfügen mutieren
        key.setValue("modified");

        // Map ist jetzt in undefiniertem Zustand
        // get(), contains(), remove() können alle fehlschlagen
        String value = map.get(key);  // Kann null oder "value" zurückgeben
    }

    public void vulnerableConcurrentModification() {
        List<String> list = new ArrayList<>();
        list.add("a");
        list.add("b");
        list.add("c");

        // Anfällig: Liste während Iteration modifizieren
        for (String s : list) {
            if (s.equals("b")) {
                list.remove(s);  // ConcurrentModificationException
                // Oder undefiniertes Verhalten in manchen Fällen
            }
        }
    }
}

Korrigierter Code

// Korrigiert: Eingaben vor API-Aufruf validieren
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <limits.h>

int secure_abs(int value) {
    // Korrigiert: Undefinierten Fall explizit behandeln
    if (value == INT_MIN) {
        // INT_MIN kann nicht negiert werden
        // Option 1: INT_MAX zurückgeben (Sättigung)
        return INT_MAX;
        // Option 2: Fehler zurückgeben
        // errno = ERANGE; return -1;
    }
    return abs(value);
}

void secure_memcpy(void *dest, const void *src, size_t count) {
    // Korrigiert: memmove für potenziell überlappende Regionen verwenden
    // memmove behandelt Überlappung korrekt
    memmove(dest, src, count);

    // Oder Überlappung erkennen und geeignete Funktion wählen
}

int secure_shift(int value, int shift_amount) {
    // Korrigiert: Verschiebungsbetrag validieren
    if (shift_amount < 0 || shift_amount >= (int)(sizeof(int) * 8)) {
        return 0;  // Oder Fehler behandeln
    }

    // Korrigiert: Nur nicht-negative Werte links verschieben
    if (value < 0) {
        // Negative Werte explizit behandeln
        unsigned int uval = (unsigned int)value;
        return (int)(uval << shift_amount);
    }

    return value << shift_amount;
}

int secure_division(int numerator, int denominator, int *result) {
    // Korrigiert: Auf alle undefinierten Fälle prüfen
    if (denominator == 0) {
        return -1;  // Fehler: Division durch Null
    }

    if (numerator == INT_MIN && denominator == -1) {
        return -2;  // Fehler: Überlauf
    }

    *result = numerator / denominator;
    return 0;  // Erfolg
}

// Korrigiert: Wrapper für plattformspezifische Funktionen
int secure_mknod(const char *path, mode_t mode, dev_t dev) {
    // Korrigiert: Korrekten Versionsparameter wie von Spezifikation gefordert verwenden
    return __xmknod(1, path, mode, &dev);  // Version = 1
}
# Korrigiert: Eingaben validieren und Grenzfälle behandeln
import re
import math

def secure_regex_compile(pattern):
    """Regex mit ordnungsgemäßer Fehlerbehandlung kompilieren."""
    if not isinstance(pattern, str):
        raise TypeError("Muster muss ein String sein")

    if not pattern:
        raise ValueError("Muster darf nicht leer sein")

    try:
        return re.compile(pattern)
    except re.error as e:
        raise ValueError(f"Ungültiges Regex-Muster: {e}")

def secure_math_operations(value):
    """Mathematische Operationen mit Eingabevalidierung durchführen."""
    # Korrigiert: Vor Aufruf validieren
    if not isinstance(value, (int, float)):
        raise TypeError("Wert muss numerisch sein")

    if value < 0:
        raise ValueError("Kann sqrt/log von negativer Zahl nicht berechnen")

    if value == 0:
        raise ValueError("Kann log von Null nicht berechnen")

    return math.sqrt(value), math.log(value)

def secure_list_access(lst, index, default=None):
    """Sicherer Listenzugriff mit Standardwert."""
    # Korrigiert: Index validieren
    if not isinstance(index, int):
        raise TypeError("Index muss Integer sein")

    # Korrigiert: Grenzprüfung verwenden
    if 0 <= index < len(lst):
        value = lst[index]
    elif -len(lst) <= index < 0:
        value = lst[index]
    else:
        return default

    # Korrigiert: Wert vor Verwendung validieren
    if value is None:
        return default

    return value.upper() if isinstance(value, str) else str(value).upper()
// Korrigiert: Sichere API-Verwendungsmuster
import java.util.*;

public class SecureAPIUsage {

    public void secureSort(List<Integer> list) {
        // Korrigiert: Nulls vor Sortierung entfernen oder behandeln
        list.removeIf(Objects::isNull);

        // Oder null-sicheren Comparator verwenden
        Collections.sort(list, Comparator.nullsLast(Comparator.naturalOrder()));
    }

    // Korrigiert: Unveränderliche Schlüssel für Maps verwenden
    public void secureMapUsage() {
        Map<String, String> map = new HashMap<>();

        // Korrigiert: String-Schlüssel sind unveränderlich
        String key = "original";
        map.put(key, "value");

        // Schlüssel kann nicht mutiert werden, Map bleibt konsistent
        String value = map.get(key);  // Gibt immer "value" zurück
    }

    public void secureIteration(List<String> list) {
        // Korrigiert: Kopie für Iteration erstellen wenn Modifikation nötig
        List<String> copy = new ArrayList<>(list);

        for (String s : copy) {
            if (s.equals("b")) {
                list.remove(s);  // Sicher: Iteration über Kopie
            }
        }

        // Oder Iterator.remove() verwenden
        Iterator<String> iter = list.iterator();
        while (iter.hasNext()) {
            if (iter.next().equals("b")) {
                iter.remove();  // Sicher: verwendet remove des Iterators
            }
        }

        // Oder removeIf verwenden
        list.removeIf(s -> s.equals("b"));
    }
}

CVE-Beispiele

Keine spezifischen CVEs sind in der MITRE-Datenbank für dieses CWE aufgelistet. Jedoch sind Schwachstellen durch undefiniertes Verhalten dokumentiert in:

  • Linux Standard Base Spezifikationsbeispielen
  • Compiler-spezifischen Sicherheitshinweisen zu undefiniertem Verhalten

Referenzen

  1. MITRE Corporation. "CWE-475: Undefined Behavior for Input to API." https://cwe.mitre.org/data/definitions/475.html
  2. CERT C Secure Coding Standard. "MSC15-C. Do not depend on undefined behavior."
  3. Linux Standard Base Specification.