Versuch auf Kind eines Nicht-Struktur-Zeigers zuzugreifen
Beschreibung
Versuch auf Kind eines Nicht-Struktur-Zeigers zuzugreifen tritt auf, wenn Code einen Pointer auf einen Nicht-Struktur-Typ behandelt, als wäre er ein Pointer auf eine Struktur, und versucht, auf ein Mitgliedsfeld zuzugreifen. Dies geschieht typischerweise aufgrund von Typverwechslung, unsachgemäßem Casting oder Zugriff auf ein Array-Element mit Mitgliedszugriffsyntax. Der Compiler fängt diesen Fehler möglicherweise in dynamisch typisierten Kontexten oder wenn void-Pointer unsachgemäß gecastet werden nicht ab. Das Ergebnis ist Speicherzugriff an einem falschen Offset, was zu Korruption oder Abstürzen führt.
Risiko
Der Zugriff auf "Mitglieder" von Nicht-Struktur-Zeigern verursacht Speicherkorruption, Abstürze und potenzielle Sicherheitsschwachstellen. Lesen an falschen Offsets exponiert unzusammenhängenden Speicher (Informationsoffenlegung). Schreiben an falschen Offsets korrumpiert benachbarte Daten. Wenn der berechnete Offset zufällig auf einen gültigen Speicherort zeigt, kann die Korruption unentdeckt bleiben bis spätere Fehler auftreten. Angreifer können dies potenziell ausnutzen, um beliebige Speicherorte zu lesen oder zu schreiben.
Lösung
Verwenden Sie ordnungsgemäße Typdeklarationen und vermeiden Sie unsichere Casts. Aktivieren Sie strikte Typprüfung und Compiler-Warnungen. Casten Sie void-Pointer nicht auf Strukturtypen ohne Verifikation. Verwenden Sie ordnungsgemäße Strukturdeklarationen, wenn Strukturzugriff benötigt wird. In C++ bevorzugen Sie Templates und typsichere Container gegenüber void-Zeigern. Verwenden Sie statische Analysewerkzeuge, die Typverwechslung erkennen. In dynamischen Sprachen validieren Sie Objekttypen vor Mitgliedszugriff.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Integrität | Bereich: Speicherkorruption Schreiben an falsche Offsets korrumpiert Speicher. |
| Vertraulichkeit | Bereich: Informationsoffenlegung Lesen falscher Offsets exponiert unzusammenhängende Daten. |
| Verfügbarkeit | Bereich: Absturz Ungültiger Speicherzugriff verursacht Segmentation Faults. |
Beispielcode + Lösungscode
Verwundbarer Code
// VERWUNDBAR: int-Pointer auf Struct-Pointer casten
struct Data {
int value;
char name[32];
};
void process_vulnerable(void* ptr) {
// ptr könnte tatsächlich ein int* sein, nicht struct Data*
struct Data* data = (struct Data*)ptr;
printf("Name: %s\n", data->name); // Liest Müll!
}
void caller_vulnerable() {
int value = 42;
process_vulnerable(&value); // Übergibt int*, erwartet Struct!
}
// VERWUNDBAR: Array als Struktur behandelt
void array_as_struct_vulnerable() {
int array[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// Fälschlicherweise Array auf Struct-Pointer casten
struct {
int first;
int second;
char data[32];
}* s = (void*)array;
// Zugriff auf 'data' liest über gültige Array-Grenzen hinaus!
printf("Data: %s\n", s->data);
}
// VERWUNDBAR: Generischer Pointer mit falschen Annahmen
void handle_generic_vulnerable(void* data, int type) {
// Fehlende Typprüfung!
struct ComplexData* complex = (struct ComplexData*)data;
// Wenn type nicht zu ComplexData passt, korrumpiert dies Speicher
complex->field1 = 100;
}
// VERWUNDBAR: Buffer als Objekt behandelt
void buffer_as_object_vulnerable() {
char buffer[16];
memset(buffer, 0, sizeof(buffer));
// Falsch! buffer ist char-Array, keine Struktur
struct Object {
int id;
double value;
char* name;
}* obj = (struct Object*)buffer;
obj->name = "test"; // Schreibt Pointer an falschen Ort!
printf("%s\n", obj->name); // Kann abstürzen
}
// VERWUNDBAR: Callback mit falschem Datentyp
typedef void (*Callback)(void* data);
void process_callback_vulnerable(Callback cb) {
int simple_value = 42;
cb(&simple_value); // Callback könnte Struktur erwarten!
}
void my_callback_vulnerable(void* data) {
struct Expected {
int count;
char* items[10];
}* expected = (struct Expected*)data;
// Stürzt ab oder korrumpiert: data ist nur ein int!
for (int i = 0; i < expected->count; i++) {
printf("%s\n", expected->items[i]);
}
}
// VERWUNDBAR: Union-Missbrauch
union Data {
int integer;
struct {
short a;
short b;
char name[20];
} structured;
};
void union_misuse_vulnerable(union Data* d, int is_struct) {
// Ignoriert is_struct-Flag!
printf("Name: %s\n", d->structured.name); // Falsch wenn !is_struct
}
// VERWUNDBAR: C++ mit Typverwechslung
class Base {
public:
virtual void process() {}
};
class Derived : public Base {
public:
std::string name;
int value;
void process() override {
std::cout << name << std::endl;
}
};
void process_vulnerable(void* ptr) {
// Annahme ptr ist Derived*, aber könnte nicht sein
Derived* d = static_cast<Derived*>(ptr);
std::cout << d->name << std::endl; // Müll wenn falscher Typ!
}
void caller_vulnerable() {
int value = 42;
process_vulnerable(&value); // Kein Derived!
}
// VERWUNDBAR: Template mit falschem Typ
template<typename T>
void access_member_vulnerable(void* ptr) {
T* typed = static_cast<T*>(ptr);
// Wenn ptr nicht tatsächlich auf T zeigt, ist dies falsch
typed->member = 0;
}
// VERWUNDBAR: Falsches reinterpret_cast
void reinterpret_vulnerable() {
double values[10];
struct WrongType {
int id;
char name[100];
}* wrong = reinterpret_cast<WrongType*>(values);
// Zugriff auf name liest weit über values-Array hinaus!
std::cout << wrong->name << std::endl;
}
// JavaScript ist dynamisch typisiert, hat aber ähnliche Probleme
function processVulnerable(obj) {
// Nimmt an, obj hat spezifische Struktur
console.log(obj.data.nested.value); // Fehler wenn falscher Typ!
}
// Aufgerufen mit falschem Typ
processVulnerable({ data: 42 }); // data ist Zahl, nicht Objekt!
processVulnerable(null); // Kann Eigenschaften von null nicht lesen!
Lösungscode
// SICHER: Typ-getaggte Unions
enum DataType {
TYPE_INT,
TYPE_STRUCT
};
struct TypedData {
enum DataType type;
union {
int integer;
struct {
int count;
char name[32];
} structured;
} data;
};
void process_safe(struct TypedData* td) {
switch (td->type) {
case TYPE_INT:
printf("Integer: %d\n", td->data.integer);
break;
case TYPE_STRUCT:
printf("Name: %s\n", td->data.structured.name);
break;
}
}
// SICHER: Ordnungsgemäße Typprüfung in generischen Funktionen
struct ComplexData {
int magic; // Typidentifikator
int field1;
char data[64];
};
#define COMPLEX_DATA_MAGIC 0x12345678
void handle_generic_safe(void* data, int type) {
if (type != TYPE_COMPLEX) {
handle_error("Falscher Typ");
return;
}
struct ComplexData* complex = (struct ComplexData*)data;
// Magic-Number für zusätzliche Sicherheit verifizieren
if (complex->magic != COMPLEX_DATA_MAGIC) {
handle_error("Ungültige Datenstruktur");
return;
}
complex->field1 = 100;
}
// SICHER: Ordnungsgemäßer Buffer zu Struktur mit Größenprüfung
void buffer_safe(const char* buffer, size_t buffer_size) {
struct Object {
int id;
double value;
};
if (buffer_size < sizeof(struct Object)) {
handle_error("Buffer zu klein");
return;
}
// In ordnungsgemäß ausgerichtete Struktur kopieren
struct Object obj;
memcpy(&obj, buffer, sizeof(obj));
printf("ID: %d, Value: %f\n", obj.id, obj.value);
}
// SICHER: Typsicherer Callback mit Funktionssignatur
typedef void (*IntCallback)(int value);
typedef void (*StructCallback)(struct Data* data);
void process_int_callback(IntCallback cb) {
int value = 42;
cb(value);
}
void process_struct_callback(StructCallback cb) {
struct Data data = {.value = 42, .name = "test"};
cb(&data);
}
// SICHER: Union mit ordnungsgemäßer Typ-Verfolgung
union SafeData {
int integer;
struct {
short a;
short b;
char name[20];
} structured;
};
struct TaggedUnion {
enum DataType type;
union SafeData data;
};
void union_safe(struct TaggedUnion* tu) {
switch (tu->type) {
case TYPE_INT:
printf("Integer: %d\n", tu->data.integer);
break;
case TYPE_STRUCT:
printf("Name: %s\n", tu->data.structured.name);
break;
default:
handle_error("Unbekannter Typ");
}
}
// SICHER: Ordnungsgemäße Typhierarchie verwenden
class Base {
public:
virtual ~Base() = default;
virtual void process() = 0;
};
class Derived : public Base {
public:
std::string name;
int value;
void process() override {
std::cout << name << std::endl;
}
};
// SICHER: Dynamic cast mit Prüfung
void process_safe(Base* ptr) {
if (ptr == nullptr) {
handleNull();
return;
}
Derived* d = dynamic_cast<Derived*>(ptr);
if (d == nullptr) {
handleWrongType();
return;
}
std::cout << d->name << std::endl;
}
// SICHER: Typsichere Variante (C++17)
#include <variant>
using SafeVariant = std::variant<int, std::string, std::vector<int>>;
void process_variant_safe(const SafeVariant& v) {
std::visit([](auto&& arg) {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, int>) {
std::cout << "Int: " << arg << std::endl;
} else if constexpr (std::is_same_v<T, std::string>) {
std::cout << "String: " << arg << std::endl;
} else {
std::cout << "Vector-Größe: " << arg.size() << std::endl;
}
}, v);
}
// SICHER: std::any mit Typprüfung
#include <any>
void process_any_safe(const std::any& a) {
if (a.type() == typeid(int)) {
std::cout << std::any_cast<int>(a) << std::endl;
} else if (a.type() == typeid(std::string)) {
std::cout << std::any_cast<std::string>(a) << std::endl;
} else {
std::cout << "Unbekannter Typ" << std::endl;
}
}
// SICHER: Template mit Concepts (C++20)
template<typename T>
concept HasMember = requires(T t) {
{ t.member } -> std::convertible_to<int>;
};
template<HasMember T>
void access_member_safe(T& obj) {
obj.member = 0; // Compile-Zeit Typsicherheit
}
// SICHER: JavaScript mit Typprüfung
function processSafe(obj) {
// Typ vor Zugriff prüfen
if (obj === null || obj === undefined) {
console.error('Objekt ist null oder undefined');
return;
}
if (typeof obj !== 'object') {
console.error('Erwartet Objekt');
return;
}
if (!obj.data || typeof obj.data !== 'object') {
console.error('Fehlende oder ungültige data-Eigenschaft');
return;
}
if (!obj.data.nested || typeof obj.data.nested !== 'object') {
console.error('Fehlende oder ungültige nested-Eigenschaft');
return;
}
console.log(obj.data.nested.value);
}
// SICHER: Optional Chaining (ES2020)
function processSafeModern(obj) {
const value = obj?.data?.nested?.value;
if (value !== undefined) {
console.log(value);
} else {
console.log('Wert nicht gefunden');
}
}
// SICHER: TypeScript mit Interfaces
interface NestedData {
value: number;
}
interface DataContainer {
data: {
nested: NestedData;
};
}
function processTyped(obj: DataContainer): void {
console.log(obj.data.nested.value); // Zur Compile-Zeit typgeprüft
}
Ausgenutzt in der Praxis
Typverwechslungs-Schwachstellen
Browser-Engines hatten Typverwechslungs-Schwachstellen, bei denen auf Objekte mit falschen Typannahmen zugegriffen wurde.
Heap-Korruption durch falsche Casts
Falsche Casts in C/C++-Code haben zu Heap-Korruptions-Exploits geführt.
Informationsoffenlegung
Lesen von Struktur-Mitgliedern aus Nicht-Struktur-Daten hat sensible Speicherinhalte exponiert.
Tools zum Testen/Ausnutzen
- Valgrind — erkennt ungültige Speicherzugriffe
- AddressSanitizer — fängt Speicherfehler ab
- UndefinedBehaviorSanitizer — erkennt Typverwechslung
- Statische Analysatoren — markieren verdächtige Casts
CVE-Beispiele
- Typverwechslungs-CVEs in Browsern (V8, SpiderMonkey)
- Objekt-Typverwechslung in Anwendungen
- Heap-Korruption durch falsches Type-Casting
Referenzen
- MITRE. "CWE-588: Attempt to Access Child of a Non-structure Pointer." https://cwe.mitre.org/data/definitions/588.html
- CERT C. "EXP39-C: Do not access a variable through a pointer of an incompatible type." https://wiki.sei.cmu.edu/confluence/display/c/