C and C++ are members of the same family of languages. The evolutionary boldness of C++ removes some of the marketplace pressure on C; people who are continually pushing for innovation are naturally drawn to the C++ development process. Each language had a coherent original design (by Dennis Ritchie and Bjarne Stroustrup, respectively), followed by successive refinement in a very competitive marketplace of ideas. Both languages share an extreme concern for performance, with the slogan "don't leave space for a more-efficient systems-programming language underneath our language (C or C++)." However, it's unfair to complain that the original designs assigned too little importance to cybersecurity; both languages pre-date the beginnings of concern for security. But in recent years, as the marketplace has started to emphasize cybersecurity, C and C++ have been responding in several ways.
In early 2002, Bill Gates' famous "battleship-turning" memo made cybersecurity a top goal for Microsoft. About a year later, Microsoft proposed a new "bounds-checking" library to WG14, which eventually became Technical Report 24731-1. It now is part of C11 as the (optional) Annex K. (An almost-final draft of C11 is available here [PDF].)
The C11 Annex K Functions
I'll start my tour of Annex K with the
fopen_s function. The main innovation is that files are opened with exclusive (also known as non-shared) access. Furthermore, if the
mode string doesn't begin with
u (such as with code being updated from the older
fopen ), then to the extent that the underlying system supports it, the file gets a file permission that prevents other users on the system from accessing the file.
In this article, I'll sequentially enumerate the security benefits of these
_s functions. The new semantics illustrate the pattern of "least privilege." This "exclusive" mode was previously available in the Posix
open() function, but the ISO standard for C doesn't standardize system-dependent, low-level I/O. See Robert Seacord's book Secure Coding in C and C++ for detailed discussion of these various security benefits of the Annex K library.
If a file is opened with
x as the last character in the
mode argument, and the requested filename is already in use, the
fopen_s function fails (as opposed to truncating the existing file, which is presumably already being used by someone). If the application program had been required first to check whether the file was in use and then to create the new file, this would illustrate the "time-of-check versus time-of-use" vulnerability (TOCTOU). The Annex K document aims to minimize the TOCTOU.
mode argument is passed to
fopen_s as a
const char* pointer, as is the
filename argument. Requiring these pointers to be non-null is one of the "runtime-constraints" of the
fopen_s function, to use the C11 terminology.
If any of the runtime-constraints is violated, the library function (here,
fopen_s) invokes the run time-constraint handler. (In Visual Studio, this handler is known as the invalid parameter handler same concept, different name.)
This approach is a new pattern of response to security issues: Invoke the runtime-constraint handler if any runtime-constraint is violated. Previously, a runtime-constraint violation would have resulted in an undefined behavior if not caught.
If the runtime-constraints are not violated, then
fopen_s returns the resulting
FILE* pointer through an argument, rather than producing it as the returned value of the function. If
fopen_s fails for any of several reasons, it returns a nonzero value according to the conventions encoded in
<errno.h>. The various Annex K headers provide the typedef name
errno_t for this returned
int value. This reduces the inconsistency of return-value idioms to the greatest extent possible, by uniformly returning
errno_t for erroneous conditions that did not violate a runtime-constraint.
This initial discussion about
fopen_s has introduced the first four patterns of the Annex K library:
- Provide least privilege;
- Minimize TOCTOU vulnerability;
- Use runtime-constraint handlers for logic errors;
- Reduce the return value variability using
In the original C standard, and still in C++ today, most library functions specify something like "if copying takes place between objects that overlap, the behavior is undefined." In C99 and C11, there is a syntactic way to specify this restriction, the
restrict keyword. As a result of all these various design decisions, the calling sequence for secure
fopen_s calls looks like this:
errno_t fopen_s(FILE * restrict * restrict streamptr, const char * restrict filename, const char * restrict mode);
Designing the runtime-constraint handler provides the implementation and the project team a range of choices. The simplest handler simply invokes
abort(). A somewhat more complex architecture gives the user a choice between aborting or debugging, potentially preserving the full state of the stack frames and global variables. Other handlers could be used: In an application that never terminates, the handler could reinitialize, flush the current transaction, start a new transaction, and so forth. In a specialized testing situation, the handler could log the failures.
freopen_s function illustrates the same patterns as
fopen_s, including the
u mode flags.
Continuing with the file-oriented functions, consider
errno_t tmpnam_s(char *s, rsize_t maxsize);
This function illustrates another security pattern in C11: "In the calling sequence of the function, every pointer through which the function might modify an array is immediately followed by the number of elements which the function is permitted to modify."
In the case of
tmpnam_s, the second argument specifies a maximum for the number of characters that can be modified by
tmpnam_s. The type of the second argument is
rsize_t, designating a "restricted
size_t" value. The intent is to prevent the common error of inadvertently passing a negative value, which after conversion to an unsigned type, becomes a huge number, and in this case, defeating the purpose of bounds-checking the string written into
s. This common error is intended to be caught within
tmpnam_s by comparing
RSIZE_MAX and invoking the runtime-constraint handler if it's larger. (I've said "intended" several times, because Annex K makes it optional whether
RSIZE_MAX is any smaller than
SIZE_MAX.) This manner of designating bounding values with the type
rsize_t is another security best practice promulgated in the Annex K library.
Next, consider the
errno_t tmpfile_s(FILE * restrict * restrict streamptr);
It could be invoked like this:
FILE *myTempFile = 0; errno_t err = tmpfile_s(&myTempFile);
There is a window of TOCTOU vulnerability between obtaining a filename from
tmpnam_s and subsequently creating that file with
tmpfile_s eliminates that particular vulnerability.