C Security Functions
In the C language, to improve code security, especially to prevent common security issues such as buffer overflow, the C11 standard introduced someSecure Functions, also known asAnnex Kstandard library functions. These secure functions are mainly enhanced versions of standard string and memory operation functions, providing better error detection and handling by adding parameters (such as buffer size).
Characteristics of security functions:
- Buffer Size CheckingAll secure functions require the size parameter of the destination buffer to be passed in to prevent buffer overflow.
- Return value checking: Most functions return
errno_tError codes of the type can be checked to determine whether the function executed successfully. - Better Error Handling: When the buffer size is insufficient or other problems occur, these functions return an error code and attempt to clear or initialize the output buffer.
Secure functions are well supported in compilers such as Visual Studio, but may not be available in some older compiler versions, so compatibility needs attention.
The following is a comparison of common secure functions in C and their corresponding traditional functions:
1. String operation security functions
strcpy_s: Security version of strcpy, copies the string and checks the target buffer size.
errno_t strcpy_s(char *dest, rsize_t destsz, const char *src);
strcat_sSecure version of strcat, appends the source string to the end of the destination string and checks the buffer size.
errno_t strcat_s(char *dest, rsize_t destsz, const char *src);
strncpy_s: Security version of strncpy, copies up to n characters and checks the buffer size.
errno_t strncpy_s(char *dest, rsize_t destsz, const char *src, rsize_t count);
strncat_sSecure version of strncat, appends at most n characters to the end of the destination string and checks the buffer size.
errno_t strncat_s(char *dest, rsize_t destsz, const char *src, rsize_t count);
strtok_sSecure version of strtok, introduces a context parameter to solve thread safety issues.
char *strtok_s(char *str, const char *delim, char **context);
2. Formatted output security functions
sprintf_sSecure version of sprintf, checks the buffer size when formatting output to a string.
int sprintf_s(char *buffer, rsize_t buffer_size, const char *format, ...);
snprintf_sSecure version of snprintf, limits the number of characters and checks the buffer size during formatted output.
int snprintf_s(char *buffer, rsize_t buffer_size, const char *format, ...);
vsprintf_sSecure version of vsprintf, accepts a va_list argument list and checks the buffer size.
int vsprintf_s(char *buffer, rsize_t buffer_size, const char *format, va_list argptr);
3. Memory operation security functions
memcpy_s: Security version of memcpy, checks the target buffer size when copying a memory region.
errno_t memcpy_s(void *dest, rsize_t destsz, const void *src, rsize_t count);
memmove_s: Security version of memmove, copies memory regions, allows overlap, and checks the target buffer size.
errno_t memmove_s(void *dest, rsize_t destsz, const void *src, rsize_t count);
memset_sSecure version of memset, fills the memory block with the specified character and checks the buffer size.
errno_t memset_s(void *dest, rsize_t destsz, int ch, rsize_t count);
4. Other common security functions
_itoa_sand_ultoa_sSecure version of integer conversion functions, checks the destination buffer size when converting integers to strings.
errno_t _itoa_s(int value, char *buffer, size_t buffer_size, int radix); errno_t _ultoa_s(unsigned long value, char *buffer, size_t buffer_size, int radix);
_strlwr_sand_strupr_sSecure versions that convert strings to lowercase or uppercase.
errno_t _strlwr_s(char *str, size_t numberOfElements); errno_t _strupr_s(char *str, size_t numberOfElements);
Example
The following are examples of using C secure functions for string operations and memory operations, demonstrating how they avoid common buffer overflow problems and provide a safer programming approach.
Example 1: strcpy_s and strcat_s
Example
#include <string.h>
int main() {
char dest[20]; // Destination buffer size is 20
const char *src = "Hello, World!";
// Use strcpy_s to copy src to dest
if (strcpy_s(dest, sizeof(dest), src) != 0) {
printf("strcpy_s failed!\n");
return 1; // Return error code
} else {
printf("After strcpy_s: %s\n", dest);
}
// Use strcat_s to append " C Language" to dest
const char *appendStr = " C Language";
if (strcat_s(dest, sizeof(dest), appendStr) != 0) {
printf("strcat_s failed!\n");
return 1; // Return error code
} else {
printf("After strcat_s: %s\n", dest);
}
return 0;
}
Output:
After strcpy_s: Hello, World! strcat_s failed!
In the above code, strcpy_s successfully copied the string "Hello, World!" to dest, but since dest has a size of 20, which is insufficient to hold "Hello, World! C Language", strcat_s will detect that the buffer is insufficient and return an error code.
Example 2: memcpy_s
Example
#include <string.h>
int main() {
char src[] = "Sensitive Data";
char dest[15]; // Destination buffer size is 15
// Use memcpy_s to copy data to dest
if (memcpy_s(dest, sizeof(dest), src, strlen(src) + 1) != 0) {
printf("memcpy_s failed!\n");
return 1; // Return error code
} else {
printf("After memcpy_s: %s\n", dest);
}
return 0;
}
Output:
After memcpy_s: Sensitive Data
In this example, memcpy_s checks whether the destination buffer dest is large enough to accommodate the data in src, including the null character at the end of the string. If destsz is smaller than strlen(src) + 1, the function returns an error and does not perform the memory copy.
Example 3: strtok_s
Example
#include <string.h>
int main() {
char str[] = "apple,orange,banana";
char *token;
char *context = NULL;
// Use strtok_s to split the string
token = strtok_s(str, ",", &context);
while (token != NULL) {
printf("Token: %s\n", token);
token = strtok_s(NULL, ",", &context);
}
return 0;
}
Output:
Token: apple Token: orange Token: banana
In this example, strtok_s uses the context parameter to save context information when splitting the string, thus avoiding the thread-unsafe issue of strtok.
Example 4: sprintf_s
Example
int main() {
char buffer[50];
int num = 42;
const char *str = "Hello";
// Use sprintf_s to format the string and check the buffer size
if (sprintf_s(buffer, sizeof(buffer), "Number: %d, String: %s", num, str) < 0) {
printf("sprintf_s failed!\n");
return 1; // Return error code
} else {
printf("Formatted String: %s\n", buffer);
}
return 0;
}
Output:
Formatted String: Number: 42, String: Hello
Here, sprintf_s accepts the buffer size as a parameter when formatting the string. If the formatted string exceeds the size of buffer, the function returns an error, thereby avoiding buffer overflow.
The above examples demonstrate ways to use C security functions for string copying, concatenation, memory copying, string splitting, and formatted output. These functions provide buffer size checks, significantly improving code security.
Reference Manual
Secure functions are mainly designed to prevent buffer overflow. This type of vulnerability usually stems from functions not checking the size of the destination buffer.
The current mainstream secure function standards mainly come from Annex K (Bounds-checking interfaces) of the C11 standard, as well as enhanced versions provided by various operating systems (such as MSVC CRT on Windows).
The following table shows a reference table of C secure functions.
1. String Processing
This is the area most prone to overflow, and secure functions usually require the size of the destination buffer to be passed in.
| Original function (unsafe) | Safe Replacement Functions | Main improvements |
|---|---|---|
strcpy |
strcpy_s |
Adds a destination buffer size parameter; if the source string is too long, a constraint handler is called. |
strcat |
strcat_s |
Adds a parameter for the remaining space size of the destination buffer to prevent out-of-bounds access during concatenation. |
strncpy |
strncpy_s |
Ensure the target string ends with\0at the end, and added runtime error checking. |
strtok |
strtok_s |
Introduces a context pointer, making itThread SafetyAnd more robust. |
strlen |
strnlen_s |
Add a maximum check length to prevent infinite loops in bad data without\0a null terminator. |
2. Formatted input/output
Unsafe formatting functions may lead to format string vulnerabilities or buffer overflows.
| Original function (unsafe) | Safe Replacement Functions | Main improvements |
|---|---|---|
sprintf |
snprintf / sprintf_s |
snprintfLimit the number of characters written;sprintf_sCheck the validity of the format string. |
vsprintf |
vsnprintf_s |
Adds buffer size limits, suitable for variable argument lists. |
scanf |
scanf_s |
Pair%sand%cconversion specifiers, etc.,ForceRequires providing the buffer size. |
fscanf |
fscanf_s |
Add a length limit when reading file input. |
sscanf |
sscanf_s |
Add a length limit when reading from a string. |
3. Standard input/output
| Original function (unsafe) | Safe Replacement Functions | Main improvements |
|---|---|---|
gets |
gets_s / fgets |
getsDeprecated in C11.gets_sMust specify the read upper limit. |
tmpnam |
tmpnam_s |
Adds checking of the buffer size for generated temporary file names. |
4. Memory Operations
| Original function (unsafe) | Safe Replacement Functions | Main improvements |
|---|---|---|
memcpy |
memcpy_s |
Adds destination buffer size checking. If overlap or overflow occurs, an error is returned. |
memmove |
memmove_s |
Adds size limit checking when handling overlapping memory regions. |
memset |
memset_s |
Ensures that the compiler does not skip memory clearing operations due to optimization (commonly used to clear sensitive data such as passwords). |