Safeguard
Application Security•

ASVS 5.0 V1.4.1: Use Memory Safe Strings and Copies

ASVS V1.4.1 requires memory safe strings, safer copies and careful pointer use to stop overflows. See unsafe versus safe C code and how to test with sanitizers.

Safeguard Security Team
5 min read

ASVS 5.0 V1.4.1 (Level 2) requires that the application uses memory safe string handling, safer memory copy functions and careful pointer arithmetic to detect or prevent stack, buffer and heap overflows. It matters for anything written in C or C++, and for native extensions and unsafe blocks inside managed languages.

Why it matters

A buffer overflow lets an attacker overwrite memory next to a buffer: return addresses, function pointers, flags or other data. The result ranges from a crash to remote code execution. Memory errors consistently account for a large share of serious vulnerabilities in native code, and they often live in parsers that handle files, images, network packets and fonts, which is exactly where untrusted data arrives.

Languages like Rust, Go, Java, C#, Python and JavaScript check bounds for you. The requirement is mostly about two situations: native code you maintain, and the native libraries your managed code calls.

How to implement it

The first choice is to avoid unmanaged code where you can. Write new parsing code in a memory safe language, and keep C and C++ for the parts that truly need it.

Where you do write C, replace unbounded calls with bounded ones and check every size.

/* unsafe: no bounds, overflows when name is longer than 15 bytes */
char buf[16];
strcpy(buf, name);

/* safer: bounded copy that always terminates, and a visible truncation check */
char buf[16];
int n = snprintf(buf, sizeof buf, "%s", name);
if (n < 0 || (size_t)n >= sizeof buf) {
    return ERR_TOO_LONG;
}

Other habits that help:

  • Avoid gets, strcpy, strcat, sprintf and scanf("%s"). Use snprintf, memcpy with explicit checked lengths, or strlcpy where available.
  • Validate lengths before arithmetic, and check for integer overflow: if (count > SIZE_MAX / elem_size) return ERR;.
  • Prefer containers with bounds checks in C++, such as std::string, std::vector and std::span, and use .at() where indexing untrusted values.
  • Keep pointer arithmetic out of parsing code. Use a length and offset pair, and check offset + len <= total.
  • Initialize memory and free it exactly once, setting the pointer to NULL after free.

Enable compiler and runtime defenses as a safety net, not as the fix: stack protectors (-fstack-protector-strong), -D_FORTIFY_SOURCE=2 (or 3), position independent executables, -Wformat-security and RELRO linking.

How to test it

Build a test binary with AddressSanitizer and UndefinedBehaviorSanitizer, then run it against your test suite and a fuzzer:

clang -g -O1 -fsanitize=address,undefined -fno-omit-frame-pointer parser.c -o parser_asan
./parser_asan < malformed_input.bin

Feed oversized fields, zero length fields, negative lengths and truncated files. A sanitizer report means a real bug. Coverage guided fuzzing (libFuzzer or AFL++) finds overflow bugs in parsers far faster than hand written cases, and a fuzz target is often just a few lines.

Static analysis flags banned functions and size mismatches across the repository, and it works well as a pull request check. The demo below shows a scan highlighting an unsafe call.

Click through it at your own pace, or open it full screen.

Do not forget the supply chain side. Image, font and compression libraries are native code too, and their memory errors become yours when you link them. Run the free in browser dependency scan at gold.safeguard.sh/scan to see which of your native dependencies carry known advisories, and use reachability analysis to learn whether your code even calls the affected function.

Common mistakes

  • Replacing strcpy with strncpy and forgetting that it may not null terminate.
  • Computing buffer sizes with sizeof on a pointer, which returns the pointer size.
  • Checking the length after the copy.
  • Trusting a length field read from the input itself.
  • Mixing signed and unsigned values in size comparisons.
  • Leaving sanitizers and fuzzing out of continuous integration.

Try it yourself

Take a small C function that copies a name into a fixed buffer, compile it with sanitizers, and break it on purpose.

#include <stdio.h>
#include <string.h>

int main(int argc, char **argv) {
    char buf[8];
    if (argc > 1) strcpy(buf, argv[1]);   /* deliberately unsafe */
    printf("%s\n", buf);
    return 0;
}

Compile with clang -g -fsanitize=address demo.c -o demo and run ./demo AAAAAAAAAAAAAAAAAAAA. AddressSanitizer stops the program and prints a stack buffer overflow report with the line number. Now replace the copy with the snprintf version from the article and run the same input. The report disappears, and the function rejects or truncates cleanly.

Last, write a three line fuzz target for one of your parsers and let it run for ten minutes in continuous integration. The first crash is usually a surprise, and the fix is often a missing length check.

Quick check

Is strncpy a safe replacement for strcpy?

Not by itself. It does not guarantee a null terminator when the source is too long, so you must terminate and check lengths yourself, or use snprintf.

Which tools find overflows during testing?

AddressSanitizer for runtime detection, plus fuzzers such as libFuzzer or AFL++ to generate malformed inputs that trigger them.

Sources

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.