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
| Auswirkung | Details |
|---|---|
| Andere | Umfang: Ändere Qualitätsverschlechterung - Code kann sich unvorhersehbar verhalten, wenn APIs mit Eingaben aufgerufen werden, die undefiniertes Verhalten auslösen. |
| Andere | Umfang: Ä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
- MITRE Corporation. "CWE-475: Undefined Behavior for Input to API." https://cwe.mitre.org/data/definitions/475.html
- CERT C Secure Coding Standard. "MSC15-C. Do not depend on undefined behavior."
- Linux Standard Base Specification.