Many library functions accept a string or wide string argument with the constraint that the string they receive is properly null-terminated. Passing a character sequence or wide character sequence that is not null-terminated to such a function can result in accessing memory that is outside the bounds of the object. Do not pass a character sequence or wide character sequence that is not null-terminated to a library function that expects a string or wide string argument.
Noncompliant Code Example
This code example is noncompliant because the character sequence str
will not be null-terminated when passed as an argument to printf()
. See STR11-C. Do not specify the bound of a character array initialized with a string literal.
Code Block |
---|
|
char str[3] = "abc";
printf("%s\n", str);
|
Compliant Solution
This compliant solution does not specify the bound of a character array in the array declaration. If the array bound is omitted, the compiler allocates sufficient storage to store the entire string literal, including the terminating null character.
Code Block |
---|
|
char str[3] = "abc";
printf("%s\n", str); |
Noncompliant Code Example
This code example is noncompliant because the wide character sequence cur_msg
will not be null-terminated when passed to wcslen()
. This will occur if lessen_memory_usage()
is invoked while cur_msg_size
still has its initial value of 1024.
Code Block |
---|
|
wchar_t *cur_msg = NULL;
size_t cur_msg_size = 1024;
size_t cur_msg_len = 0;
void lessen_memory_usage(void) {
wchar_t *temp;
size_t temp_size;
/* ... */
if (cur_msg != NULL) {
temp_size = cur_msg_size / 2 + 1;
temp = realloc(cur_msg, temp_size * sizeof(wchar_t));
// temp & cur_msg might not be null-terminated
if (temp == NULL) {
/* Handle error */
}
cur_msg = temp;
cur_msg_size = temp_size;
cur_msg_len = wcslen(cur_msg); // error
}
} |
Compliant Solution
In this compliant solution, cur_msg
will always be null-terminated when passed to wcslen()
.
Code Block |
---|
|
wchar_t *cur_msg = NULL;
size_t cur_msg_size = 1024;
size_t cur_msg_len = 0;
void lessen_memory_usage(void) {
wchar_t *temp;
size_t temp_size;
/* ... */
if (cur_msg != NULL) {
temp_size = cur_msg_size / 2 + 1;
temp = realloc(cur_msg, temp_size * sizeof(wchar_t));
// temp & cur_msg might not be null-terminated
if (temp == NULL) {
/* Handle error */
}
cur_msg = temp;
// cur_msg now properly null-terminated
cur_msg[temp_size - 1] = L'\0';
cur_msg_size = temp_size;
cur_msg_len = wcslen(cur_msg);
}
} |
Character arrays must be null-terminated before they may be safely passed as arguments to standard string-handling functions, such as strcpy()
or strlen()
. These functions, as well as other string-handling functions defined by the C Standard, depend on the existence of the string's null-termination character to determine the length of the string. Likewise, character sequences must be null-terminated before iterating on the sequence where the termination condition of the loop depends on the existence of a null-termination character within the memory allocated for the sequence, as in the following example:
Code Block |
---|
|
void func(void) {
char ntbs[16];
for (size_t i = 0; i < sizeof(ntbs); ++i) {
if (ntbs[i] == '\0') {
break;
}
}
|
...
Noncompliant Code Example (strncpy()
)
...
Code Block |
---|
|
#include <string.h>
enum { NTBSSTR_SIZE = 32 };
size_t func(const char *source) {
char ntbsstr[NTBSSTR_SIZE];
ntbsstr[sizeof(ntbsstr) - 1] = '\0';
strncpy(ntbsstr, source, sizeof(ntbsstr));
return strlen(ntbsstr);
}
|
Compliant Solution (Truncation)
...
Code Block |
---|
|
#include <string.h>
enum { NTBSSTR_SIZE = 32 };
size_t func(const char *source) {
char ntbsstr[NTBSSTR_SIZE];
strncpy(ntbsstr, source, sizeof(ntbsstr) - 1);
ntbsstr[sizeof(ntbsstr) - 1] = '\0';
return strlen( ntbsstr);
} |
Compliant Solution (Copy without Truncation)
...
Code Block |
---|
|
#include <string.h>
enum { NTBSSTR_SIZE = 32 };
size_t func(const char *source) {
char ntbsstr[NTBSSTR_SIZE];
if (source) {
if (strlen(source) < sizeof(ntbsstr)) {
strcpy(ntbsstr, source);
} else {
/* Handle string-too-large */
}
} else {
/* Handle null pointer */
}
return strlen(ntbsstr);
} |
Compliant Solution (strncpy_s(),
C11 Annex K)
...
Code Block |
---|
|
#define __STDC_WANT_LIB_EXT1__ 1
#include <string.h>
enum { NTBSSTR_SIZE = 32 };
size_t func(const char *source) {
char a[NTBSSTR_SIZE];
if (source) {
errno_t err = strncpy_s(a, sizeof(a), source, 5);
if (err != 0) {
/* Handle error */
}
} else {
/* Handle null pointer */
}
return strlen_s(s, sizeof(a));
} |
Noncompliant Code Example (realloc()
)
One method to decrease memory usage in critical situations when all available memory has been exhausted is to use the realloc()
function to halve the size of message strings. The standard realloc()
function has no concept of strings. As a result, if realloc()
is called to decrease the memory allocated for a string, the null-termination character may be truncated.
This noncompliant code example fails to ensure that cur_msg
is properly null-terminated:
Code Block |
---|
|
#include <stdlib.h>
char *cur_msg = NULL;
size_t cur_msg_size = 1024;
void lessen_memory_usage(void) {
char *temp;
size_t temp_size;
if (cur_msg != NULL) {
temp_size = cur_msg_size / 2 + 1;
temp = realloc(cur_msg, temp_size);
if (temp == NULL) {
/* Handle error */
}
cur_msg = temp;
cur_msg_size = temp_size;
fputs(stderr, cur_msg);
}
} |
Because realloc()
does not guarantee that the character sequence is properly null-terminated, and the function subsequently passes cur_msg
to a library function (fputs()
) that expects null-termination, the result is undefined behavior.
Compliant Solution (realloc()
)
In this compliant solution, the lessen_memory_usage()
function ensures that the resulting string is always properly null-terminated:
Code Block |
---|
|
#include <stdlib.h>
char *cur_msg = NULL;
size_t cur_msg_size = 1024;
void lessen_memory_usage(void) {
char *temp;
size_t temp_size;
if (cur_msg != NULL) {
temp_size = cur_msg_size / 2 + 1;
temp = realloc(cur_msg, temp_size);
if (temp == NULL) {
/* Handle error */
}
cur_msg = temp;
cur_msg_size = temp_size;
/* Ensure string is null-terminated */
cur_msg[cur_msg_size - 1] = '\0';
}
fputs(stderr, cur_msg);
} |
Risk Assessment
Failure to properly null-terminate a character sequence that is passed to a library function that expects a string can result in buffer overflows and the execution of arbitrary code with the permissions of the vulnerable process. Null-termination errors can also result in unintended information disclosure.
...