
The other post, I’ve been mostly describing the operational burden of managing seccomp profiles and describing scenarios where the seccomp profile might not be as effective as we think. This is the post I wanted to get back attacker perspective. You have a container. Seccomp is enabled. The profile looks reasonable – no bpf, no ptrace, network syscalls restricted. What’s do we do?
As I’ve been compiling this research, I learned that there are a number of situations where a seccomp filter can be bypassed. They fall into a few categories:
SCMP_ACT_KILL (thread) and SCMP_ACT_KILL_PROCESS, or SCMP_ACT_LOG to probe the filter without dying (covered in Building Insecure Profiles)Let’s talk about multiplexing next. Let’s assume the role of an attacker that has RCE in the environment. Let’s even assume that the container is running with CAP ADD ALL to grant it all capabilities and effectively rendering it not a container. Then let’s see if we can bypass seccomp.
I wrote about io_uring Attacks but let’s put it to a practical example.
The operation types that are available via io_uring_enter():
IORING_OP_SOCKET – create a socketIORING_OP_CONNECT – connect to an addressIORING_OP_SEND, IORING_OP_RECV – send and receive dataIORING_OP_OPENAT – open a fileIORING_OP_READ, IORING_OP_WRITE – read and write dataIORING_OP_CLOSE, IORING_OP_ACCEPT, IORING_OP_EPOLL_CTL, IORING_OP_FUTEX_WAIT…Let’s start off with a well meaning seccomp profile that blocks all network related system calls like socket, connect, and send.
{
"defaultAction": "SCMP_ACT_ALLOW",
"syscalls": [
{
"names": ["socket", "connect", "send", "sendto", "sendmsg", "recv", "recvfrom", "recvmsg"],
"action": "SCMP_ACT_ERRNO"
}
]
}
You’ve effectively blocked all network activity. You reviewed the profile and confirmed those syscalls return SCMP_ACT_ERRNO. Good work. But if you were to call io_uring_setup() to create a ring, submit IORING_OP_SOCKET + IORING_OP_CONNECT + IORING_OP_SEND through io_uring_enter()… you’d have a network connection. No syscall on your block list was ever called. Seccomp never triggered.
You’ve got RCE in a container. You check seccomp status:
grep Seccomp /proc/self/status
# Seccomp: 2
# Seccomp_filters: 1
Filter mode. One filter loaded. You probe for io_uring:
// Try io_uring_setup -- if this returns a valid fd, you win
int ring_fd = syscall(SYS_io_uring_setup, 32, ¶ms);
if (ring_fd >= 0) {
// io_uring is allowed. Network blocks are decorative.
// Set up submission queue, submit IORING_OP_SOCKET + IORING_OP_CONNECT
// Establish C2 channel to your server
// No socket(), connect(), send(), or recv() syscall was ever invoked
}
No socket() call was ever made. Seccomp never triggered. Your audit logs show nothing. Your network policy might catch the traffic at the CNI level, but the seccomp bypass is clean.
You’ve got RCE. Here’s the checklist for post exploitation discovery:
Check seccomp status: grep Seccomp /proc/self/status. 0 = disabled, 1 = unlikely to see but strict mode, and 2 = filter mode, proceed to step 2. 2 is turned on in all modern linux distros in my experience but there are always exceptions.
Probe tier 1 syscalls. Try bpf(), ptrace(), io_uring_setup(). If any succeed (or return anything other than EPERM/ENOSYS), you maybe have your vector. This will be loud though so if there’s any runtime monitoring, it’s easy to assume this is signal of a compromise.
io_uring available? Set up a ring. Submit IORING_OP_SOCKET + IORING_OP_CONNECT. Establish a C2 channel without touching any network syscall. Exfiltrate via IORING_OP_SEND. Your seccomp-blocked socket() and connect() were never invoked.
bpf available? Load an eBPF program. Hook sys_getdents64 to hide your files from ls. Hook sys_read on /proc/*/status to filter your processes. Hook sys_bpf to hide the eBPF programs themselves. This is the LinkPro rootkit pattern – real malware, used in the wild after exploiting Jenkins.
Nothing obvious? Try the x32 ABI. Call blocked syscalls via 0x40000000 + syscall_nr. If the profile doesn’t declare SCMP_ARCH_X32, the filter might not cover those numbers.
The honest assessment: a well-maintained, reviewed, architecture-aware profile makes actual exploitation harder. It costs the attacker real time and effort. NOTE: There are some subtle games you can play to make it less obvious that you’re enumerating all of the available system calls at your disposal but you’ll have to think about that harder.
So if building seccomp profiles is hard, and we know there are ways of mucking it up, how do we make sure that we don’t?
Last section: Measuring Scoring and Scaling with Seccompute