Skip to content

CWE-824: Access of Uninitialized Pointer

Overview

Access of Uninitialized Pointer occurs when a pointer variable is read or dereferenced before it has been assigned a valid address. An uninitialized pointer holds whatever value was already present in memory, so using it produces unpredictable behavior instead of a clean, reproducible failure. This commonly happens when an error path, a conditional branch, or a partially run constructor skips the assignment that every other path performs.

Relationship to Other CWEs

Risk

High to Critical: Dereferencing an uninitialized pointer causes crashes, memory corruption, or information disclosure. If the garbage value is attacker-influenced - reused stack or heap memory an attacker previously controlled - it can enable arbitrary code execution.

Remediation Steps

Core Principle: Never read or dereference a pointer before it has been explicitly assigned a valid address or a safe null/None value.

Trace the Data Path

  • Source: Every declaration or struct/class field of pointer type
  • Sink: The first read or dereference of that pointer
  • Missing Controls: A code path - an early branch, an unhandled error, a partially run constructor - that reaches the sink without having assigned the pointer first

Initialize Every Pointer at Declaration (Primary Defense)

  • Assign a value at the point of declaration - a valid address or an explicit null/None sentinel - rather than leaving it to be set later on some paths and not others
  • For struct/class fields, initialize every pointer member on every constructor path, including ones that exit early
  • Prefer language constructs that default to a safe empty state over ones that leave the value undefined

Check Before Dereferencing (Defense in Depth)

  • Add a null/None check immediately before any dereference where the assignment isn't guaranteed by the type system
  • Fail loudly (return an error, throw) rather than continuing with an unchecked pointer

Harden and Test

  • Enable compiler warnings for uninitialized use and treat them as build failures
  • Run a memory sanitizer that detects uninitialized reads during development, rather than waiting for a crash in production

Test with Malicious Inputs

  • Exercise every branch that could previously skip initialization, including error and exception paths
  • Cover constructors that exit early, by exception or early return, before setting every pointer field
  • Re-scan with the security scanner to confirm the finding is resolved

Common Vulnerable Patterns

// VULNERABLE - pseudo-code
declare ptr           // no initial value - contains garbage
if condition:
    ptr = allocate()
write(ptr, value)     // condition false: ptr is still garbage

Why this is vulnerable: the value in an uninitialized pointer is not random. It is whatever the program itself last left in that stack slot or heap cell, so it is repeatable for a given build and call sequence and shapeable by anyone able to influence the calls that ran earlier. That is what turns "reads garbage" into "writes to an address the attacker chose".

Repeatable is not the same as predictable from the source, and the difference cuts against the defender. In C and C++ the read is undefined behavior rather than a load of an unknown value, so an optimizing compiler may assume it never happens and reshape the surrounding code - which is why the same source can fault under -O0, silently take the wrong branch under -O2, and do a third thing under a different compiler. A test that passes on the build the developer ran is not evidence about the build that ships.

The conditional is the other half. The write is unconditional and the assignment is not, so the defect exists only on the path where condition is false - the path least likely to be covered by the tests written alongside the feature, and the first one an attacker will look for. Initializing the declaration to null is worth doing, but it is not the fix on its own: it converts an unpredictable write into a predictable crash, which cuts the blast radius without making the code correct. The write still needs a guard that the pointer was assigned.

Secure Patterns

// SECURE - pseudo-code
declare ptr = null    // explicit safe default
if condition:
    ptr = allocate()
if ptr is not null:
    write(ptr, value)

Why this works: the pointer's value no longer depends on which branch ran. A skipped assignment leaves a known, checkable value rather than leftover memory contents, and the check before the dereference rejects a pointer that was never assigned.

Language-Specific Guidance

  • C - NULL initialization discipline and where it costs a compiler diagnostic, = {0} versus memset for structs, checking before dereference, MemorySanitizer/Valgrind and their prerequisites
  • C++ - default member initializers, the new T versus new T() trap, member initializer lists and what a throwing constructor does, smart pointers that default to nullptr, delegating-constructor pitfalls

Additional Resources