Attempt to Access Child of a Non-structure Pointer
Description
Attempt to Access Child of a Non-structure Pointer occurs when code treats a pointer to a non-structure type as if it were a pointer to a structure and attempts to access a member field. This typically happens due to type confusion, improper casting, or accessing an array element with member access syntax. The compiler may not catch this error in dynamically typed contexts or when void pointers are improperly cast. The result is accessing memory at an incorrect offset, leading to corruption or crashes.
Risk
Accessing "members" of non-structure pointers causes memory corruption, crashes, and potential security vulnerabilities. Reading at incorrect offsets exposes unrelated memory (information disclosure). Writing at incorrect offsets corrupts adjacent data. If the computed offset happens to point to a valid memory location, the corruption may go undetected until later failures. Attackers can potentially exploit this to read or write arbitrary memory locations.
Solution
Use proper type declarations and avoid unsafe casts. Enable strict type checking and compiler warnings. Don't cast void pointers to structure types without verification. Use proper structure declarations when structure access is needed. In C++, prefer templates and type-safe containers over void pointers. Use static analysis tools that detect type confusion. In dynamic languages, validate object types before member access.
Common Consequences
| Impact | Details |
|---|---|
| Integrity | Scope: Memory Corruption Writing to incorrect offsets corrupts memory. |
| Confidentiality | Scope: Information Disclosure Reading incorrect offsets exposes unrelated data. |
| Availability | Scope: Crash Invalid memory access causes segmentation faults. |
Example Code + Solution Code
Vulnerable Code
// VULNERABLE: Casting int pointer to struct pointer
struct Data {
int value;
char name[32];
};
void process_vulnerable(void* ptr) {
// ptr might actually be an int*, not a struct Data*
struct Data* data = (struct Data*)ptr;
printf("Name: %s\n", data->name); // Reads garbage!
}
void caller_vulnerable() {
int value = 42;
process_vulnerable(&value); // Passing int*, expecting struct!
}
// VULNERABLE: Array treated as structure
void array_as_struct_vulnerable() {
int array[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// Wrongly cast array to struct pointer
struct {
int first;
int second;
char data[32];
}* s = (void*)array;
// Accessing 'data' reads past valid array bounds!
printf("Data: %s\n", s->data);
}
// VULNERABLE: Generic pointer with wrong assumptions
void handle_generic_vulnerable(void* data, int type) {
// Missing type check!
struct ComplexData* complex = (struct ComplexData*)data;
// If type doesn't match ComplexData, this corrupts memory
complex->field1 = 100;
}
// VULNERABLE: Buffer treated as object
void buffer_as_object_vulnerable() {
char buffer[16];
memset(buffer, 0, sizeof(buffer));
// Wrong! buffer is char array, not a structure
struct Object {
int id;
double value;
char* name;
}* obj = (struct Object*)buffer;
obj->name = "test"; // Writes pointer to wrong location!
printf("%s\n", obj->name); // May crash
}
// VULNERABLE: Callback with wrong data type
typedef void (*Callback)(void* data);
void process_callback_vulnerable(Callback cb) {
int simple_value = 42;
cb(&simple_value); // Callback might expect structure!
}
void my_callback_vulnerable(void* data) {
struct Expected {
int count;
char* items[10];
}* expected = (struct Expected*)data;
// Crashes or corrupts: data is just an int!
for (int i = 0; i < expected->count; i++) {
printf("%s\n", expected->items[i]);
}
}
// VULNERABLE: Union misuse
union Data {
int integer;
struct {
short a;
short b;
char name[20];
} structured;
};
void union_misuse_vulnerable(union Data* d, int is_struct) {
// Ignoring is_struct flag!
printf("Name: %s\n", d->structured.name); // Wrong if !is_struct
}
// VULNERABLE: C++ with type confusion
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) {
// Assuming ptr is Derived*, but might not be
Derived* d = static_cast<Derived*>(ptr);
std::cout << d->name << std::endl; // Garbage if wrong type!
}
void caller_vulnerable() {
int value = 42;
process_vulnerable(&value); // Not a Derived!
}
// VULNERABLE: Template with wrong type
template<typename T>
void access_member_vulnerable(void* ptr) {
T* typed = static_cast<T*>(ptr);
// If ptr doesn't actually point to T, this is wrong
typed->member = 0;
}
// VULNERABLE: Incorrect reinterpret_cast
void reinterpret_vulnerable() {
double values[10];
struct WrongType {
int id;
char name[100];
}* wrong = reinterpret_cast<WrongType*>(values);
// Accessing name reads way past values array!
std::cout << wrong->name << std::endl;
}
// JavaScript is dynamically typed but has similar issues
function processVulnerable(obj) {
// Assumes obj has specific structure
console.log(obj.data.nested.value); // Error if wrong type!
}
// Called with wrong type
processVulnerable({ data: 42 }); // data is number, not object!
processVulnerable(null); // Cannot read properties of null!
Fixed Code
// SAFE: Type-tagged 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;
}
}
// SAFE: Proper type checking in generic functions
struct ComplexData {
int magic; // Type identifier
int field1;
char data[64];
};
#define COMPLEX_DATA_MAGIC 0x12345678
void handle_generic_safe(void* data, int type) {
if (type != TYPE_COMPLEX) {
handle_error("Wrong type");
return;
}
struct ComplexData* complex = (struct ComplexData*)data;
// Verify magic number for extra safety
if (complex->magic != COMPLEX_DATA_MAGIC) {
handle_error("Invalid data structure");
return;
}
complex->field1 = 100;
}
// SAFE: Proper buffer to structure with size check
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 too small");
return;
}
// Copy to properly aligned structure
struct Object obj;
memcpy(&obj, buffer, sizeof(obj));
printf("ID: %d, Value: %f\n", obj.id, obj.value);
}
// SAFE: Type-safe callback using function signature
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);
}
// SAFE: Union with proper type tracking
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("Unknown type");
}
}
// SAFE: Use proper type hierarchy
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;
}
};
// SAFE: Dynamic cast with check
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;
}
// SAFE: Type-safe variant (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 size: " << arg.size() << std::endl;
}
}, v);
}
// SAFE: std::any with type check
#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 << "Unknown type" << std::endl;
}
}
// SAFE: Template with 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-time type safety
}
// SAFE: JavaScript with type checking
function processSafe(obj) {
// Check type before access
if (obj === null || obj === undefined) {
console.error('Object is null or undefined');
return;
}
if (typeof obj !== 'object') {
console.error('Expected object');
return;
}
if (!obj.data || typeof obj.data !== 'object') {
console.error('Missing or invalid data property');
return;
}
if (!obj.data.nested || typeof obj.data.nested !== 'object') {
console.error('Missing or invalid nested property');
return;
}
console.log(obj.data.nested.value);
}
// SAFE: Optional chaining (ES2020)
function processSafeModern(obj) {
const value = obj?.data?.nested?.value;
if (value !== undefined) {
console.log(value);
} else {
console.log('Value not found');
}
}
// SAFE: TypeScript with interfaces
interface NestedData {
value: number;
}
interface DataContainer {
data: {
nested: NestedData;
};
}
function processTyped(obj: DataContainer): void {
console.log(obj.data.nested.value); // Type-checked at compile time
}
Exploited in the Wild
Type Confusion Vulnerabilities
Browser engines have had type confusion vulnerabilities where objects were accessed with wrong type assumptions.
Heap Corruption via Wrong Casts
Incorrect casts in C/C++ code have led to heap corruption exploits.
Information Disclosure
Reading structure members from non-structure data has exposed sensitive memory contents.
Tools to test/exploit
-
Valgrind — detects invalid memory access.
-
AddressSanitizer — catches memory errors.
-
UndefinedBehaviorSanitizer — detects type confusion.
-
Static analyzers — flag suspicious casts.
CVE Examples
-
Type confusion CVEs in browsers (V8, SpiderMonkey).
-
Object type confusion in applications.
-
Heap corruption via incorrect type casting.
References
-
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/