Fehlerhafte bitweise Verschiebung einer Ganzzahl

Beschreibung

Fehlerhafte bitweise Verschiebung einer Ganzzahl tritt auf, wenn ein Ganzzahlwert um einen negativen Betrag oder um einen Betrag verschoben wird, der größer oder gleich der Anzahl der im Wert enthaltenen Bits ist. Negative Verschiebungsbetrage sind in verschiedenen Programmiersprachen undefiniert, wobei Compiler diese Operationen typischerweise nicht validieren. Überverschiebung (Verschiebung über die Bitbreite hinaus) erzeugt architektur- und compilerabhängige Ergebnisse, die oft explizit als undefiniertes Verhalten kategorisiert werden.

Risiko

Fehlerhafte bitweise Verschiebungen haben schwerwiegende Auswirkungen. Undefiniertes Verhalten wird ausgelöst. Programmabstürze sind möglich. Sicherheitsprüfungen werden umgangen. Division durch Null durch Überverschiebung. Speicherbeschadigung. Beliebige Codeausführung in bestimmten Kontexten. Architekturabhängige Ergebnisse. Unvorhersehbarer Programmzustand. Hohe Wahrscheinlichkeit, wenn Verschiebungsbetrage aus nicht vertrauenswürdiger Eingabe stammen.

Lösung

Explizite Validierungsprüfungen während der Implementierung implementieren, um negative oder übermäßige Verschiebungswerte vor der Ausführung abzulehnen. Sicherstellen, dass Verschiebungsbetrage immer im gültigen Bereich liegen: 0 bis (Bitbreite - 1). Vorzeichenlose Typen für Verschiebungsbetrage verwenden, wo möglich. Laufzeitprüfungen vor Verschiebungsoperationen mit variablen Betragen hinzufügen.

Häufige Auswirkungen

AuswirkungDetails
VerfügbarkeitBereich: Verfügbarkeit

Denial of Service durch Abstürze, Programmabbrüche oder Neustarts durch undefiniertes Verhalten.
IntegritätBereich: Integrität

Unerwartete Berechnungsergebnisse, die zur Umgehung von Sicherheitsprüfungen führen.

Beispielcode

Verwundbarer Code

// VERWUNDBAR: C-Code mit ungeprüften Verschiebungsoperationen

#include <stdint.h>
#include <stdio.h>

// VERWUNDBAR: Verschiebungsbetrag aus Benutzereingabe
uint32_t vulnerable_left_shift(uint32_t value, int shift_amount) {
    // VERWUNDBAR: Keine Validierung von shift_amount
    // Negativ oder >= 32 ist undefiniertes Verhalten
    return value << shift_amount;
}

// VERWUNDBAR: Rechtsverschiebung mit vorzeichenbehaftetem Wert
int32_t vulnerable_right_shift(int32_t value, int shift_amount) {
    // VERWUNDBAR: Negative Verschiebung ist undefiniert
    // Auch: Rechtsverschiebung negativer Werte ist implementierungsabhängig
    return value >> shift_amount;
}

// VERWUNDBAR: Division mittels Verschiebung (CVE-2009-4307-Muster)
int vulnerable_divide_by_power_of_2(int value, int power) {
    // VERWUNDBAR: Wenn power >= 32, ist die Verschiebung undefiniert
    // Ergebnis könnte 0 sein und Divisionsprobleme verursachen
    int divisor = 1 << power;  // Undefiniert wenn power >= 32!

    if (divisor == 0) {
        return -1;  // Diese Prüfung funktioniert möglicherweise nicht wegen UB
    }

    return value / divisor;
}

// VERWUNDBAR: Dateisystem-Blockberechnung (CVE-2009-4307-Muster)
uint64_t vulnerable_calculate_blocks(uint64_t size, uint32_t block_bits) {
    // VERWUNDBAR: block_bits aus Dateisystem-Metadaten
    // Angreifer kann block_bits >= 64 setzen

    // Diese Verschiebung ist undefiniertes Verhalten
    uint64_t block_size = 1ULL << block_bits;

    // Kann Division durch Null oder fehlerhafte Berechnung ergeben
    return size / block_size;
}

// VERWUNDBAR: Kernel-Verschiebungsschwachstelle (CVE-2020-8835-Muster)
int vulnerable_kernel_shift(unsigned long value, unsigned int shift) {
    // VERWUNDBAR: Verschiebungswert aus nicht vertrauenswürdiger Quelle
    // Überverschiebung führt zu Speicherzugriffsproblemen
    unsigned long mask = (1UL << shift) - 1;
    return value & mask;
}

// VERWUNDBAR: Negative Verschiebung bei vorzeichenbehafteter Ganzzahl
int vulnerable_negative_shift(void) {
    int x = 1;
    int shift = -1;  // VERWUNDBAR: Negative Verschiebung

    // Undefiniertes Verhalten - könnte alles sein
    return x << shift;
}
// VERWUNDBAR: Java-Verschiebungsoperationen

public class VulnerableShift {

    // VERWUNDBAR: Java maskiert den Verschiebungsbetrag, kann aber unerwartete Ergebnisse liefern
    public static int vulnerableShift(int value, int shiftAmount) {
        // Java: shiftAmount wird auf 5 Bits für int maskiert (0-31)
        // Aber negative Werte liefern weiterhin unerwartete Ergebnisse
        return value << shiftAmount;

        // Wenn shiftAmount 33 ist, verschiebt Java um 33 & 0x1F = 1
        // Dies ist möglicherweise nicht das beabsichtigte Verhalten
    }

    // VERWUNDBAR: Long-Verschiebung mit int-Überlaufpotential
    public static long vulnerableLongShift(long value, int shiftAmount) {
        // VERWUNDBAR: shiftAmount könnte aus nicht vertrauenswürdiger Eingabe stammen
        // Verschiebt möglicherweise nicht wie erwartet, wenn der Betrag außerhalb des Bereichs liegt
        return value << shiftAmount;
    }

    // VERWUNDBAR: Verschiebung zur Bit-Extraktion verwenden
    public static int vulnerableExtractBits(int value, int position, int width) {
        // VERWUNDBAR: position + width könnte 32 überschreiten
        int mask = (1 << width) - 1;  // Kann überlaufen
        return (value >> position) & mask;
    }
}
// VERWUNDBAR: JavaScript-Verschiebungsoperationen

// VERWUNDBAR: JavaScript konvertiert zu 32-Bit für Verschiebungen
function vulnerableShift(value, amount) {
    // JavaScript: konvertiert value zu 32-Bit vorzeichenbehafteter Ganzzahl
    // Verschiebungsbetrag wird auf 5 Bits maskiert (0-31)

    // Größe Werte verlieren Präzision
    // Betrag > 31 erzeugt unerwartete Maskierung
    return value << amount;
}

// VERWUNDBAR: Verschiebung mit möglicherweise negativem Betrag
function vulnerableDynamicShift(value, amount) {
    // Wenn amount negativ ist, führt JavaScript trotzdem die Verschiebung durch
    // aber mit maskiertem Wert (z.B. -1 wird zu 31)
    return value << amount;  // Unerwartetes Ergebnis!
}

// VERWUNDBAR: Verschiebung für Zweierpotenz verwenden
function vulnerablePowerOf2(exponent) {
    // VERWUNDBAR: exponent >= 32 erzeugt 0 oder unerwarteten Wert
    return 1 << exponent;
}

Sichere Lösung

// SICHER: Sichere Verschiebungsoperationen in C

#include <stdint.h>
#include <stdbool.h>
#include <limits.h>

// SICHER: Validierte Linksverschiebung
uint32_t safe_left_shift(uint32_t value, int shift_amount) {
    // SICHER: Verschiebungsbetrag validieren
    if (shift_amount < 0 || shift_amount >= 32) {
        return 0;  // Sicherer Standardwert
    }

    return value << shift_amount;
}

// SICHER: Sichere Rechtsverschiebung mit korrekter Behandlung
uint32_t safe_right_shift(uint32_t value, int shift_amount) {
    // SICHER: Vorzeichenlos verwenden, um implementierungsabhängiges Verhalten zu vermeiden
    if (shift_amount < 0 || shift_amount >= 32) {
        return 0;
    }

    return value >> shift_amount;
}

// SICHER: Sichere Division mittels Verschiebung
int safe_divide_by_power_of_2(int value, unsigned int power) {
    // SICHER: power vor der Verschiebung validieren
    if (power >= sizeof(int) * CHAR_BIT) {
        return 0;  // Wäre ohnehin Division durch riesige Zahl
    }

    int divisor = 1 << power;
    return value / divisor;
}

// SICHER: Sichere Dateisystem-Blockberechnung
uint64_t safe_calculate_blocks(uint64_t size, uint32_t block_bits) {
    // SICHER: block_bits validieren
    if (block_bits == 0 || block_bits >= 64) {
        return 0;  // Ungültige Blockgröße
    }

    // SICHER: Auch auf vernünftige Blockgröße prüfen
    if (block_bits > 30) {  // > 1GB Blockgröße ist verdächtig
        return 0;
    }

    uint64_t block_size = 1ULL << block_bits;
    return size / block_size;
}

// SICHER: Generische sichere Verschiebungsfunktion
static inline uint64_t safe_shift_left_64(uint64_t value, unsigned int shift) {
    if (shift >= 64) {
        return 0;
    }
    return value << shift;
}

static inline uint64_t safe_shift_right_64(uint64_t value, unsigned int shift) {
    if (shift >= 64) {
        return 0;
    }
    return value >> shift;
}

// SICHER: Kernel-Stil sichere Verschiebung
unsigned long safe_kernel_shift(unsigned long value, unsigned int shift) {
    // SICHER: Verschiebungsbetrag validieren
    if (shift >= sizeof(unsigned long) * CHAR_BIT) {
        return 0;
    }

    unsigned long mask = (1UL << shift) - 1;
    return value & mask;
}

// SICHER: Makro für zur Kompilierzeit geprüfte Verschiebungen
#define SAFE_SHIFT_LEFT(val, shift, type) \
    (((shift) >= 0 && (shift) < (int)(sizeof(type) * CHAR_BIT)) ? \
     ((type)(val) << (shift)) : (type)0)

#define SAFE_SHIFT_RIGHT(val, shift, type) \
    (((shift) >= 0 && (shift) < (int)(sizeof(type) * CHAR_BIT)) ? \
     ((type)(val) >> (shift)) : (type)0)
// SICHER: Sichere Java-Verschiebungsoperationen

public class SafeShift {

    // SICHER: Validierte Verschiebungsoperation
    public static int safeShiftLeft(int value, int shiftAmount) {
        // SICHER: Explizite Validierung
        if (shiftAmount < 0 || shiftAmount >= 32) {
            return 0;  // Sicherer Standardwert
        }

        return value << shiftAmount;
    }

    // SICHER: Sichere Long-Verschiebung
    public static long safeLongShiftLeft(long value, int shiftAmount) {
        // SICHER: Für 64-Bit validieren
        if (shiftAmount < 0 || shiftAmount >= 64) {
            return 0L;
        }

        return value << shiftAmount;
    }

    // SICHER: Sichere Bit-Extraktion
    public static int safeExtractBits(int value, int position, int width) {
        // SICHER: Parameter validieren
        if (position < 0 || width <= 0 || position >= 32 || width > 32) {
            return 0;
        }

        if (position + width > 32) {
            return 0;  // Würde über den Wert hinaus lesen
        }

        // SICHER: Sichere Maskenerstellung
        int mask;
        if (width >= 32) {
            mask = -1;  // Alle Bits gesetzt
        } else {
            mask = (1 << width) - 1;
        }

        return (value >>> position) & mask;  // Vorzeichenlose Rechtsverschiebung verwenden
    }

    // SICHER: Sichere Zweierpotenz
    public static long safePowerOf2(int exponent) {
        if (exponent < 0 || exponent >= 63) {
            return 0L;  // Würde überlaufen oder ungültig sein
        }

        return 1L << exponent;
    }

    // SICHER: Hilfsklasse für sichere Verschiebungen
    public static class ShiftUtils {
        public static int clampShiftAmount(int amount, int maxBits) {
            if (amount < 0) return 0;
            if (amount >= maxBits) return maxBits - 1;
            return amount;
        }
    }
}
// SICHER: Sichere JavaScript-Verschiebungsoperationen

// SICHER: Validierte Verschiebungsfunktion
function safeShiftLeft(value, amount) {
    // SICHER: Betrag für 32-Bit-Operationen validieren
    if (typeof amount !== 'number' || amount < 0 || amount >= 32) {
        return 0;
    }

    // Explizit in 32-Bit-Ganzzahl konvertieren
    return (value | 0) << amount;
}

// SICHER: Sichere vorzeichenlose Rechtsverschiebung
function safeShiftRightUnsigned(value, amount) {
    if (typeof amount !== 'number' || amount < 0 || amount >= 32) {
        return 0;
    }

    return (value >>> amount);
}

// SICHER: Sichere Zweierpotenz mit BigInt für große Werte
function safePowerOf2(exponent) {
    if (typeof exponent !== 'number' || exponent < 0) {
        return 0n;
    }

    if (exponent >= 32) {
        // BigInt für große Exponenten verwenden
        return 1n << BigInt(exponent);
    }

    return 1 << exponent;
}

// SICHER: Sichere Bit-Manipulationsbibliothek
const SafeBits = {
    shiftLeft: function(value, amount) {
        if (!Number.isInteger(amount) || amount < 0 || amount >= 32) {
            throw new RangeError('Verschiebungsbetrag muss 0-31 sein');
        }
        return (value | 0) << amount;
    },

    shiftRight: function(value, amount) {
        if (!Number.isInteger(amount) || amount < 0 || amount >= 32) {
            throw new RangeError('Verschiebungsbetrag muss 0-31 sein');
        }
        return (value | 0) >> amount;
    },

    shiftRightUnsigned: function(value, amount) {
        if (!Number.isInteger(amount) || amount < 0 || amount >= 32) {
            throw new RangeError('Verschiebungsbetrag muss 0-31 sein');
        }
        return value >>> amount;
    },

    extractBits: function(value, position, width) {
        if (position < 0 || width <= 0 || position + width > 32) {
            throw new RangeError('Ungültige Bit-Extraktionsparameter');
        }

        const mask = (1 << width) - 1;
        return (value >>> position) & mask;
    }
};

CVE-Beispiele

  • CVE-2009-4307: ext4-Dateisystem-Überverschiebung, die Division durch Null verursacht.
  • CVE-2020-8835: Linux-Kernel-Überverschiebung, die unautorisierte Lese- und Schreibzugriffe ermöglicht.
  • CVE-2015-1607: Vorzeichenbehaftete Linksverschiebung negativer Ganzzahlen, die Speicherfehler in libksba verursacht.

Verwandte CWEs

  • CWE-682: Incorrect Calculation (übergeordnet)
  • CWE-189: Numeric Errors (Kategorie)

Referenzen

  1. MITRE Corporation. "CWE-1335: Incorrect Bitwise Shift of Integer." https://cwe.mitre.org/data/definitions/1335.html
  2. CERT C. "INT34-C. Do not shift an expression by a negative number of bits or by greater than or equal to the number of bits that exist in the operand"
  3. ISO C Standard. "Undefined Behavior in Shift Operations"