2021-05-20

seccomp user notification defers the seccomp decisions to userspace. This post Seccomp Notify has a very detail description of this feature. The page has an example of seccomp. I change this example to following: seccomp BPF will forward the listen syscall’s decision to userspace. And the tracer will print the listen port and can block the specified port to be listenend. Just a poc and the program doesn’t exit normally.

    #define _GNU_SOURCE
    #include <sys/types.h>
    #include <sys/prctl.h>
    #include <fcntl.h>
    #include <limits.h>
    #include <signal.h>
    #include <sys/wait.h>
    #include <stddef.h>
    #include <stdbool.h>
    #include <linux/audit.h>
    #include <sys/syscall.h>
    #include <sys/stat.h>
    #include <linux/filter.h>
    #include <linux/seccomp.h>
    #include <sys/ioctl.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <errno.h>
    #include <netinet/in.h>

    #include "scm_functions.h"

    #define errExit(msg)    do { perror(msg); exit(EXIT_FAILURE); \
                            } while (0)

    static int
    seccomp(unsigned int operation, unsigned int flags, void *args)
    {
        return syscall(__NR_seccomp, operation, flags, args);
    }

    static int
    pidfd_getfd(int pidfd, int targetfd, unsigned int flags)
    {
        return syscall(438, pidfd, targetfd, flags);
    }

    static int
    pidfd_open(pid_t pid, unsigned int flags)
    {
        return syscall(__NR_pidfd_open, pid, flags);
    }


    #define X32_SYSCALL_BIT         0x40000000

    #define X86_64_CHECK_ARCH_AND_LOAD_SYSCALL_NR \
            BPF_STMT(BPF_LD | BPF_W | BPF_ABS, \
                    (offsetof(struct seccomp_data, arch))), \
            BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, AUDIT_ARCH_X86_64, 0, 2), \
            BPF_STMT(BPF_LD | BPF_W | BPF_ABS, \
                    (offsetof(struct seccomp_data, nr))), \
            BPF_JUMP(BPF_JMP | BPF_JGE | BPF_K, X32_SYSCALL_BIT, 0, 1), \
            BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS)


    static int
    installNotifyFilter(void)
    {
        struct sock_filter filter[] = {
            X86_64_CHECK_ARCH_AND_LOAD_SYSCALL_NR,

            /* mkdir() triggers notification to user-space tracer */

            BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_listen, 0, 1),
            BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_USER_NOTIF),

            /* Every other system call is allowed */

            BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
        };

        struct sock_fprog prog = {
            .len = (unsigned short) (sizeof(filter) / sizeof(filter[0])),
            .filter = filter,
        };

        int notifyFd = seccomp(SECCOMP_SET_MODE_FILTER,
                            SECCOMP_FILTER_FLAG_NEW_LISTENER, &prog);
        if (notifyFd == -1)
            errExit("seccomp-install-notify-filter");

        return notifyFd;
    }

    static void
    closeSocketPair(int sockPair[2])
    {
        if (close(sockPair[0]) == -1)
            errExit("closeSocketPair-close-0");
        if (close(sockPair[1]) == -1)
            errExit("closeSocketPair-close-1");
    }

    static pid_t
    targetProcess(int sockPair[2], char *argv[])
    {
        pid_t targetPid;
        int notifyFd;
        struct sigaction sa;
        int s;
        int sockfd;
        struct sockaddr_in sockaddr;

        targetPid = fork();
        if (targetPid == -1)
            errExit("fork");

        if (targetPid > 0)          /* In parent, return PID of child */
            return targetPid;


        printf("Target process: PID = %ld\n", (long) getpid());

        if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0))
            errExit("prctl");

        notifyFd = installNotifyFilter();


        if (sendfd(sockPair[0], notifyFd) == -1)
            errExit("sendfd");

        if (close(notifyFd) == -1)
            errExit("close-target-notify-fd");

        closeSocketPair(sockPair);

        sockfd = socket(AF_INET, SOCK_STREAM, 0);
        sockaddr.sin_family = AF_INET;
        sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);
        sockaddr.sin_port = htons(80);
        if (bind(sockfd, (struct sockaddr*)&sockaddr, sizeof(sockaddr)))
            errExit("Target process: bind error");
        if (listen(sockfd, 1024))
            errExit("Target process: listen error");
        printf("listen success\n");
        

    }

    static void
    checkNotificationIdIsValid(int notifyFd, __u64 id, char *tag)
    {
        if (ioctl(notifyFd, SECCOMP_IOCTL_NOTIF_ID_VALID, &id) == -1) {
            fprintf(stderr, "Tracer: notification ID check (%s): "
                    "target has died!!!!!!!!!!!\n", tag);
        }
    }

    /* Handle notifications that arrive via SECCOMP_RET_USER_NOTIF file
    descriptor, 'notifyFd'. */

    static void
    watchForNotifications(int notifyFd)
    {
        struct seccomp_notif *req;
        struct seccomp_notif_resp *resp;
        struct seccomp_notif_sizes sizes;
        char path[PATH_MAX];
        int procMem;        /* FD for /proc/PID/mem of target process */

        int pidfd;
        int listennum;
        int listenfd;

        struct sockaddr_in sa;
        int salen = sizeof(sa);

        if (seccomp(SECCOMP_GET_NOTIF_SIZES, 0, &sizes) == -1)
            errExit("Tracer: seccomp-SECCOMP_GET_NOTIF_SIZES");

        req = malloc(sizes.seccomp_notif);
        if (req == NULL)
            errExit("Tracer: malloc");

        resp = malloc(sizes.seccomp_notif_resp);
        if (resp == NULL)
            errExit("Tracer: malloc");

        /* Loop handling notifications */

        for (;;) {

            /* Wait for next notification, returning info in '*req' */

            if (ioctl(notifyFd, SECCOMP_IOCTL_NOTIF_RECV, req) == -1)
                errExit("Tracer: ioctlSECCOMP_IOCTL_NOTIF_RECV");

            printf("Tracer: got notification for PID %d; ID is %llx\n",
                    req->pid, req->id);


        pidfd = pidfd_open(req->pid, 0);
        listennum = req->data.args[0];
        listenfd = pidfd_getfd(pidfd, listennum, 0);
        getsockname(listenfd, &sa, &salen);
        printf("Tracer: listen %d port\n", ntohs(sa.sin_port));

            resp->id = req->id;
            resp->flags = SECCOMP_USER_NOTIF_FLAG_CONTINUE;        
            resp->error = 0;

            resp->val = 0;

            if (ioctl(notifyFd, SECCOMP_IOCTL_NOTIF_SEND, resp) == -1) {
                if (errno == ENOENT)
                    printf("Tracer: response failed with ENOENT; perhaps target "
                            "process's syscall was interrupted by signal?\n");
                else
                    perror("ioctl-SECCOMP_IOCTL_NOTIF_SEND");
            }
        }
    }

    static pid_t
    tracerProcess(int sockPair[2])
    {
        pid_t tracerPid;

        tracerPid = fork();
        if (tracerPid == -1)
            errExit("fork");

        if (tracerPid > 0)          /* In parent, return PID of child */
            return tracerPid;

        /* Child falls through to here */

        printf("Tracer: PID = %ld\n", (long) getpid());

        /* Receive the notification file descriptor from the target process */

        int notifyFd = recvfd(sockPair[1]);
        if (notifyFd == -1)
            errExit("recvfd");

        closeSocketPair(sockPair);  /* We no longer need the socket pair */

        /* Handle notifications */

        watchForNotifications(notifyFd);

        exit(EXIT_SUCCESS);         /* NOTREACHED */
    }

    int main(int argc, char *argv[])
    {
        pid_t targetPid, tracerPid;
        int sockPair[2];

        setbuf(stdout, NULL);

        if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockPair) == -1)
            errExit("socketpair");

        targetPid = targetProcess(sockPair, &argv[optind]);

        tracerPid = tracerProcess(sockPair);

        closeSocketPair(sockPair);

        waitpid(targetPid, NULL, 0);
        printf("Parent: target process has terminated\n");

        waitpid(tracerPid, NULL, 0);

        exit(EXIT_SUCCESS);
    }


blog comments powered by Disqus