All writingOWASP Study Notes 13

Memory Management Failures: Following the Native Code

Monochrome pixel art of stone tablets breaking through an archive compartment divider and spilling onto a lantern-lit floor.

Memory Management Failures is X02:2025 in OWASP's Next Steps section. It sits outside the ten selected categories, but it still belongs in an application-security review when the system includes components that manage memory unsafely.

For a web application, the starting point is broader than the language used for its routes. A service written in JavaScript or Python may pass uploaded files to a native image processor, document parser, or other library. The review needs to follow that processing too.

OWASP's X02 guidance discusses buffer overflows, invalid memory access, use-after-free, and related failures.

Bounds and lifetimes answer different questions

A buffer has a finite amount of space. Copying more data into it than it can hold can overwrite memory outside that buffer. An out-of-bounds read goes beyond the permitted region while reading and may expose data that the application did not intend to return.

The important detail is how the program calculates, checks, and uses the size. An incorrect length or arithmetic overflow can undermine a check that otherwise appears reasonable. CWE-120 describes a classic case: copying input without checking that it fits the destination.

Object lifetime introduces a separate question: is the memory still valid when the program uses it?

With a use-after-free, a reference remains in use after the memory has been released. That region may later be reused for something else. Reading or writing through the old reference can produce a crash, corrupt data, or contribute to code execution, depending on the circumstances. CWE-416 explains this failure.

Those outcomes are possibilities, not a claim that every memory bug provides a working exploit.

Follow an upload past the web handler

Consider a hypothetical application that accepts profile images. The web handler checks the user's permission and limits the upload size before passing the image to a native decoder.

Those checks are useful, but the decoder still needs to handle malformed file contents safely. A filename or declared content type cannot establish that its internal parsing is correct.

This gives the review several concrete questions. Which decoder runs? Which version is deployed? Does it run in the web process or a separate worker? What data and permissions can that process access?

Keeping the component patched addresses known defects. Running it with limited privileges and appropriate isolation can reduce the consequences of a failure. Neither replaces fixing the underlying memory bug.

Memory-safe languages reduce the burden

Where practical, memory-safe languages can prevent broad classes of invalid memory access rather than leaving every bounds and lifetime check to the programmer. That makes language choice relevant when building new components or replacing high-risk ones.

The guarantee still has boundaries. Native interfaces, unsafe operations, and the underlying runtime need attention. The Rustonomicon's explanation of safe and unsafe Rust describes how safe interfaces rely on unsafe implementations upholding their contracts.

Memory safety also does not make resource use unlimited. A program can retain too much data or accept too much work without corrupting memory. The resource limits discussed in the previous entry still apply.

Testing the code that handles memory

For native code, compiler warnings and static analysis can identify problems before execution. Sanitizers add runtime checks to test builds. AddressSanitizer, for example, detects several kinds of out-of-bounds access, use-after-free, and invalid frees.

Fuzzing can exercise a parser with varied and malformed inputs while a sanitizer checks the executed paths. A useful regression case preserves the input that exposed a defect so that the corrected behavior can be checked again.

A clean run has limits: it says something about the paths and inputs exercised. It does not prove that every possible memory error is absent.

Release builds also need appropriate hardening for their platform. Stack protection, address-space randomization, and non-executable memory can make exploitation harder. They do not correct an out-of-bounds access or a dangling pointer. The OWASP C-Based Toolchain Hardening Cheat Sheet separates build-time checks from these additional protections.

Questions for my notes

  • Where does untrusted data reach native code?
  • Which component owns each allocation, and when is it released?
  • Are lengths and size calculations checked before memory is used?
  • Are error and cancellation paths included in testing?
  • Which protections are actually enabled in the deployed build?

This category is a reason to follow the application's dependencies rather than stop at its primary language. A web request can reach code with very different memory-safety guarantees, and that boundary deserves its own review.

The final Next Steps entry considers another trust decision: accepting AI-generated code without sufficient review.

Sources and further reading

Building a SaaS? Explore my independent security reviews