Abhängigkeit von maschinenabhängiger Datenrepräsentation

Beschreibung

Abhängigkeit von maschinenabhängiger Datenrepräsentation tritt auf, wenn Code Datenrepräsentationen verwendet, die auf Low-Level-Konstrukten basieren, die über verschiedene Prozessoren, physische Maschinen, Betriebssysteme oder andere physische Komponenten variieren können. Dies umfasst Annahmen über Datentypgrößen, Byte-Reihenfolge (Endianness), Struktur-Padding, Ausrichtungsanforderungen und Zeigergrößen. Wenn Code von diesen implementierungsspezifischen Verhaltensweisen abhängt, wird er nicht portabel und verhält sich möglicherweise auf verschiedenen Plattformen falsch oder unsicher.

Risiko

Die Abhängigkeit von maschinenspezifischer Datenrepräsentation hat direkte Sicherheitsimplikationen. Integer-Überlaufverhalten kann über Plattformen hinweg unterschiedlich sein und zu ausnutzbaren Bedingungen führen. Byte-Reihenfolge-Annahmen können Datenkorruption bei der Kommunikation zwischen Systemen verursachen. Struktur-Padding-Unterschiede können Buffer Overflows oder Speicherkorruption verursachen. Zeigergrößen-Annahmen können 32-Bit vs. 64-Bit-Kompatibilitätsprobleme verursachen. Serialisierte Daten können auf verschiedenen Architekturen falsch interpretiert werden. Kryptographische Operationen können auf verschiedenen Plattformen unterschiedliche Ergebnisse liefern. Speicherausrichtungsverletzungen können Abstürze oder undefiniertes Verhalten verursachen.

Lösung

Verwenden Sie Festbreiten-Integertypen (int32_t, uint64_t) anstelle plattformabhängiger Typen. Behandeln Sie die Byte-Reihenfolge explizit beim Serialisieren/Deserialisieren von Daten. Verwenden Sie standardisierte Serialisierungsformate (JSON, Protocol Buffers, MessagePack). Vermeiden Sie Casts zwischen Zeigertypen und Integern. Nehmen Sie kein spezifisches Struktur-Padding oder Ausrichtung an. Verwenden Sie sizeof() und offsetof() für Strukturfeldzugriff. Testen Sie Code auf mehreren Architekturen. Verwenden Sie statische Analysetools, um Portabilitätsprobleme zu erkennen. Dokumentieren Sie beabsichtigtes plattformspezifisches Verhalten.

Häufige Auswirkungen

AuswirkungDetails
IntegritätBereich: Integrität

Datenkorruption - Daten können auf verschiedenen Plattformen falsch interpretiert werden.
VerfügbarkeitBereich: Verfügbarkeit

DoS: Absturz - Ausrichtungsverletzungen oder falsche Datengrößen können Abstürze verursachen.
AndereBereich: Ändere

Reduzierte Portabilität - Code kann auf verschiedenen Architekturen nicht korrekt ausgeführt werden.

Beispielcode

Anfälliger Code

// Anfällig: Maschinenabhängige Datenrepräsentation
#include <stdio.h>
#include <string.h>

// Anfällig: Nimmt spezifische Größe für int an
struct VulnerablePacket {
    int type;           // Größe variiert: 2 Bytes (16-Bit) oder 4 Bytes (32/64-Bit)
    long length;        // Größe variiert: 4 Bytes (32-Bit) oder 8 Bytes (64-Bit)
    char data[100];
};

void vulnerable_serialize(struct VulnerablePacket* packet, char* buffer) {
    // Anfällig: Direkte Speicherkopie nimmt spezifisches Layout an
    memcpy(buffer, packet, sizeof(struct VulnerablePacket));
    // Struktur-Padding und Feldgrößen variieren nach Plattform!
}

void vulnerable_deserialize(char* buffer, struct VulnerablePacket* packet) {
    // Anfällig: Nimmt an, Buffer hat gleiches Layout wie lokale Struktur
    memcpy(packet, buffer, sizeof(struct VulnerablePacket));
}

// Anfällig: Endianness-Annahme
uint32_t vulnerable_read_network_int(unsigned char* buffer) {
    // Anfällig: Nimmt Little-Endian-Byte-Reihenfolge an
    return *(uint32_t*)buffer;  // Falsch auf Big-Endian-Systemen!
}

void vulnerable_write_network_int(unsigned char* buffer, uint32_t value) {
    // Anfällig: Direkte Speicherkopie ignoriert Byte-Reihenfolge
    *(uint32_t*)buffer = value;  // Daten korrupt bei unterschiedlicher Endianness!
}

// Anfällig: Zeigergrößen-Annahme
void vulnerable_pointer_handling() {
    void* ptr = malloc(100);

    // Anfällig: Nimmt an, Pointer passen in unsigned int
    unsigned int addr = (unsigned int)ptr;  // Abgeschnitten auf 64-Bit!

    // Später wird diese abgeschnittene Adresse verwendet...
    void* restored = (void*)(uintptr_t)addr;  // Falsche Adresse!
    free(restored);  // Möglicher Absturz oder Korruption
}

// Anfällig: Ausrichtungsannahme
void vulnerable_alignment(char* buffer) {
    // Anfällig: Nicht ausgerichteter Zugriff kann auf manchen Architekturen abstürzen
    int* int_ptr = (int*)(buffer + 1);  // Nicht ausgerichtet!
    *int_ptr = 42;  // Absturz auf ARM, SPARC; langsam auf x86
}
// Anfällig: C++ mit plattformabhängigen Annahmen
#include <cstdint>
#include <fstream>

// Anfällig: Gepackte Struktur mit Plattformannahmen
#pragma pack(push, 1)
struct VulnerableHeader {
    int magic;           // Größe variiert nach Plattform
    size_t file_size;    // 4 Bytes auf 32-Bit, 8 Bytes auf 64-Bit
    time_t timestamp;    // Variiert stark über Plattformen
    char name[32];
};
#pragma pack(pop)

class VulnerableFileFormat {
public:
    void save(const std::string& filename, const VulnerableHeader& header,
              const std::vector<char>& data) {
        std::ofstream file(filename, std::ios::binary);

        // Anfällig: Direktes Struktur-Schreiben
        file.write(reinterpret_cast<const char*>(&header), sizeof(header));
        file.write(data.data(), data.size());
    }

    VulnerableHeader load(const std::string& filename) {
        std::ifstream file(filename, std::ios::binary);
        VulnerableHeader header;

        // Anfällig: Direktes Struktur-Lesen nimmt gleiche Plattform an
        file.read(reinterpret_cast<char*>(&header), sizeof(header));

        return header;  // Korrupt wenn Datei von anderer Architektur!
    }
};

// Anfällig: Union Type Punning
union VulnerableFloatBits {
    float f;
    uint32_t bits;  // Nimmt float ist 32-Bits mit spezifischer Repräsentation an
};

uint32_t vulnerable_float_to_bits(float f) {
    VulnerableFloatBits u;
    u.f = f;
    return u.bits;  // Verhalten variiert nach Plattform
}
# Anfällig: Python mit maschinenabhängigen Annahmen
import struct
import ctypes

class VulnerableSerializer:

    def serialize_int(self, value):
        # Anfällig: Verwendet native Byte-Reihenfolge
        return struct.pack('i', value)  # Endianness variiert!

    def deserialize_int(self, data):
        # Anfällig: Nimmt native Byte-Reihenfolge entspricht Quelle an
        return struct.unpack('i', data)[0]

    def serialize_pointer(self, ptr):
        # Anfällig: Zeigergröße variiert (4 vs 8 Bytes)
        return struct.pack('P', ptr)  # Plattformabhängig!


class VulnerableBinaryProtocol:

    def read_message(self, socket):
        # Anfällig: Nimmt 4-Byte-Integer für Länge an
        length_data = socket.recv(4)

        # Anfällig: Native Byte-Reihenfolge
        length = struct.unpack('i', length_data)[0]

        return socket.recv(length)

    def write_message(self, socket, data):
        # Anfällig: Native Byte-Reihenfolge für Länge
        length = struct.pack('i', len(data))
        socket.send(length)
        socket.send(data)


# Anfällig: ctypes mit Plattformannahmen
class VulnerableStruct(ctypes.Structure):
    _fields_ = [
        ('value', ctypes.c_int),    # Größe variiert
        ('pointer', ctypes.c_void_p), # Größe variiert
        ('data', ctypes.c_char * 10)
    ]

    def to_bytes(self):
        # Anfällig: Layout ist plattformabhängig
        return bytes(self)

    @classmethod
    def from_bytes(cls, data):
        # Anfällig: Nimmt gleiches Plattform-Layout an
        return cls.from_buffer_copy(data)

Korrigierter Code

// Korrigiert: Plattformunabhängige Datenrepräsentation
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <arpa/inet.h>  // Für htonl, ntohl

// Korrigiert: Festbreiten-Typen verwenden
struct FixedPacket {
    uint32_t type;      // Immer 4 Bytes
    uint64_t length;    // Immer 8 Bytes
    char data[100];
};

// Korrigiert: Explizite Serialisierung mit definierter Byte-Reihenfolge
size_t fixed_serialize(const struct FixedPacket* packet, uint8_t* buffer) {
    size_t offset = 0;

    // Type in Netzwerk-Byte-Reihenfolge (Big-Endian) schreiben
    uint32_t type_be = htonl(packet->type);
    memcpy(buffer + offset, &type_be, sizeof(type_be));
    offset += sizeof(type_be);

    // Length in Netzwerk-Byte-Reihenfolge schreiben
    uint64_t length_be = htobe64(packet->length);
    memcpy(buffer + offset, &length_be, sizeof(length_be));
    offset += sizeof(length_be);

    // Daten schreiben (bereits Bytes)
    memcpy(buffer + offset, packet->data, sizeof(packet->data));
    offset += sizeof(packet->data);

    return offset;
}

int fixed_deserialize(const uint8_t* buffer, struct FixedPacket* packet) {
    size_t offset = 0;

    // Type in Netzwerk-Byte-Reihenfolge lesen
    uint32_t type_be;
    memcpy(&type_be, buffer + offset, sizeof(type_be));
    packet->type = ntohl(type_be);
    offset += sizeof(type_be);

    // Length in Netzwerk-Byte-Reihenfolge lesen
    uint64_t length_be;
    memcpy(&length_be, buffer + offset, sizeof(length_be));
    packet->length = be64toh(length_be);
    offset += sizeof(length_be);

    // Daten lesen
    memcpy(packet->data, buffer + offset, sizeof(packet->data));

    return 0;
}

// Korrigiert: Explizite Byte-Reihenfolge-Behandlung
uint32_t fixed_read_network_int(const uint8_t* buffer) {
    // Bytes explizit lesen, um jede Endianness zu behandeln
    return ((uint32_t)buffer[0] << 24) |
           ((uint32_t)buffer[1] << 16) |
           ((uint32_t)buffer[2] << 8) |
           ((uint32_t)buffer[3]);
}

void fixed_write_network_int(uint8_t* buffer, uint32_t value) {
    // Bytes explizit in Netzwerkreihenfolge (Big-Endian) schreiben
    buffer[0] = (value >> 24) & 0xFF;
    buffer[1] = (value >> 16) & 0xFF;
    buffer[2] = (value >> 8) & 0xFF;
    buffer[3] = value & 0xFF;
}

// Korrigiert: uintptr_t für Pointer/Integer-Konvertierungen verwenden
void fixed_pointer_handling() {
    void* ptr = malloc(100);

    // Korrigiert: Korrekten Typ für zeigergroße Integer verwenden
    uintptr_t addr = (uintptr_t)ptr;

    // Sichere Wiederherstellung
    void* restored = (void*)addr;
    free(restored);
}

// Korrigiert: Sicherer nicht ausgerichteter Zugriff
uint32_t fixed_read_unaligned(const uint8_t* buffer) {
    // Byte-weise lesen, um jede Ausrichtung zu behandeln
    uint32_t value;
    memcpy(&value, buffer, sizeof(value));
    return value;
}
// Korrigiert: C++ mit plattformunabhängiger Datenbehandlung
#include <cstdint>
#include <fstream>
#include <vector>
#include <cstring>

// Korrigiert: Explizite Feldgrößen und Serialisierung
struct FixedHeader {
    static constexpr uint32_t MAGIC = 0x46495845;  // "FIXE"
    static constexpr size_t SERIALIZED_SIZE = 4 + 8 + 8 + 32;

    uint32_t magic;
    uint64_t file_size;
    int64_t timestamp;  // Festgrößen-Zeitstempel
    char name[32];
};

class FixedFileFormat {
public:
    void save(const std::string& filename, const FixedHeader& header,
              const std::vector<char>& data) {
        std::ofstream file(filename, std::ios::binary);

        // Korrigiert: Explizite Byte-für-Byte-Serialisierung
        write_uint32_be(file, header.magic);
        write_uint64_be(file, header.file_size);
        write_int64_be(file, header.timestamp);
        file.write(header.name, sizeof(header.name));

        file.write(data.data(), data.size());
    }

    FixedHeader load(const std::string& filename) {
        std::ifstream file(filename, std::ios::binary);
        FixedHeader header{};

        // Korrigiert: Explizite Deserialisierung
        header.magic = read_uint32_be(file);
        header.file_size = read_uint64_be(file);
        header.timestamp = read_int64_be(file);
        file.read(header.name, sizeof(header.name));

        return header;
    }

private:
    static void write_uint32_be(std::ostream& os, uint32_t value) {
        uint8_t bytes[4] = {
            static_cast<uint8_t>(value >> 24),
            static_cast<uint8_t>(value >> 16),
            static_cast<uint8_t>(value >> 8),
            static_cast<uint8_t>(value)
        };
        os.write(reinterpret_cast<char*>(bytes), 4);
    }

    static void write_uint64_be(std::ostream& os, uint64_t value) {
        uint8_t bytes[8];
        for (int i = 7; i >= 0; --i) {
            bytes[7 - i] = static_cast<uint8_t>(value >> (i * 8));
        }
        os.write(reinterpret_cast<char*>(bytes), 8);
    }

    static void write_int64_be(std::ostream& os, int64_t value) {
        write_uint64_be(os, static_cast<uint64_t>(value));
    }

    static uint32_t read_uint32_be(std::istream& is) {
        uint8_t bytes[4];
        is.read(reinterpret_cast<char*>(bytes), 4);
        return (static_cast<uint32_t>(bytes[0]) << 24) |
               (static_cast<uint32_t>(bytes[1]) << 16) |
               (static_cast<uint32_t>(bytes[2]) << 8) |
               static_cast<uint32_t>(bytes[3]);
    }

    static uint64_t read_uint64_be(std::istream& is) {
        uint8_t bytes[8];
        is.read(reinterpret_cast<char*>(bytes), 8);
        uint64_t result = 0;
        for (int i = 0; i < 8; ++i) {
            result = (result << 8) | bytes[i];
        }
        return result;
    }

    static int64_t read_int64_be(std::istream& is) {
        return static_cast<int64_t>(read_uint64_be(is));
    }
};

// Korrigiert: memcpy für Type Punning verwenden (C++20 hat auch std::bit_cast)
uint32_t fixed_float_to_bits(float f) {
    static_assert(sizeof(float) == sizeof(uint32_t), "Float muss 32 Bits sein");
    uint32_t bits;
    std::memcpy(&bits, &f, sizeof(bits));
    return bits;
}
# Korrigiert: Plattformunabhängige Python-Serialisierung
import struct
from typing import Tuple


class FixedSerializer:
    """Plattformunabhängige binäre Serialisierung."""

    def serialize_int32(self, value: int) -> bytes:
        # Korrigiert: Explizite Big-Endian-Byte-Reihenfolge
        return struct.pack('>i', value)  # '>' = Big-Endian

    def deserialize_int32(self, data: bytes) -> int:
        # Korrigiert: Explizite Big-Endian-Byte-Reihenfolge
        return struct.unpack('>i', data)[0]

    def serialize_int64(self, value: int) -> bytes:
        return struct.pack('>q', value)

    def deserialize_int64(self, data: bytes) -> int:
        return struct.unpack('>q', data)[0]

    def serialize_uint32(self, value: int) -> bytes:
        return struct.pack('>I', value)

    def deserialize_uint32(self, data: bytes) -> int:
        return struct.unpack('>I', data)[0]


class FixedBinaryProtocol:
    """Korrigiertes Binärprotokoll mit expliziter Byte-Reihenfolge."""

    def __init__(self):
        self._serializer = FixedSerializer()

    def read_message(self, socket) -> bytes:
        # Korrigiert: Länge mit expliziter Byte-Reihenfolge lesen
        length_data = self._recv_exact(socket, 4)
        length = self._serializer.deserialize_uint32(length_data)

        # Länge validieren, um DoS zu verhindern
        if length > 10 * 1024 * 1024:  # 10 MB Maximum
            raise ValueError(f"Nachricht zu groß: {length}")

        return self._recv_exact(socket, length)

    def write_message(self, socket, data: bytes) -> None:
        # Korrigiert: Länge mit expliziter Byte-Reihenfolge schreiben
        length = self._serializer.serialize_uint32(len(data))
        socket.sendall(length)
        socket.sendall(data)

    def _recv_exact(self, socket, size: int) -> bytes:
        """Exakt size Bytes empfangen."""
        data = b''
        while len(data) < size:
            chunk = socket.recv(size - len(data))
            if not chunk:
                raise ConnectionError("Verbindung geschlossen")
            data += chunk
        return data


# Korrigiert: Standardisiertes Serialisierungsformat verwenden
import json

class JsonProtocol:
    """JSON für plattformunabhängigen Datenaustausch verwenden."""

    def serialize(self, data: dict) -> bytes:
        return json.dumps(data).encode('utf-8')

    def deserialize(self, data: bytes) -> dict:
        return json.loads(data.decode('utf-8'))


# Für binäre Effizienz Protocol Buffers oder MessagePack verwenden
try:
    import msgpack

    class MsgPackProtocol:
        """MessagePack für effiziente plattformunabhängige Serialisierung."""

        def serialize(self, data) -> bytes:
            return msgpack.packb(data)

        def deserialize(self, data: bytes):
            return msgpack.unpackb(data)
except ImportError:
    pass

CVE-Beispiele

Plattformabhängige Datenbehandlung hat zahlreiche Schwachstellen verursacht, insbesondere in Netzwerkprotokollen und Dateiformaten, bei denen Daten zwischen verschiedenen Systemen ausgetauscht werden.


Verwandte CWEs

  • CWE-758: Reliance on Undefined, Unspecified, or Implementation-Defined Behavior (Eltern)
  • CWE-1105: Insufficient Encapsulation of Machine-Dependent Functionality (Peer)
  • CWE-681: Incorrect Conversion between Numeric Types (verwandt)

Referenzen

  1. MITRE Corporation. "CWE-1102: Reliance on Machine-Dependent Data Representation." https://cwe.mitre.org/data/definitions/1102.html
  2. CERT C Coding Standard. INT36-C: Converting a pointer to integer or integer to pointer.
  3. C++ Core Guidelines. "Portability and machine-specific issues."