Memory Safety and Language-Specific Risks

Relevant to C, C++, native extensions, firmware, and systems code:

5.1 Memory-safety concepts

Relevant to C, C++, native extensions, firmware, and systems code:

Buffer overflow and out-of-bounds access Use-after-free and double-free Dangling pointers and lifetime errors

Integer overflow/underflow and truncation Race conditions and data races

Unsafe type conversions and alignment assumptions Stack exhaustion and uncontrolled recursion Incorrect bounds or length calculations

5.2 C: check lengths before copying

Unsafe C APIs such as unbounded string copies can overflow destination buffers. Prefer APIs and patterns that explicitly track buffer sizes, validate lengths, and guarantee termination. Do not assume strncpy is automatically safe; it can leave a string unterminated and is easy to misuse.

Illustrative pattern:

_Compiled with GCC 11.2 with -Wall -Wextra -Werror -fsanitize=address,undefined, Ubuntu 22.04 (WSL2) — no warnings. The boundary tests follow in “Testing the copy routine with sanitizers”._

Caller obligations still matter: src_length must accurately describe readable source memory, and dst_capacity must describe the real destination allocation. In security-critical code, prefer APIs that carry buffer lengths and avoid ambiguous ownership.

Testing the copy routine with sanitizers The routine above was compiled with warnings promoted to errors and with AddressSanitizer and UndefinedBehaviorSanitizer enabled, then exercised at its boundaries:

Output (GCC 11.2 with -Wall -Wextra -Werror -fsanitize=address,undefined, Ubuntu 22.04 (WSL2); linked with copy_text exactly as shown above):

Seven characters fit in an 8-byte buffer (one byte is reserved for the terminator); eight are rejected and the destination is left as an empty string. The sanitizers reported nothing, which is evidence for these inputs only — a fuzzer would generate many more.

#include <stdbool.h>
#include <stddef.h>
#include <string.h>

bool copy_text(char *dst, size_t dst_capacity,
               const char *src, size_t src_length) {
    if (dst == NULL || src == NULL || dst_capacity == 0) {
        return false;
    }

    /* Reserve one byte for the terminating NUL. */
    if (src_length >= dst_capacity) {
        dst[0] = '\0';
        return false;
    }

    memcpy(dst, src, src_length);
    dst[src_length] = '\0';
    return true;
}
#include <stdio.h>
#include <string.h>
#include <stdbool.h>

bool copy_text(char *dst, size_t dst_capacity, const char *src, size_t src_length);

int main(void) {
    char dst[8];
    const char *cases[] = { "", "1234567", "12345678", "hello" };
    for (int i = 0; i < 4; i++) {
        bool ok = copy_text(dst, sizeof dst, cases[i], strlen(cases[i]));
        printf("len %zu -> %s, dst=\"%s\"\n", strlen(cases[i]), ok ? "copied" : "rejected", dst);
    }
    printf("zero capacity -> %s\n", copy_text(dst, 0, "a", 1) ? "copied" : "rejected");
    printf("NULL source -> %s\n", copy_text(dst, sizeof dst, NULL, 0) ? "copied" : "rejected");
    return 0;
}
len 0 -> copied, dst=""
len 7 -> copied, dst="1234567"
len 8 -> rejected, dst=""
len 5 -> copied, dst="hello"
zero capacity -> rejected
NULL source -> rejected

5.3 C and C++ best practices

Prefer RAII, smart pointers, containers, and standard-library abstractions in modern C++.

Minimize raw owning pointers and manual allocation.

Define ownership and lifetimes explicitly.

Check all size calculations for overflow before allocation.

Avoid undefined behavior; it invalidates assumptions about compiler output.

Use compiler warnings, sanitizers, static analysis, fuzzing, and hardened build options.

Keep parsing code small and defensive.

Separate privileged code from complex input parsing.

5.4 Integer overflow

A size expression such as count * element_size can overflow before allocation. Check bounds before multiplication, use suitable unsigned/signed types, and validate conversions. Signed overflow in C/C++ can be undefined behavior; unsigned arithmetic wraps modulo its range but can still cause security bugs.

Check the multiplication before allocating Output (GCC 11.2 with -Wall -Wextra -Werror -fsanitize=address,undefined, Ubuntu 22.04 (WSL2)):

__builtin_mul_overflow is a GCC/Clang built-in; C23 standardises the same idea as ckd_mul in <stdckdint.h>. Without the check, the product wraps to a small number and a much smaller buffer is allocated than the caller expects.

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

/* Returns NULL instead of allocating a truncated buffer when count * size overflows. */
void *alloc_array(size_t count, size_t size) {
    size_t total;
    if (__builtin_mul_overflow(count, size, &total)) {
        return NULL;
    }
    return malloc(total);
}

int main(void) {
    void *a = alloc_array(1000, sizeof(uint32_t));
    printf("1000 x 4 -> %s\n", a ? "allocated 4000 bytes" : "refused");
    free(a);

    void *b = alloc_array(SIZE_MAX / 2, 4);
    printf("SIZE_MAX/2 x 4 -> %s\n", b ? "allocated" : "refused (would overflow)");
    free(b);
    return 0;
}
1000 x 4 -> allocated 4000 bytes
SIZE_MAX/2 x 4 -> refused (would overflow)
sizecheck

5.5 Managed-language risks

Managed runtimes reduce many memory errors but do not eliminate:

Race conditions and TOCTOU Excessive resource consumption Insecure reflection or dynamic evaluation Dependency and configuration risks

Secret exposure in logs or crash reports Java example: avoid unsafe deserialization

Do not deserialize arbitrary attacker-controlled native object streams. Prefer a constrained data format, explicit schemas, bounded input size, and strict type/field validation. If a legacy serialization mechanism must remain, apply documented filtering and isolate the feature.

5.6 Rust and memory safety

Rust's ownership and borrowing model prevents many classes of memory unsafety in safe Rust, but application security still requires authorization, input validation, correct cryptography, dependency review, and careful auditing of unsafe blocks and FFI boundaries. A memory-safe language does not make business logic secure automatically.

5.7 Concurrency and race conditions

Race condition: result depends on timing of concurrent operations.

TOCTOU: time-of-check to time-of-use gap allows state to change after validation.

Use atomic operations, locks, transactions, immutable data, and safe APIs as appropriate.

Do not check file permissions and later open a path in a way that allows an attacker to swap the target.

Enforce single-use tokens and state transitions atomically.

Lab

Compile a small native parser with warnings and available address/undefined-behavior sanitizers. Test empty input, maximum lengths, truncated data, extreme numeric values, and malformed encodings. Fix defects and add regression tests.

Quiz

? Why is `strncpy(dst, src, sizeof dst)` not automatically safe?
- It is slower than `strcpy`. :: Speed is not the issue.
+ It may leave `dst` without a terminating NUL when `src` is long enough. :: Later string functions then read past the buffer.
- It cannot copy empty strings. :: It can.
- It allocates memory. :: It does not allocate.

? `count * element_size` is computed in `size_t` and wraps around. What is the risk?
+ A smaller buffer than expected is allocated, and later writes go out of bounds. :: The allocation size and the loop bound disagree.
- The program always crashes immediately. :: Often it does not, which makes the bug harder to find.
- Nothing — unsigned overflow is defined. :: Defined wrap-around can still produce a security bug.
- The compiler rejects the code. :: It compiles without complaint.

? Which statements about memory-safe languages are accurate? (select all that apply)
+ They remove many memory-corruption bugs in safe code. :: That is their main benefit.
- They prevent broken authorization. :: Business logic bugs remain.
+ `unsafe` blocks and FFI boundaries still need careful review. :: The compiler's guarantees stop at those boundaries.
- They make de
…