Löschung von Datenstruktur-Sentinel

Beschreibung

Löschung von Datenstruktur-Sentinel ist eine Schwachstelle, bei der das versehentliche Löschen eines Datenstruktur-Sentinels ernsthafte Programmierlogik-Probleme verursachen kann. Datenstruktur-Sentinels markieren die Struktur von Daten, wie Null-Zeichen, die Strings terminieren, oder spezielle Marker, die das Ende von verketteten Listen anzeigen. Wenn Code einfachen Zugriff auf und Modifikation dieser Kontrolldaten ohne ordnungsgemäßen Schutz erlaubt, können Sentinels versehentlich entfernt werden. Dies führt dazu, dass Funktionen, die auf Sentinels angewiesen sind, fehlfunktionieren und möglicherweise über beabsichtigte Grenzen hinaus lesen, Daten korrumpieren oder unbefugten Zugriff ermöglichen.

Risiko

Gelöschte Sentinels erzeugen unvorhersehbares und gefährliches Verhalten. Das Entfernen des Null-Terminators von einem C-String führt dazu, dass String-Funktionen über den beabsichtigten Puffer hinaus lesen, was möglicherweise sensible Daten preisgibt oder Abstürze verursacht. Gelöschte Listen-Terminatoren führen dazu, dass Iteration in ungültigen Speicher fortgesetzt wird. Das Risiko erstreckt sich auf Sicherheitskontrollen, wenn Sentinel-Löschung das Umgehen von Zugriffsbeschränkungen oder Längenbegrenzungen ermöglicht. Puffer-Überlesungen können sensible Informationen aus dem Speicher preisgeben. Die Schwachstelle ist besonders gefährlich, weil ihre Auswirkungen nicht sofort sichtbar sind und sich unvorhersehbar manifestieren können.

Lösung

Verwenden Sie Abstraktionsbibliotheken, um Anwendungen von riskanten APIs abzuschirmen, die direkte Sentinel-Manipulation erlauben. Setzen Sie Compiler-basierte Buffer Overflow-Schutzmaßnahmen ein wie Microsoft Visual Studio /GS-Flag, GCC FORTIFY_SOURCE, oder Technologien wie StackGuard und ProPolice. Nutzen Sie OS-Level präventive Funktionalität wie ASLR und nicht ausführbare Speicherbereiche. Entwerfen Sie Datenstrukturen mit klaren Schnittstellen, die direkten Zugriff auf Sentinels verhindern. Validieren Sie alle Operationen, die Sentinels beeinflussen könnten. Verwenden Sie langen-präfixierte Datenstrukturen anstelle von sentinel-terminierten, wo praktikabel.

Häufige Auswirkungen

AuswirkungDetails
VerfügbarkeitUmfang: Verfügbarkeit, Sonstiges

DoS: Absturz/Beendigung/Neustart - Datenstrukturen können fehlfunktionieren oder Abstürze verursachen, wenn Sentinels gelöscht werden.
AutorisierungUmfang: Autorisierung, Sonstiges

Privilegien erlangen oder Identität annehmen - Das Entfernen von Steuerzeichen wie NULL kann Zugriffskontrollen brechen oder Sicherheitsprüfungen umgehen.
VertraulichkeitUmfang: Vertraulichkeit

Speicher lesen - Gelöschte String-Terminatoren verursachen Lesezugriffe über beabsichtigte Grenzen hinaus, was möglicherweise sensible Daten preisgibt.

Beispielcode

Anfälliger Code

// Anfällig: Null-Terminator überschrieben
#include <stdio.h>
#include <string.h>

void vulnerable_string_fill(char *buffer, size_t buffer_size) {
    // Anfällig: Füllt gesamten Puffer einschließlich Null-Terminator-Position
    for (size_t i = 0; i < buffer_size; i++) {
        buffer[i] = 'a';
    }

    // Null-Terminator überschrieben!
    // printf("%s") wird über Puffer hinaus lesen

    printf("Inhalt: %s\n", buffer);  // Liest über Puffer hinaus
}

void vulnerable_copy() {
    char buffer[10];

    // Anfällig: Kopiert genau 10 Zeichen, kein Platz für Null
    strncpy(buffer, "0123456789", 10);

    // Anfällig: buffer ist nicht null-terminiert
    printf("%s\n", buffer);  // Undefiniertes Verhalten
}

void vulnerable_input_processing() {
    char buffer[256];

    // Anfällig: fgets enthält Zeilenumbruch, strlen berücksichtigt ihn nicht
    fgets(buffer, sizeof(buffer), stdin);

    // Versucht Zeilenumbruch durch Überschreiben mit Null zu entfernen
    size_t len = strlen(buffer);
    if (buffer[len - 1] == '\n') {
        buffer[len - 1] = '\0';
    }

    // Anfällig: Wenn Eingabe genau 255 Zeichen ohne Zeilenumbruch ist
    // könnte len - 1 tatsächliche Daten oder Sentinel korrumpieren
}
// Anfällig: Verkettete-Liste-Sentinel gelöscht
#include <stdlib.h>

typedef struct Node {
    int data;
    struct Node* next;
} Node;

typedef struct {
    Node* head;
    Node* sentinel;  // Spezieller End-Marker
} LinkedList;

void vulnerable_delete_node(LinkedList* list, int value) {
    Node* current = list->head;
    Node* prev = NULL;

    while (current != NULL) {
        if (current->data == value) {
            if (prev == NULL) {
                list->head = current->next;
            } else {
                prev->next = current->next;
            }

            // Anfällig: Keine Prüfung ob Sentinel gelöscht wird!
            free(current);
            return;
        }
        prev = current;
        current = current->next;
    }

    // Wenn Angreifer Sentinel-Löschung verursacht,
    // wird Iteration über Listenende hinausgehen
}

void iterate_list(LinkedList* list) {
    Node* current = list->head;

    // Anfällig: Verlasst sich auf Sentinel um Iteration zu stoppen
    while (current != list->sentinel) {
        process(current->data);
        current = current->next;

        // Wenn Sentinel gelöscht wurde, läuft dies endlos
        // oder greift auf ungültigen Speicher zu
    }
}
# Anfällig: Trennzeichen-Entfernung bei Datenverarbeitung
class VulnerableParser:
    RECORD_DELIMITER = '\x00'  # Null-Byte trennt Datensätze

    def parse_records(self, data):
        records = []
        current = ""

        for char in data:
            if char == self.RECORD_DELIMITER:
                records.append(current)
                current = ""
            else:
                current += char

        # Anfällig: Wenn letztes Trennzeichen fehlt, gehen Daten verloren
        return records

    def clean_input(self, data):
        # Anfällig: Entfernt alle Steuerzeichen einschließlich Trennzeichen
        cleaned = ''.join(c for c in data if c.isprintable() or c == ' ')

        # Datensatz-Trennzeichen entfernt - Datenstruktur korrumpiert
        return cleaned

    def truncate_record(self, record, max_length):
        # Anfällig: Kann Sentinel abschneiden
        if len(record) > max_length:
            return record[:max_length]  # Trennzeichen möglicherweise entfernt
        return record
// Anfällig: Array-Sentinel-Entfernung
public class VulnerableArrayHandler {

    private static final int SENTINEL = Integer.MIN_VALUE;

    public int[] processArray(int[] data) {
        // Array erwartet mit SENTINEL zu enden

        // Anfällig: Benutzer kann Array einschließlich Sentinel modifizieren
        for (int i = 0; i < data.length; i++) {
            if (data[i] == SENTINEL) {
                // Anfällig: Überschreibt Sentinel
                data[i] = 0;  // Daten "bereinigen"
            }
        }

        return data;
        // Sentinel gelöscht - Iteration weiß nicht wo zu stoppen
    }

    public void iterateToSentinel(int[] data) {
        int i = 0;
        // Anfällig: Nimmt an dass Sentinel existiert
        while (data[i] != SENTINEL) {
            process(data[i]);
            i++;
            // Wenn Sentinel entfernt wurde, ArrayIndexOutOfBoundsException
        }
    }
}

Korrigierter Code

// Korrigiert: Ordnungsgemäße Null-Terminator-Behandlung
#include <stdio.h>
#include <string.h>

void secure_string_fill(char *buffer, size_t buffer_size) {
    if (buffer_size == 0) return;

    // Korrigiert: Platz für Null-Terminator lassen
    for (size_t i = 0; i < buffer_size - 1; i++) {
        buffer[i] = 'a';
    }

    // Korrigiert: Null-Terminierung immer sicherstellen
    buffer[buffer_size - 1] = '\0';

    printf("Inhalt: %s\n", buffer);
}

void secure_copy() {
    char buffer[11];  // Korrigiert: Extra Byte für Null

    // Korrigiert: Höchstens n-1 Zeichen kopieren, Null-Terminierung sicherstellen
    strncpy(buffer, "0123456789", sizeof(buffer) - 1);
    buffer[sizeof(buffer) - 1] = '\0';  // Korrigiert: Terminierung garantieren

    printf("%s\n", buffer);
}

// Korrigiert: Sicherere String-Funktionen verwenden
void secure_copy_with_strlcpy() {
    char buffer[10];

    // Korrigiert: strlcpy terminiert immer mit Null
    strlcpy(buffer, "0123456789", sizeof(buffer));
    // Ergebnis: "012345678\0" (9 Zeichen + Null)

    printf("%s\n", buffer);
}

void secure_input_processing() {
    char buffer[256];

    if (fgets(buffer, sizeof(buffer), stdin) == NULL) {
        return;  // Korrigiert: Fehler behandeln
    }

    // Korrigiert: Sichere Zeilenumbruch-Entfernung
    size_t len = strlen(buffer);
    if (len > 0 && buffer[len - 1] == '\n') {
        buffer[len - 1] = '\0';
    }

    // Korrigiert: Puffer ist immer ordnungsgemäß terminiert
}
// Korrigiert: Geschützter verkettete-Liste-Sentinel
#include <stdlib.h>
#include <stdbool.h>

typedef struct Node {
    int data;
    struct Node* next;
    bool is_sentinel;  // Korrigiert: Flag zur Identifikation des Sentinels
} Node;

typedef struct {
    Node* head;
    Node* sentinel;
} LinkedList;

LinkedList* create_list() {
    LinkedList* list = malloc(sizeof(LinkedList));

    // Korrigiert: Geschützten Sentinel erstellen
    list->sentinel = malloc(sizeof(Node));
    list->sentinel->data = 0;
    list->sentinel->next = NULL;
    list->sentinel->is_sentinel = true;

    list->head = list->sentinel;
    return list;
}

bool secure_delete_node(LinkedList* list, int value) {
    Node* current = list->head;
    Node* prev = NULL;

    while (current != list->sentinel) {
        if (current->data == value) {
            // Korrigiert: Prüfen ob versucht wird Sentinel zu löschen
            if (current->is_sentinel) {
                // Kann Sentinel nicht löschen
                return false;
            }

            if (prev == NULL) {
                list->head = current->next;
            } else {
                prev->next = current->next;
            }

            free(current);
            return true;
        }
        prev = current;
        current = current->next;
    }

    return false;  // Nicht gefunden
}

void iterate_list(LinkedList* list) {
    Node* current = list->head;

    // Korrigiert: Sowohl Sentinel-Flag als auch Pointer prüfen
    while (current != NULL && !current->is_sentinel) {
        process(current->data);
        current = current->next;
    }
}
# Korrigiert: Geschützte Trennzeichen-Behandlung
class SecureParser:
    RECORD_DELIMITER = '\x00'

    def parse_records(self, data):
        records = []
        current = ""

        for char in data:
            if char == self.RECORD_DELIMITER:
                records.append(current)
                current = ""
            else:
                current += char

        # Korrigiert: Letzten Datensatz ohne Trennzeichen behandeln
        if current:
            records.append(current)

        return records

    def clean_input(self, data, preserve_delimiters=True):
        # Korrigiert: Optional Trennzeichen bewahren
        if preserve_delimiters:
            cleaned = ''.join(
                c for c in data
                if c.isprintable() or c == ' ' or c == self.RECORD_DELIMITER
            )
        else:
            cleaned = ''.join(c for c in data if c.isprintable() or c == ' ')

        return cleaned

    def truncate_record(self, record, max_length):
        # Korrigiert: Sicherstellen dass Trennzeichen bewahrt wird
        if len(record) > max_length:
            # Prüfen ob Datensatz mit Trennzeichen endet
            ends_with_delim = record.endswith(self.RECORD_DELIMITER)
            truncated = record[:max_length]

            # Korrigiert: Trennzeichen wieder hinzufügen wenn es vorhanden war
            if ends_with_delim and not truncated.endswith(self.RECORD_DELIMITER):
                truncated = truncated[:-1] + self.RECORD_DELIMITER

            return truncated
        return record

    # Korrigiert: Explizite Länge anstelle von Sentinel verwenden
    def parse_records_with_length(self, data, record_lengths):
        """Parsen mit expliziten Längen anstelle von Sentinels."""
        records = []
        offset = 0

        for length in record_lengths:
            if offset + length > len(data):
                raise ValueError("Daten kürzer als angegebene Längen")
            records.append(data[offset:offset + length])
            offset += length

        return records
// Korrigiert: Array ohne Sentinel-Abhängigkeit
public class SecureArrayHandler {

    // Korrigiert: Wrapper-Klasse anstelle von Sentinel verwenden
    public static class SafeArray {
        private final int[] data;
        private final int length;  // Explizite Länge

        public SafeArray(int[] source) {
            this.length = source.length;
            this.data = Arrays.copyOf(source, length);
        }

        public int get(int index) {
            if (index < 0 || index >= length) {
                throw new IndexOutOfBoundsException();
            }
            return data[index];
        }

        public void set(int index, int value) {
            if (index < 0 || index >= length) {
                throw new IndexOutOfBoundsException();
            }
            data[index] = value;
        }

        public int length() {
            return length;
        }

        public void iterate(Consumer<Integer> processor) {
            for (int i = 0; i < length; i++) {
                processor.accept(data[i]);
            }
        }
    }

    // Korrigiert: Wenn Sentinel erforderlich ist, ihn schützen
    public static class SentinelArray {
        private static final int SENTINEL = Integer.MIN_VALUE;
        private final int[] data;

        public SentinelArray(int[] source) {
            // Korrigiert: Kopieren und Sentinel hinzufügen
            this.data = new int[source.length + 1];
            System.arraycopy(source, 0, this.data, 0, source.length);
            this.data[source.length] = SENTINEL;
        }

        public void set(int index, int value) {
            // Korrigiert: Überschreiben des Sentinels verhindern
            if (index < 0 || index >= data.length - 1) {
                throw new IndexOutOfBoundsException();
            }
            if (value == SENTINEL) {
                throw new IllegalArgumentException("Sentinel-Wert kann nicht verwendet werden");
            }
            data[index] = value;
        }

        public void iterate(Consumer<Integer> processor) {
            for (int i = 0; data[i] != SENTINEL; i++) {
                processor.accept(data[i]);
            }
        }
    }
}

CVE-Beispiele

Keine spezifischen CVEs sind in der MITRE-Datenbank für dieses CWE aufgeführt. Das Muster ist jedoch verwandt mit:

  • Puffer-Überlesungs-Schwachstellen (CWE-125)
  • Unsachgemäße Null-Terminierung (CWE-170)
  • Off-by-one-Fehler, die String-Terminatoren betreffen

Referenzen

  1. MITRE Corporation. "CWE-463: Deletion of Data Structure Sentinel." https://cwe.mitre.org/data/definitions/463.html
  2. CERT C Secure Coding Standard. "STR32-C. Do not pass a non-null-terminated character sequence to a library function that expects a string."