GNU/Linux from the Adversary's Side - Buffer Overflows, the Boot Chain, and a Real Attack
- Buffer Overflow
- The Boot Chain Under a Physical Adversary
- A Remote Adversary: From Web Exploit to Root
- Conclusion
- Frequently Asked Questions
- References
The preceding articles built up the twelve base and advanced security primitives of GNU/Linux (authentication, file permissions, ACLs, extended attributes, capabilities, SECCOMP, the LSM family, mount restrictions, chroot, netfilter, cgroups, and namespaces) as atomic building blocks that compose into isolation and virtualization. This article takes the reverse path: instead of building defenses, it tests them by trying to bypass them. It follows the chronological startup of a GNU/Linux server to locate where an attacker can act, examines a stack buffer overflow and the mitigations that blunt it, and walks through a real remote compromise from an initial web exploit to root. The goal is the point a threat model exists to make: a primitive is only as useful as the adversary it is chosen to stop.
This article has been made with the help of Claude Code and several custom skills
[TOC]
Buffer Overflow
Before the attack walkthrough, one more primitive completes the picture: the defenses against memory-corruption attacks of the buffer-overflow class.
Think of memory as the long paper tape of a Turing machine. When a program uses functions, the code for each function is placed in memory once and re-run on demand. To support calls and returns, the CPU keeps a stack: a region of memory that stores the return address to resume at when a function finishes, and also the function’s local variables (including buffers).
The Stack Overflow
A typical frame holds, from higher to lower addresses, the saved return address (RET), the saved frame pointer (RBP), and then the local buffer. The buffer is written from its own address upward, toward the saved frame pointer and return address.
Adversary model. If a program lets more data be written into a local variable than there is room for, the write runs past the buffer and overwrites the saved frame pointer and the return address. By controlling the value that lands on RET, an attacker redirects execution when the function returns and takes control of the program.
The canonical vulnerable pattern copies attacker-controlled input into a fixed buffer with no bound:
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv) {
char buf[64];
strcpy(buf, argv[1]); /* no length check: argv[1] may exceed 64 bytes */
printf("you entered :%s\n", buf);
return 0;
}
The layout makes the danger concrete. An argv[1] longer than 64 bytes overflows buf and keeps writing into the saved RBP and then RET:
stack (grows toward lower addresses)
+------------------+
| RET | <- saved return address (overwrite target)
+------------------+
| saved RBP | <- frame pointer
+------------------+
| |
| buf[64] | <- strcpy writes here, upward, past the end on overflow
| |
+------------------+
The Three Mitigations
Three protections, used together, raise the cost of exploiting such a bug:
- Stack canary. A random guard value is placed between the local variables and the saved return address, and checked before the function returns. An overflow large enough to reach
RETalso corrupts the canary, so the mismatch aborts the program. The mechanism depends on the compiler and the C library. - NX (no-execute). If the attacker’s code (shellcode) sits in the stack, marking the stack non-executable prevents it from running. This separation of “writable” from “executable” memory is an idea that MULTICS already had.
- ASLR (Address Space Layout Randomization). Randomly placing the data regions (stack, heap) in virtual memory defeats attacks that rely on fixed, known addresses inside the process.

None of these is absolute (return-oriented programming, information leaks that defeat ASLR, and canary-bypass techniques all exist), but together they turn a once-trivial overwrite into a much harder problem.
The Boot Chain Under a Physical Adversary
To find where an attacker can act, follow the chronological startup of a standard GNU/Linux server (Intel CPU, motherboard, memory, SSD) under one explicit adversary: physical access to the machine (which also models a virtual machine whose host is hostile). The question is which threats the previously studied primitives can mitigate, and which they cannot.
The boot proceeds through six stages:

- BIOS/UEFI. The Basic Input Output System is simple and MBR-based; the Unified Extensible Firmware Interface is complex and largely proprietary. This stage initializes the hardware (memory, disk, graphics). Open alternatives exist (coreboot, Libreboot, LinuxBoot, U-Boot). Critically, out-of-band management engines run beneath the operating system: Intel AMT, AMD Secure Technology (PSP), IPMI, and HP iLO. A TPM anchors measured boot and SecureBoot.
- Bootloader. GRUB, LILO, Syslinux, or U-Boot. It is the first binary of the distribution, can manage encrypted partitions (GRUB), and lets the kernel boot options be edited. That editing capability is the lever behind the evil maid attack: brief physical access to alter the boot path.
- initramfs. The
initrd/initramfsis a temporary filesystem (/boot/initramfs, a cpio archive mounted as tmpfs) that loads the drivers and modules needed to runinit, including unlocking an encrypted root partition (LUKS, with a key that can be TPM-sealed). - Kernel.
/boot/vmlinuz, launched with parameters passed by the bootloader. - init.
/sbin/init, today usually/lib/systemd/systemd. - Runlevel / services. Starts the system services and processes; the model comes from System V UNIX (
/etc/rc), with modern implementations such as systemd, launchd, Runit, and OpenRC.
Adversary model for this stage. With physical (or hostile-host) access, the chain of trust depends on proprietary tools (SecureBoot, TPM, OPAL self-encrypting drives). BIOS/UEFI access is critical; the bootloader is exposed to boot-parameter tampering and the evil maid attack, which partition encryption plus authentication and integrity checks aim to counter; and unused out-of-band services (Intel AMT, IPMI, iLO) should be disabled because they sit outside the operating system’s own protections.
A Remote Adversary: From Web Exploit to Root
Now switch to a remote adversary trying to take control of a GNU/Linux server that runs a web service and an SSH service, which is a realistic case. Offensive work tends to follow a recurring sequence (the “ethical hacking 101” kill chain): reconnaissance, then an attack vector, then privilege elevation, then persistence, cleanup, and expansion.
The stages are:
- Reconnaissance. Search for attack vectors (inputs, exposed services, port scanning).
- Attack vector. Exploit a vulnerability or misconfiguration in a service.
- Privilege elevation. Exploit a vulnerability or misconfiguration to rise from the service account toward root.
- Persistence. Maintain access through a backdoor.
- Cleanup. Erase traces (logs).
- Expansion. Pivot to other machines.

The Concrete Case
From the adversary’s point of view, the lecture’s example proceeds as follows:
- Attack vector. Exploit a WordPress plugin to drop a
.phpfile on the server, then use that PHP script to execute a shell. This lands the attacker as the web-service account (for examplewww-data), not as root. - Privilege elevation. Exploit sudo’s “Baron Samedit” flaw, CVE-2021-3156, a heap buffer overflow reachable through
sudoedit, to obtain root. - Then the system is compromised: clean the logs, install persistence, and pivot.
It is worth noting that the privilege-elevation step is itself a buffer overflow, which ties this walkthrough back to the first section.
How the Primitives Defend
The defenses map onto the two attacker steps:
- Against the attack vector. A web stack with WordPress is, to a degree, a fatality: that part of the adversary model is accepted, because public web input is exposed by design. What the primitives constrain is the consequence. File rights and read/execute restrictions (LSM, ACLs, extended attributes, plain permissions) limit what the web-service account can read, write, or run, so dropping and executing a
.phppayload is harder or its reach is smaller. Process isolation (namespaces, chroot, LSM) further confines the web process. - Against privilege elevation. Isolate the process so it does not have access to the programs it does not need (namespaces, chroot, LSM, capabilities). If the confined web process cannot reach a vulnerable
sudo, the CVE-2021-3156 path is cut off even though the bug exists on the system. This is least privilege applied to the blast radius of an already-compromised service.
Conclusion
The security primitives work and protect against a range of generic attacks, but the adversary models that matter are increasingly fatalistic: the realistic assumption is that a public-facing system will be compromised at some point, which is why the strategy shifts to defense in depth with process isolation. That shift breaks with the UNIX philosophy of small cooperating tools, because processes become autonomous through their isolation (systemd, dhcp, http). Service outsourcing (SaaS, remote monitoring, cloud, VoIP, telecom) is sold as administrative simplicity but carries extreme technical complexity and a loss of control over data. Hardware risks are underestimated: adversary models are often absent and the technologies are proprietary and obscure, which amounts to trust by submission (UEFI, BIOS, SecureBoot, TPM, AMT, PSP). The recurring conclusion of the series holds here as well: the value of a GNU/Linux security primitive depends on the adversary model, on what one is defending against, and identifying that model correctly is the task that gives the primitives their meaning.

Frequently Asked Questions
Q: In a stack buffer overflow, why does overwriting the buffer let an attacker take control of execution?
The stack stores a function’s local buffer together with the saved return address (RET) that tells the CPU where to resume when the function returns. Because the buffer is written upward toward RET, copying more bytes than the buffer holds (for example with an unchecked strcpy) runs past the buffer and overwrites the saved return address. When the function returns, the CPU jumps to whatever value now sits in RET, so an attacker who controls that value redirects execution to code of their choosing.
Q: What does each of the three mitigations (canary, NX, ASLR) target?
They address different links in the exploit chain. A stack canary places a random value before the return address and checks it on return, so an overflow that reaches RET is detected and the program aborts. NX marks the stack non-executable, so shellcode written into a stack buffer cannot run. ASLR randomizes the base addresses of the stack and heap, so an attacker cannot rely on fixed, known addresses to aim a jump. They are complementary rather than redundant, which is why they are deployed together.
Q: Why does the boot chain analysis assume a physical-access adversary, and what makes the bootloader stage sensitive?
Physical access (which also models a hostile virtualization host) is the strongest realistic local adversary and exposes the parts of the system that run before the OS protections are active. The bootloader is sensitive because it is the first distribution binary and it allows the kernel boot parameters to be edited; brief physical access to change that boot path is the evil maid attack. Defenses at this stage rely on encrypting partitions and on authentication and integrity of the boot components, often anchored in proprietary mechanisms such as SecureBoot, TPM, and OPAL drives.
Q: In the remote attack walkthrough, what privilege does the attacker hold after the WordPress exploit, and why is that not the end?
After exploiting the WordPress plugin to drop and run a PHP shell, the attacker executes code as the web-service account (for example www-data), not as root. That account is deliberately unprivileged, so it cannot by itself read protected files, install persistence cleanly, or fully control the host. The attacker therefore needs a separate privilege-elevation step, in the example the sudo CVE-2021-3156 heap overflow, to reach root.
Q: How would process isolation have stopped the privilege-elevation step even though the sudo bug was present on the system?
Privilege elevation through CVE-2021-3156 requires the compromised web process to actually reach and invoke the vulnerable sudo binary. If the web service runs inside a tight isolation boundary (namespaces, chroot, an LSM profile, a reduced capability set) that does not expose sudo or the paths it needs, the exploit has nothing to call, so the elevation fails despite the bug existing elsewhere on the machine. This is least privilege applied to containment: the goal is not only to prevent the initial compromise but to bound what a compromised service can reach next.
Q: The conclusion calls some adversary models “fatalistic” and says hardware risk is underestimated. How do these two ideas fit together with the series’ main thesis?
A fatalistic model assumes the system will be breached and plans for containment (defense in depth, isolation) rather than relying only on a hard perimeter, which is sound for exposed services. The hardware point is the blind spot in that same reasoning: firmware, out-of-band engines, and boot-trust technologies (UEFI, SecureBoot, TPM, AMT, PSP) are often adopted with no stated adversary model and are proprietary, so they are trusted by submission rather than by analysis. Both observations restate the series’ thesis: a primitive or technology has value only against an explicitly identified adversary, and an unstated or wrong model (whether for software or hardware) gives a false sense of protection.
References
- HEIG-VD - “Sécurité des systèmes d’exploitation”
- CVE-2021-3156 “Baron Samedit” — Qualys advisory
- GNU GRUB
- UEFI Forum
- coreboot
- Libreboot
- Trusted Computing Group (TPM)
- Claude Code