Introduction
Two programmers went to buy cigarettes. One buys them and goes “Dude, did you read this? Warning! Smoking causes lung cancer witch is fatal”. Then the other one says “Yeah, forget the warning, just tell me the errors!”
When using Microsoft Visual Studio 2005 or newer, the compiler may give a bunch of annoying warnings saying something like: “warning C4996: ‘strcpy’: This function or variable may be unsafe. Consider using strcpy_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details”. CRT functions like strcpy are standard functions, so why the compiler complains? Well, it simply recommends using of more secure versions of CRT functions which have ‘_s’ postfix (strcpy_s instead of strcpy, sprintf_s instead of sprintf and so on). So, at a first look nothing fatal happened and we can go ahead. However, simply ignoring the warnings is not a good practice and moreover someone may work in a company or project in which “zero warnings” is a must.
This article is about how to deal with C4996 warning, what secure CRT functions are and how to correctly use them. Further, let’s call them “_s functions”.
Can we get rid of C4996 warnings?
Especially when porting code written under an older Visual C++ version, we may want to get rid of C4996 warnings. The answer is: yes, we can like in case of other compiler warnings, by adding pragma warning directive in the source code.
#pragma warning(disable: 4996)
You may add pragma warning in each source file or, if the project uses precompiled header, adding only it in StdAfx.h is pretty enough.
Another and better way is to add _CRT_SECURE_NO_WARNINGS to preprocessor definitions, as suggested in the description of the warning. To find out how to add _CRT_SECURE_NO_WARNINGS, take a look in MSDN at /D (Preprocessor Definitions).
No warnings, no more headaches, and the boss is happy. However, please read further! One good way to avoid C4996 warnings is… by using _s functions.
Why using _s functions?
Here, I would like to quote from one of Michael Howard’s articles:
Think about it for a moment. When were functions like strcpy and strcat invented? They were designed in happier times when the C language was first developed by Kernighan and Ritchie long ago, when threats were by no means as serious as they are today, and when networking wasn’t as pervasive as it is today. Now don’t get me wrong, you can write secure code with functions like strcpy. It’s the data that’s the culprit, after all. But functions like strcpy don’t help you write secure code, and an error in such a function can be catastrophic. Of course, gets is just plain evil! ‘Nuff said.
I heard C/C++ programmers saying: “I’ve never ever made a bug”. That’s pretty possible if they never ever made a line of code. Otherwise, I can’t believe them. Nobody is perfect and everyone can do mistakes.
In the first side, standard C functions were designed for speed. For that reason, they do not perform run-time checking which is usually performed in other programming languages. A programmer must be carefully when use those functions and validate parameters in the own code in order to avoid troubles like buffer overrun, access violation, malicious attacks, and so on.
Well, _s functions do additional checking for you. Details about Security Enhancements in the CRT can be found in MSDN library.
I will give just a trivial example concerning null termination string problem. One can say “this is stupid, something like that can never happen”. Wrong! In the real programming world, other ones even more stupid, much more insidious and having uglier effects, can be found.
Two strings walk into a bar. One of them says: “A beer, please!%@8Hj(^&9))%@!$%*” The other one says: “You’ll have to excuse my friend, he is not zero-terminated.”
May be a good programmers’ joke, but in a C/C++ application, a not-zero terminated string is not so funny. Let’s try translating it into code:
#define MAX_BUFFER 1000 void append_joke(char* buff, const char* psz) { strcat(buff, psz); // classic } int main() { char* joke = new char[MAX_BUFFER]; memcpy(joke, "A beer, please!", strlen("A beer, please!")); append_joke(joke, "You'll have to excuse my friend…"); printf(joke); delete []joke; #ifdef _DEBUG system("pause"); #endif return 0; }
If the program doesn’t crash (here because of heap corruption) we can’t say that we are lucky. The result may be a bunch of garbage like in the above joke. But if we use strcat_s instead of strcat, a run-time checking against not properly null terminated string will be performed. Further we’ll see what’s happen in this case.
Do _s functions prevent program crashing?
Most of programmers dealing first time with _s functions think that program will never crash even they are doing mistakes. In other words, simply replacing standard CRT functions with more secure ones, prevents application crashing. That is not true. Let’s modify append_joke function and replace strcat with “more secure” strcat_s.
void append_joke(char* buff, const char* psz) { strcat_s(buff, MAX_BUFFER, psz); // more secure }
Now, let’s run again the program in debug mode. Oups!.. A “Debug Assertion Failed” message box is shown. If press “Ignore” button, another “fatal error” message is shown then program terminates. What happened? First, strcat_s function checks parameters validity. If one is invalid (in our example we have not null terminated string) and if the program runs in debug mode, a “debug assertion failed” message is shown. That’s very good, because we have a chance to fix the mistake, first looking at assertion message (here is “String is not null terminated”), then pressing “Retry” and search the source of error in “Call Stack” window. That is not possible in case of using the “classic” function strcat.
Finally, an invalid parameter handler function is called and the process terminates. That’s also good because, if something is really going bad, it’s preferable to close the application instead of let it doing unpredictable things.
How to customize the _s functions behavior?
As stated above, when _s functions detect an invalid parameter, a message is shown then the application process terminates.
However, someone may want to do something else before application exits or simply wants application to continue. Someone else may wonder why the _s functions return an error code as long as by default, if an invalid parameter is passed, the process is terminated. Well, the default invalid parameter handler function may be replaced with an application-defined one. For doing that, we can call _set_invalid_parameter_handler function.
Here is an example. It prevents showing the debug assertion failure message and sets an application-defined invalid parameter handler function. In case that invalid parameter is passed, the process isn’t terminated. Also, the error code returned by strcat_s is used by program, which prints out the string only if it’s Ok.
#include <string.h> #include <stdio.h> #include <stdlib.h> #include <crtdbg.h> void app_handler(const wchar_t * expression, const wchar_t * function, const wchar_t * file, unsigned int line, uintptr_t pReserved) { wprintf_s(L"%snFunction:%snFile:%snLine:%un", expression, function, file, line); } #define MAX_BUFFER 1000 errno_t finish_joke(char* buff, const char* psz) { return strcat_s(buff, MAX_BUFFER, psz); } int main() { // prevent showing "debug assertion failed" dialog box _CrtSetReportMode(_CRT_ASSERT, 0); // set application-defined invalid parameter handler _set_invalid_parameter_handler(app_handler); char* joke = new char[MAX_BUFFER]; memcpy(joke, "A beer, please!", strlen("A beer, please!")); errno_t ret = finish_joke(joke, "You'll have to excuse my friend…"); if(0 == ret) // prints only if finish_joke succeeded { printf_s(joke); } delete []joke; #ifdef _DEBUG system("pause"); #endif return 0; }
Conclusion
- It’s no sweat to get rid of C4996 warnings but also it’s good to know that secure CRT functions are great.
- Secure CRT functions perform run-time parameters checking which helps to find mistakes and prevent troubles.
- The default invalid parameter handler terminates the aplication process; if we want to change this, we have to use an application-defined handler.
- Last but not the least: if you want to write more solid and secure code, prefer using secure version of CRT functions!
See also
- MSDN: Security Enhancements in the CRT
- Michael Howard: Saying Goodbye to an Old Friend
- Secure CRT Functions Standard