====== Dangerous Standard Library Functions and Safer Alternatives ====== The previous chapters explained different buffer overflow exploitation techniques. However, the overflows themselves did not happen in self-written code, but in standard library functions which were called with insufficiently validated parameters. We can see that one does not only need to be careful about writing safe code but also about using provided functions that make assumptions about the passed input. The standard library of the C programming language builds the foundation for larger applications but contains several functions which expect the user to pass only validated parameters and can - if these validations do not happen - cause buffer overflows(([[https://wiki.sei.cmu.edu/confluence/pages/viewpage.action?pageId=87152038|SEI CERT C Coding Standard - Rule 07. Characters and Strings (STR)]])). As an example of handling this issue in a real-world project, Git provides a header file to ban specific functions of the standard library in its source code(([[https://github.com/git/git/blob/be7935ed8bff19f481b033d0d242c5d5f239ed50/banned.h|git/banned.h at master · git/git]])). ===== Functions Writing Memory of Unspecified Length ===== Following functions of the C standard library are easily used incorrectly as they do not receive any information about the size of the destination buffer. Instead, they expect the user to take care of sufficient buffer sizes and correctly terminated strings. Each function description includes a demonstration of incorrect usage. ==== gets ==== [[c:lib:stdio:gets|gets()]] reads a string from the standard input and writes it to the passed buffer. It reads until a newline or EOF occurs and accepts all other characters (including ''\0''). There is no possibility to restrict the length of the input. This implies that every usage of ''gets()'' is vulnerable to a buffer overflow. // gcc -std=c99 gets.c #include int main() { char buffer[10]; gets(buffer); return 0; } :!: As there is no way to use ''gets()'' safely, it was removed from the C standard with C11. ==== strcpy / wcscpy ==== [[c:lib:string:strcpy|strcpy()]] copies a string to another memory location. The destination buffer might be smaller than the source buffer, causing an overflow at the location of the destination buffer. // gcc strcpy.c #include int main() { char dst_buffer[10]; char const *src_buffer = "This string is too long for the dst_buffer"; strcpy(dst_buffer, src_buffer); return 0; } With [[c:lib:wchar:wcscpy|wcscpy()]], the same vulnerability applies to wide strings. ==== strcat / wcscat ==== [[c:lib:string:strcat|strcat()]] concatenates two strings. The destination buffer might be too small to also contain the content of the source buffer in addition to the existing string. Similar to ''strcpy()'', the destination buffer can potentially be overflown. // gcc strcat.c #include int main() { char dst_buffer[10] = "This "; char const *src_buffer = "string is too long for dst_buffer"; strcat(dst_buffer, src_buffer); return 0; } With [[c:lib:wchar:wcscat|wcscat()]], the same vulnerability applies to wide strings. ==== scanf ==== [[c:lib:stdio:scanf|scanf()]] and its related functions ([[c:lib:stdio:sscanf|sscanf()]], [[c:lib:stdio:fscanf|fscanf()]], etc.) read formatted input. By default, there is no length restriction on the input which has similar effects as the ''gets()'' function. // gcc scanf.c #include int main() { char buffer[10]; scanf("%s", buffer); return 0; } ==== sprintf / vsprintf ==== [[c:lib:stdio:sprintf|sprintf()]] and [[c:lib:stdio:vsprintf|vsprintf()]] write a formatted string to a buffer. By default, there is no length restriction on the generated string written to the destination buffer. // gcc sprintf.c #include int main() { char buffer[10]; sprintf(buffer, "Hallo %s!\n", "World"); return 0; } ==== wctomb / wcrtomb ==== [[c:lib:stdlib:wctomb|wctomb()]] converts a [[theory:encoding:widechar|wide character]] to [[theory:encoding:multibyte|multibyte]] encoding. The developer needs to make sure that at least ''MB_CUR_MAX'' bytes are available in the destination buffer, otherwise it might overflow depending on the used encoding scheme. // gcc wctomb.c #include #include int main() { wchar_t wc = L'\u00df'; char buffer[1]; // not large enough setlocale(LC_ALL, "en_US.utf8"); wctomb(buffer, wc); return 0; } [[c:lib:stdlib:wcrtomb|wcrtomb()]] behaves like ''wctomb()'' but uses a narrow multibyte encoding. ==== mbtowc / mbrtowc ==== [[c:lib:stdlib:mbtowc|mbtowc()]] converts a [[theory:encoding:multibyte|multibyte character]] to its [[theory:encoding:widechar|wide character]] representation. The actual size of ''wchar_t'' is at least 8 bits but is compiler-dependent. This means the developer is not allowed to make any other assumptions on the size of the result than being of type ''wchar_t''. Using a smaller type than required for the conversions results in a memory corruption. // gcc mbtowc.c #include #include int main() { char wc; // should be wchar_t char buffer[] = {0xc3, 0x9f}; // ß setlocale(LC_ALL, "en_US.utf8"); mbtowc(&wc, buffer, 2); return 0; } [[c:lib:stdlib:mbrtowc|mbrtowc()]] behaves like ''mbtowc()'' but uses a narrow wide character representation. ===== Functions Writing Memory of Specified Length ===== Unlike those listed in the previous section, many functions of the standard library actually require the buffer size to be specified. They are safer in the sense that the developer is forced to handle buffer sizes, but can still be misused for memory corruptions due to wrongly implemented size calculations, numerical overflows, numerical underflows or buffer overflows caused by other functions overwriting already validated parameters. Although these functions are less likely to be vulnerable than those writing memory of unspecified length, they should be used carefully. Several functions without length specification such as ''strcpy()'' and ''strcat()'' meanwhile received a secure equivalent with an update of the language standard which gets additional information about the buffer size. To keep existing code working, these new versions have a ''_s'' suffix (e.g. ''strcpy_s()'' and ''strcat_s()''). Some common examples of functions writing memory of specified length are presented in detail here. Due to the high number of functions in this category, most of them are simply listed without any detailed explanation at the end of the section. ==== memcpy ==== [[c:lib:string:memcpy|memcpy()]] copies one memory region to another. The number of bytes to be copies is passed and needs to be less than or equal to the size of the destination buffer. // gcc memcpy.c #include int main() { char *src = "Hello World\n"; char dst[5]; memcpy(dst, src, 1 + strlen(src)); return 0; } ==== fgets ==== [[c:lib:stdio:fgets|fgets()]] is a safer alternative to the ''gets()'' or ''scanf()'' function. It allows the developer to set the size of the buffer where the read data is stored. // gcc fgets.c #include int main() { char buffer[10]; fgets(buffer, 10, stdin); return 0; } ==== strncpy / strlcpy / wcsncpy / wcslcpy ==== When thinking about a safer alternative for ''strcpy()'', the most obvious solution is probably [[c:lib:string:strncpy()|strncpy()]]. // gcc strncpy.c #include int main() { char dst_buffer[10]; char const *src_buffer = "This string is too long for the dst_buffer"; strncpy(dst_buffer, src_buffer, 10); return 0; } It is important to note that also ''strncpy()'' can cause unintended effects. If the source buffer is larger than the size passed to ''strncpy()'', there is no '''\0''' termination added to the destination string. One alternative taking care of '''\0''' termination is ''strlcpy()''. However, this function is not part of the C standard and only available on some Unix systems (e.g. BSD). // gcc strlcpy.c #include int main() { char dst_buffer[10]; char const *src_buffer = "This string is too long for the dst_buffer"; strlcpy(dst_buffer, src_buffer, 10); return 0; } With [[c:lib:wchar:wcsncpy|wcsncpy()]] and wcslcpy(), the same vulnerability applies to wide strings. ==== strncat / strlcat / wcsncat / wcslcat ==== Similar to ''strncpy()'', the obvious alternative to ''strcat()'' is [[c:lib:string:strncat()|strncat()]]. // gcc strncat.c #include int main() { char dst_buffer[10] = "This "; char const *src_buffer = "string is too long for dst_buffer"; strncat(dst_buffer, src_buffer, 4); return 0; } Again, also ''strcat()'' can cause problems if the existing string is not terminated by a '''\0''' character. Analogous to ''strlcpy()'', there is a safer alternative called ''strlcat()'' taking the size of the destination buffer as a parameter on some Unix systems. // gcc strlcat.c #include int main() { char dst_buffer[10] = "This "; char const *src_buffer = "string is too long for dst_buffer"; strlcat(dst_buffer, src_buffer, 10); return 0; } With [[c:lib:wchar:wcsncpy|wcsncat()]] and wcslcat(), the same vulnerability applies to wide strings. ==== snprintf / vsnprintf ==== [[c:lib:stdio:snprintf|snprintf()]] and [[c:lib:stdio:vsnprintf|vsnprintf()]] are safer alternatives to the ''sprintf()'' and ''vsprintf()'' functions allowing to specify the size of the destination buffer. // gcc snprintf.c #include int main() { char buffer[10]; snprintf(buffer, 10, "Hallo %s!\n", "World"); return 0; } ==== Others ==== Following functions of the C standard library were also identified to write memory of specified lengths. * asctime_s * c16rtomb * c32rtomb * ctime_s * fgetws * fread * mbrtoc16 * mbrtoc32 * mbrtowc * mbsrtowcs * mbstowcs * memset * strftime * wcsftime * wcsrtombs * wcstombs * wcsxfrm * wmemcpy * wmemmove * wmemset Additionally, all functions with added size specification (commonly marked with a ''_s'' suffix) are part of this category. Note that other common functions like ''recv()'' are not part of the official C standard but are part of this category as well. \\ ----
[[..exploitation:heap|← Back to heap overflows]] [[..start|Overview]] [[.ascii-armor|Continue with ASCII-armored addresses →]]