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 returnerrno_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 <stdio.h>
#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 <stdio.h>
#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 <stdio.h>
#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

#include <stdio.h>

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).
other extensions