Windows System Software -- Consulting, Training, Development -- Engineering Excellent, Every Time.

Using the Windows String Safe Functions

Unless you have had your head in a hole for the last couple of years, you have undoubtedly heard about Microsoft fixing security holes in Windows kernel mode code.    Many of the problems they encountered had to do with string handling functions, for example strcat, strcpy, and their related c-runtime library functions (CRT) .   The weakness in these functions is that no buffer lengths are specified, so it is very easy for a user to copy a long string into a short buffer and thus corrupt memory or worse, cause some unhandled exception.    Buffer overruns have even been used as security attacks.  To fix this class of problem Microsoft created a safer set of functions called the “String Safe” Functions.  These relatively recent functions (XP SP1), to no one’s surprise, add parameters to describe the length of the string buffers, to reduce the chance that buffers are overflowed

When Microsoft created the string safe functions they created two sets of functions.    One set uses byte counts to describe buffer lengths, while the other set of functions uses character counts to describe the buffer lengths.   Byte Count related functions always contain “Cb” (for count in bytes) in the name, for example RtlStringCbCat.  The character count related functions always contain “Cch” (for count in characters) in the name, for example RtlStringCchCat.   The type of function to use is entirely up to you.

Now, it is nice to know that Windows provides these functions, and some of the advantages are probably already evident to you.  However, let’s review some of the major benefits to using these newer functions: Since you are providing the correct size of the destination buffer this ensures that the function will not write past the end of the input buffer.  All string safe functions guarantee that the output buffers are null terminated, even if the operation truncates the intended result.

  • All the string safe functions return an NTSTATUS, with the only possible success code being STATUS_SUCCESS (there are, of course, several possible error codes).
  • Each string handling function has both a Cch and Cb variant available for use.
  • Most functions have an extended “Ex” version which provides extended functionality.

String Safe Character Count Functions
Table 1 lists the character count string safe functions provided in “strsafe.h” and the c-runtime functions (CRT) functions they are intended to replace.

Table 1

Table 1

String Safe Byte Count Functions
Table 2 lists the byte count string safe functions provided in “strsafe.h” and the c-runtime functions (CRT) functions they are intended to replace.

Table 2

Table 2

Unicode Strings
At this point I probably have given you the impression that the string safe functions are only intended to replace c-runtime functions.   That is not true.   The string safe functions include many functions, of both Cch and Cb variants, to help you with Unicode strings also.  The list below contains the functions provided:

  • RtlUnicodeStringXxxCatN, RtlUnicodeStringXxxCatNEx
  • RtlUnicodeStringXxxCatStringN, RtlUnicodeStringXxxCatStringNEx
  • RtlUnicodeStringXxxCopyN, RtlUnicodeStringXxxCopyNEx
  • RtlUnicodeStringXxxCopyStringN, RtlUnicodeStringXxxCopyStringNEx

Example
Let us assume for the moment that you need to write a function to generate the full name of a file from an input full directory path and the handle to a file.   Furthermore, we will also assume that we will never see a long path name, because everyone must be like us and hate to have long file names.   Thus the function that you may write to handle this could be as shown as:

wchar* GenerateFullPath(wchar* fulldirPath,HANDLE File)
{
	static wchar	dirname[100];  // assume you think all paths are small
	wchar*	fileName = NULL;
	
	ASSERT(fulldirPath);
	ASSERT(File);
	
	fileName = GetFileName(File);  	// Get the name of the input file
	strcpy(dirname,fulldirPath);   	// copy the input dir name
	strcat(dirname,L"\\");		// append a \ to the name
	strcat(dirname,fileName);	// concatenate the filename
	
	return dirname;			// return the full path to the caller.
}

If you analyze this code, I am sure that you can see a great many faults that could occur in this code.  Most of them center around overrunning the dirname buffer since we forgot to look at the string lengths of the buffers we were concatenating from.    A better solution to this function could be either of the following, one illustrating the use of the Cch version of the string safe functions and the other using the Cb version:

wchar* GenerateFullPath(wchar* fulldirPath,HANDLE File)
{
	static wchar	dirname[MAX_PATH];
	wchar*			fileName = NULL;
	NTSTATUS		status;
	
	ASSERT(fulldirPath);
	ASSERT(File);
	
	ASSERT(MAX_PATH <= STRSAFE_MAX_CCH);
	
	fileName = GetFileName(File);  	// Get the name of the input file
	
	status = RtlStringCchCopyW(dirname,MAX_PATH,fulldirPath);
	if(!NT_SUCCESS(status) {
		return NULL;
	}
	status = RtlStringCchCatW(dirname,MAX_PATH,L"\\");
	if(!NT_SUCCESS(status) {
		return NULL;
	}
	status = RtlStringCchCatW(dirname,MAX_PATH,fileName);
	if(!NT_SUCCESS(status) {
		return NULL;
	}	
	return dirname;					// return the full path to the caller.
}
wchar* GenerateFullPath(wchar* fulldirPath,HANDLE File)
{
	static wchar	dirname[MAX_PATH];
	wchar*			fileName = NULL;
	NTSTATUS		status;
	
	ASSERT(fulldirPath);
	ASSERT(File);
	
	ASSERT(MAX_PATH <= STRSAFE_MAX_CCH);
	
	fileName = GetFileName(File);  	// Get the name of the input file
	
	status = RtlStringCbCopyW(dirname,MAX_PATH*sizeof(WCHAR),fulldirPath);
	if(!NT_SUCCESS(status) {
		return NULL;
	}
	status = RtlStringCbCatW(dirname,MAX_PATH*sizeof(WCHAR),L"\\");
	if(!NT_SUCCESS(status) {
		return NULL;
	}
	status = RtlStringCbCatW(dirname,MAX_PATH*sizeof(WCHAR),fileName);
	if(!NT_SUCCESS(status) {
		return NULL;
	}
	return dirname;					// return the full path to the caller.
}

As you notice with the Cch and Cb versions of the function, the main difference is that the size of the destination buffer is specified in each call.   This provides the string safe functions with the ability to ensure that the destination buffer is not overrun.   In addition, the functions return a status so that we can be sure to terminate function processing if an error occurs.

Building Your Code
In order to build your code correctly, you may want to know about the compile time variables that you can define to allow you to optimize how you use the string safe functions.

STRSAFE_LIB
If you want to use the string safe functions in library form then define STRSAFE_LIB before including “strsafe.h” in your code.   In addition, you will need to insure that you link your code against “$(SDK_LIB_PATH)\strsafe.lib”.    Why do this?    Well, instead of including the string safe routines that you use in your binary, this allows your driver to dynamically bind to the string safe functions when it loads, hopefully allowing you to be running against the latest most improved implementations of these functions.   This can be both good and bad.

Example:

#define STRSAFE_LIB
#include

If you don’t define STRSAFE_LIB in your code, the current revision of the string safe functions will be compiled as part of your driver code (i.e., the implementations of the routines will be compiled as part of “strsafe.h”), and you do not need to link against strsafe.lib

STRSAFE_NO_DEPRECATE
Normally when you include “strsafe.h” all older functions (c-runtime functions) that are replaced by “strsafe.h” defined functions are deprecated, i.e. you are given warnings that that the functions are not to be used and you are urged to use their string safe equivalents.   If for some unknown reason you have to use one of these functions, you should define STRSAFE_NO_DEPRECATE.  This definition will suppress the warnings and allow you to compile your driver.

Example:

#define STRSAFE_NO_DEPRECATE
#include

STRSAFE_NO_CB_FUNCTIONS
If for some reason you want to limit your code to only use character count related string safe functions, then you should define STRSAFE_NO_CB_FUNCTIONS.   This definition will then limit your code to the use of the RtlStringCchxxx functions.

Example:

#define STRSAFE_NO_CB_FUNCTIONS
#include

STRSAFE_NO_CCH_FUNCTIONS
Similarly, the STRSAFE_NO_CCH_FUNCTIONS define allows you to limit your code to only use byte count related string functions.   Using this definition will then limit your code to use of the RtlStringCbxxx functions.

Example:

#define STRSAFE_NO_CCH_FUNCTIONS
#include

Summary
While it may seem like a hassle to learn a new set of functions to replace the old c-runtime functions that we have grown to love, it really is in your best interest to change.   The string safe functions provided in “strsafe.h” provide you with a robust set of functions which will make your driver safer from buffer overruns and silly string mistakes that we all tend to make.