Help us improve
Share bugs, ideas, or general feedback.
From prodsec-skills
Apply when reviewing or writing C/C++ code that uses memory or string operations. Identifies banned functions, provides safe replacements, compiler hardening flags, and refactoring examples.
npx claudepluginhub redhatproductsecurity/prodsec-skills --plugin prodsec-skillsHow this skill is triggered — by the user, by Claude, or both
Slash command
/prodsec-skills:safe-c-functionsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Actively identify, flag, and provide secure refactoring options for insecure memory and string functions. When generating new code, always default to the safest function available.
Detects function-local misuse of memory and resource APIs in C, C++, and Rust unsafe — unchecked allocations, double-frees, uninitialized locks, and fd leaks across exec. Use when writing or reviewing low-level code that calls malloc, mmap, pthread_mutex, fopen, or raw FFI pointer APIs.
Guides choosing memory-safe languages and reviewing C/C++ code for memory corruption vulnerabilities. Provides threat context and mitigation strategies.
Use AddressSanitizer to detect memory safety bugs in C/C++ programs. Identifies use-after-free, buffer overflow, memory leaks, and other memory errors.
Share bugs, ideas, or general feedback.
Actively identify, flag, and provide secure refactoring options for insecure memory and string functions. When generating new code, always default to the safest function available.
gets() -- Critical. No bounds checking whatsoever. Always replace with:
fgets(char *str, int n, FILE *stream)strcpy() -- High risk. Copies until null terminator with no bounds check. Replace with:
snprintf(), strcpy_s() (C11 Annex K), or strncpy() (with manual null termination)strcat() -- High risk. Appends with no bounds check. Replace with:
snprintf(), strcat_s() (C11 Annex K), or strncat() (with careful handling)sprintf() / vsprintf() -- High risk. No output buffer bounds check. Replace with:
snprintf(), vsprintf_s() (C11 Annex K)scanf() family -- %s without width limit causes buffer overflows.
scanf("%127s", buffer)fgets() then parse with sscanf()strtok() -- Not reentrant or thread-safe (static internal buffer). Replace with:
strtok_r() (POSIX) or strtok_s() (C11 Annex K)memcpy() / memmove() -- Not inherently insecure but common source of bugs with miscalculated sizes. Prefer memcpy_s() / memmove_s() when available.
| Banned | Safe Replacement |
|---|---|
memcpy() | memcpy_s() |
memset() | memset_s() |
memmove() | memmove_s() |
memcmp() | memcmp_s() |
bzero() | memset_s() |
memzero() | memset_s() |
| Banned | Safe Replacement |
|---|---|
strstr() | strstr_s() |
strtok() | strtok_s() |
strcpy() | strcpy_s() |
strcmp() | strcmp_s() |
strlen() | strnlen_s() |
strcat() | strcat_s() |
sprintf() | snprintf() |
gets(), strcpy(), strcat(), or sprintf().snprintf() for string formatting and concatenation.fgets() for reading string input from files or stdin.-fstack-protector-all or -fstack-protector-strong-fsanitize=address (development builds)-D_FORTIFY_SOURCE=2 (runtime bounds checks for unsafe functions)-Wformat -Wformat-securitystrcpy// UNSAFE
char destination[64];
strcpy(destination, source_string);
// SAFE
char destination[64];
snprintf(destination, sizeof(destination), "%s", source_string);
strncpy Usage// UNSAFE -- may not null-terminate if strlen(source) >= 10
char dest[10];
strncpy(dest, source, sizeof(dest));
// SAFE -- explicit null termination
char dest[10];
strncpy(dest, source, sizeof(dest) - 1);
dest[sizeof(dest) - 1] = '\0';
scanf// UNSAFE
char user_name[32];
scanf("%s", user_name);
// SAFE
char user_name[32];
if (fgets(user_name, sizeof(user_name), stdin)) {
user_name[strcspn(user_name, "\n")] = 0;
}
// UNSAFE
char dest[256];
strcpy(dest, src);
// SAFE
char dest[256];
errno_t result = strcpy_s(dest, sizeof(dest), src);
if (result != 0) {
// Handle error: src too long or invalid parameters
return ERROR;
}
// UNSAFE
char buffer[256] = "prefix_";
strcat(buffer, suffix);
// SAFE
char buffer[256] = "prefix_";
errno_t result = strcat_s(buffer, sizeof(buffer), suffix);
if (result != 0) {
return ERROR;
}
// UNSAFE
memcpy(dest, src, size);
// SAFE
errno_t result = memcpy_s(dest, dest_max_size, src, size);
if (result != 0) {
return ERROR;
}
// UNSAFE
char *token = strtok(str, delim);
// 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);
}
// WRONG -- using source size instead of destination size
strcpy_s(dest, strlen(src), src);
// CORRECT -- using destination buffer size
strcpy_s(dest, sizeof(dest), src);
// WRONG -- error not checked
strcpy_s(dest, sizeof(dest), src);
// CORRECT -- checking return value
if (strcpy_s(dest, sizeof(dest), src) != 0) {
// Handle error
}
// WRONG -- sizeof(char*) = 8, not the buffer size
void func(char *buffer) {
strcpy_s(buffer, sizeof(buffer), src);
}
// CORRECT -- pass buffer size as parameter
void func(char *buffer, size_t buffer_size) {
strcpy_s(buffer, buffer_size, src);
}
memcpy, memset, memmove, memcmp, bzero)strcpy, strcat, strcmp, strlen, sprintf, strstr, strtok)*_s() variants with proper size parameterssizeof() or known limitserrno_t return values handledrsize_t dmax parameters are correct