Search code examples
clinuxruntime-errorbpfseccomp

Getting "Bad System Call" working with seccomp filters


I have just started learning about seccomp filters and I am using libseccomp v2.4.4. I tried to write a basic whitelisting filter that will only allow writing to the file named file1 but I am getting a "Bad system call" message in STDOUT. Here is my code:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <seccomp.h>
#include <stdlib.h>
#include <errno.h>

int main(void)
{
        FILE *fil = fopen("file1", "w");
        scmp_filter_ctx filter = seccomp_init(SCMP_ACT_KILL);

        if (filter==NULL || NULL==fil)
                goto End1;

        int chk1 = seccomp_rule_add(filter, SCMP_ACT_ALLOW, SCMP_SYS(write), 1,
                                    SCMP_A0(SCMP_CMP_EQ, fileno(fil)));
        int chk2 = seccomp_load(filter);
        if (chk1<0 || chk2<0)
                goto End;

        fprintf(stdout,"Filter did not work correctly\n");
        fprintf(fil,"Filter worked correctly\n");

End:
        seccomp_release(filter); //releasing filter before closing file
        fclose(fil);

End1:
        return 0;
}

Additionally, I see no output in file1. I am just a beginner and unsure about a lot of things here as I have written the code from my understanding, not from some reference. What seems to be the issue?


Edit: I removed the filter completely and simply executed

int main(void)
{
        FILE *fil = fopen("file1", "w");

        fprintf(stdout,"Filter did not work correctly\n");
        fprintf(fil,"Filter worked correctly\n");

End:
        fclose(fil);

End1:
        return 0;
}

Stracing this I observed that syscalls fstat,close as mentioned in the answer below by @pchaigno and additionally mmap,munmap were being called. So I must allow these syscalls for the expected behaviour.


Solution

  • Debugging

    Stracing your application will reveal the issue:

    $ strace ./test
    [...]
    seccomp(SECCOMP_SET_MODE_FILTER, 0, {len=12, filter=0x55ace60a6600}) = 0
    fstat(1,  <unfinished ...>)             = ?
    +++ killed by SIGSYS (core dumped) +++
    Bad system call (core dumped)
    

    Allow minimum set of syscalls

    You need to allow a lot more syscalls than just write(2) for the end of your program to proceed without errors.

    You'll need fstat(2) for fprintf(), close(2) for fclose(), and exit_group(2) to end the process.

    Deny syscall instead of killing process

    In addition, if you want your program to continue running after an unauthorized syscall, you shouldn't kill it with SCMP_ACT_KILL. SCMP_ACT_ERRNO(EPERM) is probably more appropriate in this context.

    Result

    int main(void) {
            FILE *fil = fopen("file1", "w");
            scmp_filter_ctx filter = seccomp_init(SCMP_ACT_ERRNO(EPERM));
            if (filter == NULL || NULL == fil)
                    return 1;
            if (seccomp_rule_add(filter, SCMP_ACT_ALLOW, SCMP_SYS(write), 1, SCMP_A0(SCMP_CMP_EQ,fileno(fil))) < 0)
                    goto err;
            if (seccomp_rule_add(filter, SCMP_ACT_ALLOW, SCMP_SYS(fstat), 0) < 0)
                    goto err;
            if (seccomp_rule_add(filter, SCMP_ACT_ALLOW, SCMP_SYS(close), 0) < 0)
                    goto err;
            if (seccomp_rule_add(filter, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0) < 0)
                    goto err;
            if (seccomp_load(filter) < 0)
                    goto err;
            fprintf(stdout, "Filter did not work correctly\n");
            fprintf(fil, "Filter worked correctly\n");
    err:
            seccomp_release(filter); //releasing filter before closing file
            fclose(fil);
            return 0;
    }
    

    gives us:

    $ ./test; echo file1
    Filter worked correctly