I want to launch untrusted compute-only executables in linux server. Process will have no access to system and files except stdin and stdout

My idea is to use ptrace to catch and block syscalls to linux kernel. Also i use it to get and set process internal state(registers + RAM). Is it safe sandbox? What ways you know to brake it?

Also i want to limit RAM and CPU time usage to avoid DOS

  • 1
    this question seems to be a Linux question.
    – Skaperen
    Nov 12, 2017 at 8:32

1 Answer 1


This is exactly what seccomp is for. Seccomp is supported in most modern Linux kernels and is designed to filter syscalls. It comes in two forms, called mode 1 and mode 2.

Mode 1 seccomp

The process is allowed only 4 system calls: read(), write(), rt_sigreturn(), and exit() (note that this is the exit() syscall, not the function. The glibc function uses the non-whitelisted exit_group() syscall). If any other calls are attempted, they will not return, and the program will be killed. This is intended for computing untrusted bytecode in a secure broker process. The trusted code can create an untrusted process which executes potentially dangerous bytecode after enabling mode 1 seccomp, and can communicate with the parent through pipes.

Mode 2 seccomp

This is also called seccomp-bpf, as it uses eBPF bytecode to create a dynamic filter for restricting syscalls based on both their number and their arguments. It can furthermore be set to take a variety of actions on violation, from killing the process by force, to denying the syscall and raising a signal to be trapped without killing the process, to returning a custom errno, to simply denying the syscall for testing purposes. The libseccomp library abstracts much of this, making it unnecessary to write eBPF bytecode yourself.

Both of these methods are significantly faster than a ptrace-based sandbox, which would incur significant overhead. Also, a ptrace-sandbox would not necessarily send its filters to any child, so calls like execve(), fork(), vfork(), and clone() would need to be disabled, lest you become vulnerable to TOCTOU race conditions. Both seccomp modes, on the other hand, preserve the filter across any execution or fork.

Example using mode 1 seccomp that securely runs "return 42" in bytecode:

#include <unistd.h>
#include <stdint.h>
#include <stdio.h>
#include <sys/prctl.h>
#include <sys/syscall.h>
#include <linux/seccomp.h>

void main(void)
    /* "mov al,42; ret" aka "return 42" */
    static const unsigned char code[] = "\xb0\x2a\xc3";
    int fd[2], ret;

    /* spawn child process, connected by a pipe */
    if (fork() == 0) {
        /* we're the child, so let's close this end of the pipe */

        /* enter mode 1 seccomp and execute untrusted bytecode */
        ret = (*(uint8_t(*)())code)();

        /* send result over pipe, and exit */
        write(fd[1], &ret, sizeof(ret));
        syscall(SYS_exit, 0);
    } else {
        /* we're the parent, so let's close this end of the pipe */

        /* read the result from the pipe, and print it */
        read(fd[0], &ret, sizeof(ret));
        printf("untrusted bytecode returned %d\n", ret);

Example using mode 2 seccomp, with several arbitrary syscall filters:

#include <seccomp.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>

void main(void)
    /* initialize the libseccomp context */
    scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_KILL);

    /* allow exiting */
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0);

    /* allow getting the current pid */
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(getpid), 0);

    /* allow changing data segment size, as required by glibc */
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(brk), 0);

    /* allow writing up to 512 bytes to fd 1 */
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 2,
        SCMP_A0(SCMP_CMP_EQ, 1),
        SCMP_A2(SCMP_CMP_LE, 512));

    /* if writing to any other fd, return -EBADF */
    seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EBADF), SCMP_SYS(write), 1,
        SCMP_A0(SCMP_CMP_NE, 1));

    /* load and enforce the filters */

    printf("this process is %d\n", getpid());

There are a few important things to remember with seccomp:

  • Calls which are not "true" system calls but rather vDSOs such as gettimeofday() and time() cannot be filtered. For performance, they execute in userspace, avoiding an expensive context switch. However this also prevents seccomp from being aware that they are running. This typically isn't a problem because the only syscalls that can be implemented as a vDSO are typically very simple and expose virtually no attack surface area.

  • Prior to Linux 4.8 (?), a whitelisted ptrace() call can be used to escape the sandbox by modifying the registers after the call had been permitted, but before it actually executes. The solution on kernels older than 4.8 is simply to not whitelist that call.

  • As syscalls work by passing registers to the kernel, seccomp (and any ptrace-based sandbox) can only filter based on the contents of the registers themselves. This means that arguments which contain pointers to memory, such as the file name provided to open(), cannot be filtered. Seccomp only checks the contents of the registers, and cannot examine memory.

  • Filters cannot be revoked or changed once they are in place. If you want to use multiple stages of sandboxing, start out with a more loose policy, and ensure seccomp() (on >= Linux 3.17) and prctl() are whitelisted until the next stage loads, since they are necessary for adding new filters. The second stage sandbox should either whitelist of the same syscalls as the first stage, minus ones you want disabled, or a selective blacklist of the syscalls you want disabled.


You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .