clinuxseccomp

Pointer return values in seccomp systemcall trap functions


I'm trying to implement trap functions for different systemcalls. The target is, that a broker will execute them and then return the result. So the client won't execute the commands itself.

Seccomp offers the ability to achieve this:

What have i done?

  1. Initialize a signal handler vor SIGSYS signals.
  2. Initialized a seccomp filter with the Action SCMP_ACT_TRAP.
  3. Wait for a SA_SIGINFO signal.
  4. Modify the return value of the resulting system call.

In general, this method works. It is also used like this in the chromium project and by mozilla.

The Problem

Change the return value for system calls returning integers like open works flawlessly. Changing the return value for functions returning pointers does not work (for example getcwd).

Somehow, just the first parameter is returned and this not even in all cases. Sometimes NULL is returned.

What i also tried

I also created a working example using ptrace. The ptrace solution intercepts the system calls by changing the instruction pointer to another user space function and modifying the return call. That solution works, but is a little bit hacky and not preferred due to the use of ptrace in the background.

Example Code

Here is the minimalistic break down of the code.

#define _GNU_SOURCE
#include <stdio.h>
#include <sys/stat.h>
#include <signal.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <unistd.h>
#include <seccomp.h>
#include <fcntl.h>
#include <sys/prctl.h>
#include <linux/seccomp.h>
#include <sys/socket.h>
#include <dirent.h>
#include <linux/filter.h>
#include <ucontext.h>

extern int errno;


#define SECCOMP_REG(_ctx, _reg) ((_ctx)->uc_mcontext.gregs[(_reg)])
#define SECCOMP_RESULT(_ctx)    SECCOMP_REG(_ctx, REG_RAX)
#define SECCOMP_SYSCALL(_ctx)   SECCOMP_REG(_ctx, REG_RAX)
#define SECCOMP_IP(_ctx)        SECCOMP_REG(_ctx, REG_RIP)
#define SECCOMP_PARM1(_ctx)     SECCOMP_REG(_ctx, REG_RDI)
#define SECCOMP_PARM2(_ctx)     SECCOMP_REG(_ctx, REG_RSI)
#define SECCOMP_PARM3(_ctx)     SECCOMP_REG(_ctx, REG_RDX)
#define SECCOMP_PARM4(_ctx)     SECCOMP_REG(_ctx, REG_R10)
#define SECCOMP_PARM5(_ctx)     SECCOMP_REG(_ctx, REG_R8)
#define SECCOMP_PARM6(_ctx)     SECCOMP_REG(_ctx, REG_R9)


static char fake[100] = "fake";

/*
* Catch violations so we see, which system call caused the problems
*/
static void catchViolation(int sig, siginfo_t* si, void* void_context)
{
    int old_errno = errno;

    printf("Attempted banned syscall number [%d] see doc/Seccomp.md for more information [%d]\n",
           si->si_syscall, sig);

    ucontext_t* ctx = (ucontext_t*)void_context;

    // Just printing some registers for debugging
    printf("RAX IS: %p\n", (void*)SECCOMP_RESULT(ctx));
    printf("RIP IS: %p\n", (void*)SECCOMP_IP(ctx));
    printf("RDI IS: %p\n", (void*)SECCOMP_PARM1(ctx));
    printf("RSI IS: %p\n", (void*)SECCOMP_PARM2(ctx));
    printf("RDX IS: %p\n", (void*)SECCOMP_PARM3(ctx));
    printf("R10 IS: %p\n", (void*)SECCOMP_PARM4(ctx));
    printf("R8 IS: %p\n", (void*)SECCOMP_PARM5(ctx));
    printf("R9 IS: %p\n", (void*)SECCOMP_PARM6(ctx));

    // Set register 4 to 0 according to ABI and set return value
    // to fake address
    SECCOMP_PARM4(ctx) = 0;
    SECCOMP_RESULT(ctx) = (greg_t)fake;

    printf("RAX After Change: %p\n", (void*)SECCOMP_RESULT(ctx));

    errno = old_errno;
}

/*
* Setup error handling
*/
static void init_error_handling(){
    struct sigaction sa = { .sa_sigaction = catchViolation, .sa_flags = SA_SIGINFO | SA_NODEFER };
    if (sigaction(SIGSYS, &sa, NULL)){
        printf("Failed to configure SIGSYS handler [%s]\n", strerror(errno));
    }
}


void init_seccomp_filters(){
    if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
        perror("Could not start seccomp:");
        exit(1);
    }

    scmp_filter_ctx ctx;
    ctx = seccomp_init(SCMP_ACT_TRAP);

    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0);
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0);
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(recvmsg), 0);
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(rt_sigreturn), 0);
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(fstat), 0);
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(lstat), 0);
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(writev), 0);
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(readlink), 0);
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(sendmsg), 0);
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(getppid), 0);
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit), 0);
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0);

    if (seccomp_load(ctx)== -1) {
        perror("Could not start seccomp:");
        exit(1);
    }
}


int main(){
    init_error_handling();
    init_seccomp_filters();

    char dir[100] = "hello";

    printf("CALL GETCWD\n");
    char *t = getcwd(dir, 100);

    printf("---------------------\n");
    printf("PTR IS: %p\n", t);
    printf("EXPECTED: %p\n", fake);
    printf("Text is - %s\n", t);

    exit(0);
}

Console output

// SITUATION 1 RETURNING WRONG POINTER
CALL GETCWD
Attempted banned syscall number [79] see doc/Seccomp.md for more information [31]
RAX IS: 0x4f
RIP IS: 0x7f3c1dadff8a
RDI IS: 0x7fff983f8940
RSI IS: 0x64
RDX IS: 0x7f3c1dd9f760
R10 IS: 0x61c
R8 IS: 0x3
R9 IS: 0x410
RAX After Change: 0x563659aa70a0
---------------------
PTR IS: 0x7fff983f8940
EXPECTED: 0x563659aa70a0
Text is - hello

// SITUATION 2 RETURNING NULL
CALL GETCWD
Attempted banned syscall number [79] see doc/Seccomp.md for more information [31]
RAX IS: 0x4f
RIP IS: 0x7eff3372bf8a
RDI IS: 0x7ffce201d880
RSI IS: 0x64
RDX IS: 0x7eff339eb760
R10 IS: 0x61c
R8 IS: 0x3
R9 IS: 0x410
RAX After Change: 0x55fcab2c70a0
---------------------
PTR IS: (nil)
EXPECTED: 0x55fcab2c70a0
Text is - (null)

Solution

  • AFAIK, you properly need this What are the return values of system calls in Assembly?

    It's just the Linux syscall conversion, that any return vale from -1 to -4096 are treated as errno.

    also, refer to here https://code.woboq.org/userspace/glibc/sysdeps/unix/sysv/linux/hppa/syscall.c.html

      if ((unsigned long int) __sys_res >= (unsigned long int) -4095)
        {
          __set_errno (-__sys_res);
          __sys_res = -1;
        }
      return __sys_res;
    
    

    Also, I curious why is your fake pointer so large?

    PS: don't declare errno, it's defined in the system header and usually as macro.

    And for getcwd, this Linux syscall doesn't return a pointer, it's defined as this int __getcwd(char* buf, size_t size)