There are several concepts that can go into hardening. There are compile-time changes such as stack protection as well as more invasive runtime changes such as mandatory access control. Hardening itself is not a correction to any particular issue. It's not a 'bug fix'. Hardening looks at a threat and provides a means for mitigating an exploit that successfully used the vulnerability. Hardening is best used in a layered fashion without relying on any one technique since there can be flaws in any technique.
A stack protection scheme protects against a stack buffer overflow. The basic premise is that there is a buffer on the stack. That buffer is probably a local variable in a programming language like C. That buffer gets overwritten by unchecked data from an external source without verifying the length. This results in writing past the end of the buffer and potentially overwriting the function's return address. This address is where the code goes when the function is finished. If an attacker can get data written there, they can take over control. That is how such issues as the Morris worm worked that exploited a buffer overflow in the fingerd program. If stack protection of some sort were there, when the exploit was attempted, the finger server would have died instead of getting exploited. Stack protection consequently converts a possible root exploit into a denial of service instead.
gcc has the
-fstack-protector flag and related flags available. The basics of what stack protection does is to put a so-called canary value on the stack and make sure that value is the same when the stack frame is popped. If the canary value changes, that indicates that the stack was overwritten by some sort of buffer overflow. When this happens, it indicates that the return address could have been overwritten which allows an attacker to control where the next instruction is. There are two components to how
-fstack-protector works. The compiler inserts code to check the values. The C library provides the functions. Consequently you can run into issues where there is code compiling without a libc that implements the helper functions that will technically succeed at compiling but fail to link. This results in needing to turn off stack protection for at least parts of glibc and gcc as well as not using that feature in programs linking against things like klibc.
The typical memory allocation leaves the memory as is when it is freed. The problem with this is that later that memory can be allocated elsewhere, given that memory, and read. This allows a password to be used in one part of software and be read elsewhere. A similar concern occurs when there is a core dump due to a crash or memory gets swapped to disk.
This sort of issue can be addressed mostly through intelligent choices by software developers. However, sometimes a specific choice is required from users. For example, dev-db/sqlite provides a
secure-delete USE flag that forces safely wiping out removed database records.
Some programs such as portage run as root. This is a problem when root privileges are not necessary. It provides a window of opportunity to exploit a program and gain root privileges.
In the case of Portage, this can be addressed through the
usersync features. For daemon sorts of programs, there is usually a configuration mechanism that allows the program to be started as root but drop privileges to run as nobody or apache or some similar user minimizing the window of opportunity to gain root access through an exploit.
Reduce Surface Area
Minimizing installed packages and open network ports is an obvious hardening strategy. The premise here is that even if there's a vulnerability in a package, unless that package is actually used for something, it's not a problem for you because it won't even be installed and even if it is installed and running, it's not visible to the network.
Intelligent choices for what to include in the base stage as well as firewall rules are ways to address this hardening strategy.