Full Report
This post is from 2019, not was a defense-in-depth measure that I had not seen before. So, I thought it was worth making a note about! In OpenBSD, the system will block all system calls (syscall) NOT made by LibC wrappers. This prevents the usage of shellcode with syscalls in it. One could make the argument that an attacker could simply use Return Oriented Programming, right? The OpenBSD kernel relinks libc into a randomize location at boot. This means that the offsets into LibC are not obvious. So, ROPing into LibC isn't nearly as much of an option. The overall goal is to remove all syscalls that are outside of libc. The main program text segment needs to be in the list of memory regions where system calls are allowed to come from. The kernel can mark regions are valid for syscalls with the msyscall, since it can only be called once per process. For static binaries, the valid regions to make the calls would only be the text segment and the signal trampoline page. For dynamic binaries, ld.so/libc.so text, signal trampoline and the main programs text are the only regions. Together, these features are a powerful protection against ROP. Being able to restrict where syscalls can be run in combination with libc function stub randomization, PIE and everything else would be major blockers in the binary exploitation front.
Analysis Summary
# Best Practices: System-Call-Origin Verification (SCOV)
## Overview
These practices address **Return-Oriented Programming (ROP)** and binary exploitation. By restricting where system calls (syscalls) can originate in memory, an organization can prevent attackers from using "gadgets" to execute arbitrary kernel commands, even if they have achieved an initial memory corruption or code execution primitive.
## Key Recommendations
### Immediate Actions
1. **Enforce W^X (Write XOR Execute):** Ensure system memory pages are never simultaneously writable and executable. This is the foundational layer for syscall protection.
2. **Audit System Call Usage:** Identify applications (like older Go versions or custom assembly) that make direct system calls instead of using standard library (libc) wrappers.
3. **Update Core Libraries:** Ensure your OS and C libraries are updated to versions that support syscall origin validation and pointer randomization.
### Short-term Improvements (1-3 months)
1. **Transition to API-based Programming:** Shift development away from ABI (Application Binary Interface) dependence. Program to the API (libc stubs) so that the underlying syscall mechanism can be hardened without breaking the application.
2. **Enable Library Randomization:** Implement "re-linking at boot" or advanced ASLR (Address Space Layout Randomization) for `libc` to ensure function displacement offsets are non-predictable for ROP gadgets.
3. **Refactor Static Binaries:** Where possible, move toward dynamic linking to benefit from `ld.so` security enhancements and specialized memory region marking.
### Long-term Strategy (3+ months)
1. **Strict Origin Enforcement:** Reach a state where *all* syscalls originate exclusively from the `libc` text segment or signal trampoline pages, eliminating the main program text as a valid origin.
2. **Adopt Lock-Step Environment Management:** Synchronize kernel and user-space development (as seen in OpenBSD) to ensure the C library never lags behind kernel syscall availability, removing the "need" for raw syscalls.
## Implementation Guidance
### For Small Organizations
- **Monitor OS Defaults:** Leverage security-focused operating systems (like OpenBSD) where these features are enabled by default, reducing the need for manual configuration.
- **Dependency Management:** Use standard, modern compilers that automatically support PIE (Position Independent Executables).
### For Medium Organizations
- **Sandboxing via Seccomp:** On Linux environments, use `seccomp-bpf` to filter syscalls. While not native origin verification, it can be configured to check the Instruction Pointer (IP) to ensure calls come from expected memory ranges.
- **Language Updates:** Move Go-based toolchains to versions that utilize libc wrappers for syscalls rather than direct traps.
### For Large Enterprises
- **Binary Integrity Verification:** Implement tools to verify that syscall instructions in memory correspond to valid instructions in the on-disk binary.
- **Systematic Re-linking:** Use automated deployment pipelines to re-link core system libraries periodically (not just at boot) to further invalidate ROP gadget offsets across the fleet.
## Configuration Examples
### OpenBSD Kernel/Linker Logic
The `msyscall()` system call is used by the dynamic linker to register valid memory regions.
* **Action:** Invoke `msyscall(addr, len)` via `ld.so`.
* **Constraint:** This call is "write-once"; subsequent attempts to change the allowed regions within the same process will be rejected.
### Linux/Seccomp Alternative (Conceptual)
Using Seccomp-BPF to inspect the instruction pointer:
c
// Pseudo-logic for BPF filter
if (syscall_number == EXECVE) {
if (instruction_pointer < LIBC_START || instruction_pointer > LIBC_END) {
return SECCOMP_RET_KILL;
}
}
## Compliance Alignment
* **NIST SP 800-53:** SI-7 (Software, Firmware, and Information Integrity).
* **CIS Benchmarks:** Guidance on enabling ASLR, DEP/NX, and specialized kernel hardening.
* **ISO/IEC 27001:** A.12.5.1 (Installation of software on operational systems).
## Common Pitfalls to Avoid
* **Raw Assembly Traps:** Using `int $0x80` or `syscall` instructions directly in application code. This will cause the process to be killed under origin verification.
* **ABI Reliance:** Hardcoding syscall numbers or assuming fixed offsets in `libc`.
* **Writable Memory Segments:** Failing to enforce W^X, which allows attackers to inject their own syscall-originating code.
## Resources
* **OpenBSD Manual Pages:** `man msyscall(2)` - [https://man.openbsd.org/msyscall]
* **LWN.net Research:** Detailed analysis of kernel security features - [https://lwn.net/]
* **MITRE ATT&CK:** Technique T1055 (Process Injection) and T1211 (Exploitation for Privilege Escalation).