12 KiB
description, languages, alwaysApply
| description | languages | alwaysApply | ||
|---|---|---|---|---|
| Safe C Functions and Memory and String Safety Guidelines |
|
false |
rule_id: codeguard-0-safe-c-functions
Prioritize Safe Memory and String Functions in C/C++
When processing C or C++ code, your primary directive is to ensure memory safety. Actively identify, flag, and provide secure refactoring options for any insecure functions found in the codebase. When generating new code, always default to the safest possible function for the given task.
1. Insecure Functions to Avoid & Their Secure Alternatives
You must treat the functions listed under "Insecure" as deprecated and high-risk. Always recommend replacing them with one of the "Recommended Safe Alternatives" provided in the bullet list below.
• Never use gets() - This is a critical security risk. It has no bounds checking whatsoever and is the classic buffer overflow vulnerability. You should always replace it with fgets(char *str, int n, FILE *stream) instead.
• Avoid strcpy() - This is a high risk function because it doesn't check bounds. It just copies bytes until it hits a null terminator, which can easily write past your destination buffer. Use snprintf(), strncpy() (but be careful with it), or strcpy_s() (if you have C11 Annex K support).
• Don't use strcat() - Another high risk function with no bounds checking. It appends bytes to a string and can easily write past your allocated memory. Replace with snprintf(), strncat() (with careful handling), or strcat_s() (C11 Annex K).
• Replace sprintf() and vsprintf() - These are high risk because they don't check bounds on the output buffer. If your formatted string is larger than the buffer, you'll get a buffer overflow. Use snprintf(), snwprintf(), or vsprintf_s() (C11 Annex K) instead.
• Be careful with scanf() family - This is a medium risk. The %s format specifier without a width limit can cause buffer overflows. Here's what you should do:
- Use width specifiers like
scanf("%127s", buffer) - Even better: Read the line with
fgets()and parse it withsscanf()
• Avoid strtok() - This is a medium risk because it's not reentrant or thread-safe. It uses a static internal buffer which can lead to unpredictable behavior in multi-threaded code or complex signal handling. Use strtok_r() (POSIX) or strtok_s() (C11 Annex K) instead.
• Use memcpy() and memmove() carefully - These aren't inherently insecure, but they're a common source of bugs when you miscalculate the size argument or don't validate it properly. Here's what you should do:
- Double-check your size calculations
- Prefer
memcpy_s()(C11 Annex K) when available - Use
memmove()if source and destination buffers might overlap
2. Actionable Implementation Guidelines
For New Code Generation:
-
NEVER generate code that uses
gets(),strcpy(),strcat(), orsprintf(). -
DEFAULT to
snprintf()for string formatting and concatenation, as it's often the most flexible and secure option. -
DEFAULT to
fgets()for reading string input from files or standard input.
For Code Analysis and Refactoring:
-
Identify: Scan the code and flag every instance of a function from the "Insecure" column.
-
Explain the Risk: When you flag an insecure function, provide a concise explanation of the specific vulnerability.
- Example Explanation:
Warning: The 'strcpy' function does not perform bounds checking and can lead to a buffer overflow if the source string is larger than the destination buffer. This is a common security vulnerability.
- Example Explanation:
-
Provide Context-Aware Replacements: Your suggestion must be a drop-in, safe replacement that considers the context of the surrounding code.
Use Compiler Flags:
Enable these protective compiler flags to catch buffer overflow vulnerabilities at compile time and runtime:
- Stack Protection: Use
-fstack-protector-allor-fstack-protector-strongto detect stack buffer overflows - Address Sanitizer: Use
-fsanitize=addressduring development to catch memory errors - Object Size Checking (OSC): Use
-D_FORTIFY_SOURCE=2to enable runtime checks for buffer overflows in functions likestrcpy,strcat,sprintf, etc. This adds bounds checking to many of the unsafe functions mentioned above - Format String Protection: Use
-Wformat -Wformat-securityto catch format string vulnerabilities
3. Refactoring Examples
Your suggestions should be concrete and actionable.
Example 1: Replacing strcpy
-
Original Unsafe Code:
char destination[64]; strcpy(destination, source_string); -
Your Suggested Refactoring:
char destination[64]; snprintf(destination, sizeof(destination), "%s", source_string); -
Your Explanation:
Replaced 'strcpy' with 'snprintf' to ensure that no more than 63 characters plus a null terminator are written to the destination buffer, preventing a potential buffer overflow.
Example 2: Correcting strncpy Usage
The strncpy function is a common but imperfect replacement. It may not null-terminate the destination buffer. If you must use it or see it used, you must enforce correct handling.
-
Original (Potentially Unsafe)
strncpy:// This is unsafe if strlen(source) >= 10 char dest[10]; strncpy(dest, source, sizeof(dest)); -
Your Corrected Suggestion:
char dest[10]; strncpy(dest, source, sizeof(dest) - 1); dest[sizeof(dest) - 1] = '\0'; -
Your Explanation:
Added an explicit null termination for 'strncpy'. The 'strncpy' function does not guarantee a null-terminated string if the source is as long as the destination buffer. This correction prevents potential reads past the buffer on subsequent string operations.
Example 3: Securing scanf
-
Original Unsafe Code:
char user_name[32]; printf("Enter your name: "); scanf("%s", user_name); -
Your Suggested Refactoring:
char user_name[32]; printf("Enter your name: "); if (fgets(user_name, sizeof(user_name), stdin)) { // Optional: Remove trailing newline character from fgets user_name[strcspn(user_name, "\n")] = 0; } -
Your Explanation:
Replaced 'scanf("%s", ...)' with 'fgets()' to read user input. 'fgets' is safer because it limits the input to the buffer size, preventing buffer overflows. The original 'scanf' had no such protection.
Memory and String Safety Guidelines
Unsafe Memory Functions - FORBIDDEN
NEVER use these unsafe memory functions that don't check input parameter boundaries:
Banned Memory Functions:
memcpy()→ Usememcpy_s()memset()→ Usememset_s()memmove()→ Usememmove_s()memcmp()→ Usememcmp_s()bzero()→ Usememset_s()memzero()→ Usememset_s()
Safe Memory Function Replacements:
// Instead of: memcpy(dest, src, count);
errno_t result = memcpy_s(dest, dest_size, src, count);
if (result != 0) {
// Handle error
}
// Instead of: memset(dest, value, count);
errno_t result = memset_s(dest, dest_size, value, count);
// Instead of: memmove(dest, src, count);
errno_t result = memmove_s(dest, dest_size, src, count);
// Instead of: memcmp(s1, s2, count);
int indicator;
errno_t result = memcmp_s(s1, s1max, s2, s2max, count, &indicator);
if (result == 0) {
// indicator contains comparison result: <0, 0, or >0
}
Unsafe String Functions - FORBIDDEN
NEVER use these unsafe string functions that can cause buffer overflows:
Banned String Functions:
strstr()→ Usestrstr_s()strtok()→ Usestrtok_s()strcpy()→ Usestrcpy_s()strcmp()→ Usestrcmp_s()strlen()→ Usestrnlen_s()strcat()→ Usestrcat_s()sprintf()→ Usesnprintf()
Safe String Function Replacements:
// String Search
errno_t strstr_s(char *dest, rsize_t dmax, const char *src, rsize_t slen, char **substring);
// String Tokenization
char *strtok_s(char *dest, rsize_t *dmax, const char *src, char **ptr);
// String Copy
errno_t strcpy_s(char *dest, rsize_t dmax, const char *src);
// String Compare
errno_t strcmp_s(const char *dest, rsize_t dmax, const char *src, int *indicator);
// String Length (bounded)
rsize_t strnlen_s(const char *str, rsize_t strsz);
// String Concatenation
errno_t strcat_s(char *dest, rsize_t dmax, const char *src);
// Formatted String (always use size-bounded version)
int snprintf(char *s, size_t n, const char *format, ...);
Implementation Examples:
Safe String Copy Pattern:
// Bad - unsafe
char dest[256];
strcpy(dest, src); // Buffer overflow risk!
// Good - safe
char dest[256];
errno_t result = strcpy_s(dest, sizeof(dest), src);
if (result != 0) {
// Handle error: src too long or invalid parameters
EWLC_LOG_ERROR("String copy failed: %d", result);
return ERROR;
}
Safe String Concatenation Pattern:
// Bad - unsafe
char buffer[256] = "prefix_";
strcat(buffer, suffix); // Buffer overflow risk!
// Good - safe
char buffer[256] = "prefix_";
errno_t result = strcat_s(buffer, sizeof(buffer), suffix);
if (result != 0) {
EWLC_LOG_ERROR("String concatenation failed: %d", result);
return ERROR;
}
Safe Memory Copy Pattern:
// Bad - unsafe
memcpy(dest, src, size); // No boundary checking!
// Good - safe
errno_t result = memcpy_s(dest, dest_max_size, src, size);
if (result != 0) {
EWLC_LOG_ERROR("Memory copy failed: %d", result);
return ERROR;
}
Safe String Tokenization Pattern:
// Bad - unsafe
char *token = strtok(str, delim); // Modifies original string unsafely
// Good - safe
char *next_token = NULL;
rsize_t str_max = strnlen_s(str, MAX_STRING_SIZE);
char *token = strtok_s(str, &str_max, delim, &next_token);
while (token != NULL) {
// Process token
token = strtok_s(NULL, &str_max, delim, &next_token);
}
Memory and String Safety Code Review Checklist:
Pre-Code Review (Developer):
- No unsafe memory functions (
memcpy,memset,memmove,memcmp,bzero) - No unsafe string functions (
strcpy,strcat,strcmp,strlen,sprintf,strstr,strtok) - All memory operations use
*_s()variants with proper size parameters - Buffer sizes are correctly calculated using
sizeof()or known limits - No hardcoded buffer sizes that could change
Code Review (Reviewer):
- Memory Safety: Verify all memory operations use safe variants
- Buffer Bounds: Confirm destination buffer sizes are properly specified
- Error Handling: Check that all
errno_treturn values are handled - Size Parameters: Validate that
rsize_t dmaxparameters are correct - String Termination: Ensure strings are properly null-terminated
- Length Validation: Check that source string lengths are validated before operations
Static Analysis Integration:
- Enable compiler warnings for unsafe function usage
- Use static analysis tools to detect unsafe function calls
- Configure build system to treat unsafe function warnings as errors
- Add pre-commit hooks to scan for banned functions
Common Pitfalls and Solutions:
Pitfall 1: Wrong Size Parameter
// Wrong - using source size instead of destination size
strcpy_s(dest, strlen(src), src); // WRONG!
// Correct - using destination buffer size
strcpy_s(dest, sizeof(dest), src); // CORRECT
Pitfall 2: Ignoring Return Values
// Wrong - ignoring potential errors
strcpy_s(dest, sizeof(dest), src); // Error not checked
// Correct - checking return value
if (strcpy_s(dest, sizeof(dest), src) != 0) {
// Handle error appropriately
}
Pitfall 3: Using sizeof() on Pointers
// Wrong - sizeof pointer, not buffer
void func(char *buffer) {
strcpy_s(buffer, sizeof(buffer), src); // sizeof(char*) = 8!
}
// Correct - pass buffer size as parameter
void func(char *buffer, size_t buffer_size) {
strcpy_s(buffer, buffer_size, src);
}
You must always explain how this rule was applied and why it was applied.