Last Update:
C++ Software Security Sins: Basic Issues
C++ Software Security Sins
In the world of software development, we are up against new cybersecurity threats each day, and the risks and consequences of un-secure software are too significant to be unaware of.
Let’s review some common security threats that might lurk in our C/C++ code.
This article is an adapted version of the presentation given by Mary Kelly, supported by Embarcadero.
Mary is an Experienced Application Developer with a demonstrated history of working in the computer software industry. Skilled in C++, Delphi, Databases, Pre-sales, and Technical Writing. Strong engineering professional with a Bachelor’s degree focused in Physics from Iowa State University. See his profile at Linkedin and other blogs at Embarcadero.
What is software security
To set the background for our today’s discussion, let’s have a look at the definition of Security:
According to Techopedia:
Software security is an idea implemented to protect software against malicious attacks and other hacker risks so that the software continues to function correctly under such potential risks. Security is necessary to provide integrity, authentication, and availability.
The importance of software security
- Less likely to get a data breach
- Customer safety
- Reputation
- Compliance issues/Regulatory/Law
- Potential loss of revenue
- Easier to maintain
I’d like to stress the last bullet point: easier to maintain. Finding security bugs is very hard as they might not be obvious and are often related to edge cases of your business logic. Writing secure code from the start reduces the time required to find and fix those bugs.
Buffer overruns
Those might be the most common issues that lead to various spectacular bugs in the past.
In short:
- you have a buffer of size N
- you get some input data of size M
- you write the data into your buffer without checking the size if
M < N
.
For example, if your password can contain a maximum of 28 characters, hackers can exploit it and send you:
helloworldthisisfirst28charsrundll
If you don’t check the length, then there’s a chance the additional part of the input message will leak into the adjacent memory in your program.
In most serious cases, you could add some additional payload that executes a system call and spawns a root shell!
The following is a snippet of a common “old school” buffer overrun:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main() {
char password[28];
char otherImportantBuffer[100] = { 'a'};
printf("Enter your password: ");
scanf("%s", password);
printf("your secret: %s\n", password);
}
Try passing more than 28 characters.
At best, you’ll get some hard crash or unhandled exception situation. But there’s also a chance the buffer will “eat” some memory.
Fortunately, such code is even hard to compile on modern compiles! This is because various “safe” alternatives to functions like scanf
, gets
, or strcpy
require you to pass length
.
When dealing with buffer overflows, here are a few common fixes:
- Use the latest compilers and libraries - they offer updated security fixes and the most secure version of functions you use.
- Use the C++ Standard Library and STL
- Use libraries that check bounds
- For buffer overruns or overflows, there is a popular method called fuzz testing. Fuzz Testing, or fuzzing as it’s known in many circles, is a testing technique by which you test your inputs with generated semi-randomized values helping with the stability and performance of your applications. I mentioned one fuzzing library that I use called libFuzzer.
And here’s a great explanation about Heartbleed - a scary bug in OpenSSL that affected millions of users: https://www.youtube.com/watch?v=1dOCHwf8zVQ.
In short, it’s a variation of a buffer overflow scenario we pass less than the actual buffer size is. This causes the server to respond with data that might be located outside the buffer, and we can sneak some various information about the program.
Format string problems
Another one comes from printf
-like functions: See the code:
void vulnerable() {
char buffer[60];
if (fgets(buffer, sizeof (buffer), stdin) == NULL)
return;
printf(buffer);
}
void notVulnerable () {
char buffer[60];
if (fgets(buffer, sizeof (buffer), stdin) == NULL)
return;
printf ("%s", buffer);
}
Which function is safer?
The main problem here is that if the buffer
contains some additional format string characters and we don’t check it, it’s possible to add some additional instructions and execute them. In the case of notVulnerable()
, we can only print strings so that no extra code can be invoked.
Recommended fixes:
- Don’t pass user input directly as the format string to formatting functions
- Use fixed format strings or format strings from a trusted source
- Keep an eye on compiler warnings and errors
- When necessary to use format strings, use:
printf(“%s”, user_input)
- Even better, use don’t use the
printf
family of functions if you can avoid it. Use stream operations likestd::cout
orstd::format
(C++20) - they are typesafe.
Integer overflows
The integer overflow occurs when the result of an operation is larger than the allowed max value for the data type of an operation and can cause crashes, logic errors, escalation of privileges, and execution of arbitrary code.
Some easy fixes you can do:
- Study and understand your code. Do a little bit of math!
- Check all calculations used to determine that your memory allocations and array indexes can’t overflow.
- Use unsigned variables for array offsets and sizes for memory allocation
- Pay attention to your compiler warnings
- Check for truncation and sign issues when working with
size_t
- Again, C++20 improves functionality here with Safe Integral Comparison Functions in C++20.
Array new
and delete
When you write new in your applications, you are creating unmanaged objects, and you are then required to call delete later on if you don’t want to risk leaks. So don’t use new
and delete
at all, as this is considered a C++ bad practice. Better yet, working in modern C++ allows you to use smart pointers and Standard library container classes that make it easier to match every new
with exactly one delete
.
See C++ Core Guidelines - R.11: Avoid calling new
and delete
explicitly.
Poor resource handling
In C++, a copy constructor is called when a new variable will be created from an object. If you don’t create a copy constructor, then your compiler generates a copy constructor. This sounds great! But if you don’t properly set up your constructor, then the errors replicate.
class PrtHolder {
public:
PtrHolder(void* p) : m_ptr(p) { }
~PtrHolder() {
delete m_ptr;
}
private:
void* m_ptr;
};
When your class controls resources, you should declare a private copy constructor and assignment operator with no implementation (or use = delete
); this way if a class external to the class with your private declaration attempts to invoke one of these, then you will get a compiler error about invoking a private method. Even if you accidentally call one internally, then you will get a link error.
Pointer initialization
Foo* pFoo;
if (GetFooPtr ( &pFoo ) )
{
// some code
}
// If pFoo is uninitialized, this is exploitable
pFoo->Release();
There are a few methods to use when wanting to avoid pointer problems. Use these steps in C++:
- Initialize pointers when you declare them – Kind of a no brainer, but a great way to make your application a little easier to debug instead of worrying about some previously used pointer value
- Zero pointers out after use
- To avoid memory leaks, allocate memory from the heap and return it at the same abstraction level.
- Return blocks to the heap while your pointers are still in scope
- Make sure that the types of pointers match
Lack of STL knowledge
Know C++ Standards.
There is an awesome group of people out there who make up rules regarding the evolution of the C++ language. Since C++11, there has been an uptick in features that help avoiding many pitfalls surrounding the security of your C++ code. My recommendation for learning more about the C++ STL or the C++ Standard Library is to check out cppreference.com.
The whole presentation
You can watch the whole presentation from Mary here:
Useful resources
I usually like to recommend a few books or resources in my webinars, and this one is no different. For learning about Software Security or ways to solve these “sins” with an emphasis on C++ applications, check out the following:
- Writing Secure Code, Second Edition by Michael Howard and David LeBlanc
- 24 Deadly Software Security Sins: Programming Flaws and How to Fix Them by Michael Howard, David LeBlanc, John Viega
- Software Security: Building Security In by Gary McGraw
- Effective C++: 55 Specific Ways to Improve Your Programs and Designs (3rd Edition) by Scott Meyers
- STL Tutorial and Reference Guide by David Musser
And additional:
Back To You
- Do you agree with the suggestions from this article?
- Do you use safer alternatives to the presented techniques about printf, type safety, RAII?
- How do you strive to write secure code?
Let us know in the comments below.
I've prepared a valuable bonus if you're interested in Modern C++!
Learn all major features of recent C++ Standards!
Check it out here: