Chrome 90 for Windows adopts Hardware-enforced Stack Protection, a mitigation technology to make the exploitation of security bugs more difficult for attackers. This is supported by Windows 20H1 (December Update) or later, running on processors with Control-flow Enforcement Technology (CET) such as Intel 11th Gen or AMD Zen 3 CPUs. With this mitigation the processor maintains a new, protected, stack of valid return addresses (a shadow stack). This improves security by making exploits more difficult to write. However, it may affect stability if software that loads itself into Chrome is not compatible with the mitigation. Below we describe some exploitation techniques that are mitigated by stack protection, discuss its limitations and what we will do next to approach them. Finally, we provide some quick tips for other software authors as they enable /cetcompat for their Windows applications.
Imagine a simple use-after-free (UAF) bug where an attacker can induce a program to call a pointer of their choosing. Here the attacker controls an object which occupies space formerly used by another object, which the program erroneously continues to use. The attacker sets a field in this region that is used as a function call to the address of code the attacker would like to execute. Years ago an attacker could simply write their shellcode to a known location, then, in their overwrite, set the instruction pointer to this shellcode. In time, Data Execution Prevention was added to prevent stacks or heaps from being executable.
In response, attackers invented Return Oriented Programming (ROP). Here, attackers take advantage of the process’s own code, as that must be executable. With control of the stack (either to write values there, or by changing the stack pointer) and control of the instruction pointer, an attacker can use the `ret` instruction to jump to a different, useful, piece of code.
During an exploit attempt, the instruction pointer is changed so that instead of its normal destination, a small fragment of code, called an ROP gadget, is invoked instead. These gadgets are selected so that they do something useful (such as prepare a register for a function call) then call return.
These tiny fragments need not be a complete function in the normal program, and could even be found part-way through a legitimate instruction. By lining up the right set of “return” addresses, a chain of these gadgets can be called, with each gadget’s `ret` switching to the next gadget. With some patience, or the right tooling, an attacker can piece together the arguments to a function call, then really call the function.
Chrome has a multi-process architecture — a main browser process acts as the logged-in user, and spawns restricted renderer and utility processes to host website code. This isolation reduces the severity of a bug in a renderer as its process cannot do much by itself. Attackers will then attempt to use another sandbox escape bug to run code in the browser, which lets them act as the logged-in user. As libraries are mapped at the same address in different processes by Windows, any bug that allows an attacker to read memory is enough for them to examine Chrome’s binary and any loaded libraries for ROP gadgets. This makes preventing ROP chains in the browser process especially useful as a mitigation.
Enter stack-protection. Along with the existing stack, the cpu maintains a shadow stack. This stack cannot be directly manipulated by normal program code and only stores return addresses. The CALL instruction is modified to push a return address (the instruction after the CALL) to both the normal stack, and the shadow stack. The RET (return) instruction still takes its return address from the normal stack, but now verifies that it is the same as the one stored in the shadow stack region. If it is, then the program is left alone and it continues to work as it always did. If the addresses do not match then an exception is raised which is intercepted by the operating system (not by Chrome). The operating system has an opportunity to modify the shadow region and allow the program to continue, but in most cases an address mismatch is the result of a program error so the program is immediately terminated.
In our example above, the attacker will be able to make their initial jump into a ROP gadget, but on trying to return to their next gadget they will be stopped.
Some software may be incompatible with this mechanism, especially some older security software that injects into a process and hooks operating system functions by overwriting the prelude with `rax = &hook; push rax; ret`.
Chrome does not yet support every direction of control flow enforcement. Stack protection enforces the reverse-edge of the call graph but does not constrain the forward-edge. It will still be possible to make indirect jumps around existing code as stack protection is only validated when a return instruction is encountered, and call targets are not validated. On Windows a technology called Control Flow Guard (CFG) can be used to verify the target of an indirect function call before it is attempted. This prevents calling into the middle of a function, significantly reducing the scope of useful instructions for attackers to use. Another approach is provided by Intel’s CET which includes an ENDBRANCH instruction to prevent jumps into arbitrary code locations. Memory tagging tools such as MTE can be used to make it more difficult to modify pointers to valid code sequences (and makes UAFs more difficult in general). We are working to introduce CFG to Chrome for Windows, and will add other techniques over time.
By itself, stack protection can be bypassed in some contexts. For instance, stack protection does not prevent an attacker tricking a program into calling an existing function by entirely replacing an object containing a function pointer. This approach does not involve ROP as the function call happens instead of the expected call, and returns to the address it was originally called from, so must be allowed. However, the called function must be useful to an attacker, and most functions will not be. An example of an attack using this method is to craft a call to add the `–no-sandbox` argument to Chrome’s command line. This results in future renderers being launched without normal protections. Over time we will identify and remove such useful tools.
Finally, stack protection doesn’t stop the bugs in the first place. Everything we have discussed above is a mitigation that makes it more difficult to execute arbitrary code. If a programming error allows arbitrary writes then it is very unlikely that we can prevent this being used to run arbitrary code. Attackers will adapt and find new ways to turn memory safety errors into code execution.
You can see if Hardware-enforced Stack Protection is enabled for a process using the Windows Task Manager. Open task manager, open the Details Tab, Right Click on a heading, Select Columns & Check the Hardware-enforced Stack Protection box. The process display will then indicate if a process is opted-in to this mitigation. ‘Compatible Modules Only’ indicates that any dll marked as /cetcompat at build time will raise an exception if a return address is invalid.
Source: Google Online Security