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
| Auswirkung | Details |
|---|---|
| Integrität | Bereich: Integrität Datenkorruption - Daten können auf verschiedenen Plattformen falsch interpretiert werden. |
| Verfügbarkeit | Bereich: Verfügbarkeit DoS: Absturz - Ausrichtungsverletzungen oder falsche Datengrößen können Abstürze verursachen. |
| Andere | Bereich: Ä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
- MITRE Corporation. "CWE-1102: Reliance on Machine-Dependent Data Representation." https://cwe.mitre.org/data/definitions/1102.html
- CERT C Coding Standard. INT36-C: Converting a pointer to integer or integer to pointer.
- C++ Core Guidelines. "Portability and machine-specific issues."