There are two overarching goals at work – the first is to reduce the number of bugs in the code, and the second is to make it harder to reliably exploit any bugs that remain.
As part of the Security Development Lifecycle (SDL) engineers must attend security training. The reason for this is easy, and it’s not just to raise awareness, it’s to reduce the chance engineers will make mistakes in the first place. That being said, mistakes are made, and most issues should be picked up during peer review. We assume mistakes will still be made, so we run a bunch of tools on every developer’s code check-in to look for issues. The tools we run look for missing SAL annotations, buffer overruns, array bounds issues, integer overflows, banned APIs, banned cryptographic functions and more. We’re also removing banned functions from the product. Functions such as strcpy, strncpy and their evil brethren are just not OK anymore. I loath strncpy, and telling people to replace strcpy with strncpy is bad advice. We’re replacing the functions with either strsafe or SafeCRT. As you can imagine, this is a monumental effort!
The C/C++ compiler included with Visual Studio 2005 automatically flags many of the banned APIs:
c:\code\stackaddr.cpp(14) : warning C4996: 'strcpy' was declared deprecated
Also, functions that read from or write to buffers are SAL annotated to help our tools find harder-to-find bugs. Again, this is a large effort.
Every few weeks we run PREfix on the entire Windows Vista codebase. PREfix is PREfast’s much, much bigger inter-procedural brother. We also use internal and external penetration testing teams for high-risk items, and central fuzz testing of network protocols and file parsers. This part of the process is driven in part by the threat models as these help us understand the interfaces, privilege levels and accessibility of networking end-points, and hence give us a good feel for overall risk.
Does all this work mean the code is 100%? I doubt it! The reason is simple, we can do the very best we could possibly do, but Windows Vista will be in the market place for years and in that time, I can guarantee new attack techniques will be discovered, as will new bug types, and we can’t necessarily anticipate the future. Also, our tools are not perfect; we know they won’t find all vulnerable code. With that in mind, we must add other defenses. Read on.
Making it Harder for Exploit Code to Work Predictably
We have a bunch of defenses in Windows Vista, some are new to Vista, and some are updates to existing defenses in Windows Server 2003 and Windows XP SP2. The defenses are not there to compensate for insecurities; they are there to reduce the chance an exploit will work correctly.
I want to make one thing really clear; in my opinion, the overall effect of these defenses is greater than the sum of the parts.
Let’s assume for a moment that there is a stack-based buffer overrun in some service code. C/C++ Code in Windows Vista is compiled with the /GS flag, and there is a good chance any stack overflow will trip the /GS defenses, terminating the process, rather than running the attacker’s code. The Visual C++ compiler also re-arranges the stack, placing buffers in higher memory than non-buffers. For example, a function pointer is stored below stack-based buffers, which means the code would need to suffer a buffer underrun to be vulnerable. Buffer underruns are significantly rarer than overruns. We also move various function arguments (pointers, for example) to lower memory. Also, in builds after beta 2, we will randomize every thread’s stack too; I’ll discuss this in an upcoming post.
Let’s assume the attacker finds a way around /GS, another defense comes into play, No eXecute (NX or otherwise known as Data Execution Protection, DEP.) NX uses the CPU [the CPU must support NX] to stop code from executing in a data segment. Most traditional exploits contain shellcode which enter the system as data but is then executed as code; NX can stop execution from occurring.
But let’s assume the attacker has the time to find a way around /GS and NX. This is of course, possible if there are enough “degrees of freedom” in the way the code is written. Most exploit code calls into various Windows APIs, such as socket() and LoadLibrary(), with address space layout randomization (ASLR) it becomes hard to locate these functions in memory as they can be in any of 256 possible locations. There may be areas in the OS that are predictable, and we’re identifying these to make sure we can make ASLR as good as possible. But remember, ASLR will be v1 in Vista, and we will update it as time goes by, just like we did with the Windows Firewall.
As an aside, if the buffer overrun corrupts an exception handler on the stack, things get a little tricky for the attacker too. The operating system checks the address of the exception handler prior to dispatch, and if the address is not in the list of valid exception addresses compiled into the image’s PE header, the process is terminated. The list of addresses is added by the compiler. This is the /SafeSEH linker option.
Now let’s assume the bug is in code that has a heap-based buffer overrun. /GS and SafeSEH are irrelevant, as these two defenses are stack-based, not heap-based. But we have made numerous heap improvements too to make exploits harder to pull off successfully:
· Heap blocks are check-summed.
· Various heap block metadata elements are encoded (XORd) with a random number.
· The base address of heaps are randomized.
· Functions pointers used by the Heap, such as the CommitProcedure, are encoded (XORd) with a random number.
· For most applications included with Windows Vista, we will simply terminate the application if we detect heap corruption.
Note, if the bug is an integer overflow in a call to the C++ new operator, the code will detect the condition at run time and simply fail the memory allocation rather than causing a potential security vulnerability.
Ok, let’s assume that the attacker has the motivation, time, patience and expertise to bypass all these defenses. There’s more!
A new defense for Windows Vista is Service hardening, it’s a broad subject, so I want to focus on just two parts of service hardening. The first is the ability to describe the privileges that a service requires, and the service control manager (SCM) will assign only those privileges to the process. You can add code like this to your service setup code:
// Set up the required privileges
BOOL fRet = ChangeServiceConfig2(
The exploit code runs with the same privileges as the host process, and reducing the privileges associated with the process means the exploit code can do less damage. Of course, there may very well be privilege elevation bugs in Windows Vista that we do not know about, but in my opinion it’s better to put up defenses, rather than no defenses at all.
Another part of service hardening is services are assigned network firewall policy, which makes it more difficult for rogue code running in a service from making network connections beyond the scope of that defined as ‘normal’. Not impossible, just harder.
Oh, and the firewall is on by default too! But you knew that.