fanotify(7)めも

fapolicydがどのようにアプリケーションの実行を禁止しているのだろうか?と思って調べためもです。

fapolicydがアプリケーションの実行を禁止する仕組み

仕組みとしてはfanotifyの仕組みを使ってファイルが実行のために開かれた場合に通知を受け取り、設定されたルールを調べて実行可能かどうかを返すということをしてます。

fanotify

fanotifyの機能を使うには2つの関数があります。fanotify_init(2)fanotify_mark(2)の2つです。fanotify_init(2)で初期化をしてファイルディスクリプタを得ます。次にfanotify_mark(2)で通知を受け取りたいイベントなどを設定します。イベントの受信はpoll(2)を使います。pollfd構造体の配列のうち1つはfanotify_init(2)によって初期化したfdを設定します。

実行の許可

fanotify_response構造体のresponse変数にFAN_ALLOWもしくはFAN_DENYの値を設定することでその後の処理の継続を許可するか拒否するか設定します。レスポンスはfdに対して書き込みます。

サンプル

fanotify(7)にあるサンプルコードベースに作ってみました。実行するアプリケーションが/tmp/lsの場合だけ許可しません。

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/fanotify.h>
#include <fcntl.h>
#include <poll.h>
#include <errno.h>
#include <limits.h>
#include <unistd.h>

static void handle_events(int fd)
{
    struct fanotify_event_metadata *metadata;
    struct fanotify_event_metadata buf[256];
    struct fanotify_response response;
    ssize_t len;

    while (1) {
        len = read(fd, buf, sizeof(buf));
        if (len == -1 && errno != EAGAIN) {
            perror("read()");
            exit(-1);
        }

        if (len <= 0)
            break;

        metadata = buf;

        while (FAN_EVENT_OK(metadata, len)) {
            if (metadata->vers != FANOTIFY_METADATA_VERSION) {
                fprintf(stderr, "fanotify version mismatch\n");
                exit(-1);
            }

            if (metadata->fd >= 0) {
                if (metadata->mask & FAN_OPEN_EXEC_PERM) {
                    char *proc_path, path[PATH_MAX] = { '\0' };
                    ssize_t path_len;
                    
                    asprintf(&proc_path, "/proc/self/fd/%d", metadata->fd);
                    path_len = readlink(proc_path, path, sizeof(path) - 1);
                    if (path_len == 1) {
                        perror("readlink()");
                        exit(-1);
                    }

                    if (!strcmp(path, "/tmp/ls")) {
                        printf("[+]Deny execute application %s (pid:%d)\n", path, metadata->pid);
                        response.response = FAN_DENY;
                    } else
                        response.response = FAN_ALLOW;

                    response.fd = metadata->fd;
                    write(fd, &response, sizeof(response));

                    free(proc_path);
                }
            }

            close(metadata->fd);
            metadata = FAN_EVENT_NEXT(metadata, len);
        }
    }
}

int main(int argc, char **argv)
{
    int fd;
    int poll_num;
    struct pollfd fds[2];
    
    fd = fanotify_init(FAN_CLOEXEC | FAN_NONBLOCK | FAN_CLASS_CONTENT,
        O_RDONLY | O_LARGEFILE | O_CLOEXEC | O_NOATIME);
    
    if (fd == -1) {
        perror("fanotify_init()");
        exit(-1);
    }

    if (fanotify_mark(fd, FAN_MARK_ADD | FAN_MARK_MOUNT,
        FAN_OPEN_EXEC_PERM, -1, "/tmp") == -1 ) {
        perror("fanotify_mark()");
        exit(-1);
    }

    fds[0].fd = STDIN_FILENO;
    fds[0].events = POLLIN;
    fds[1].fd = fd;
    fds[1].events = POLLIN;

    printf("[+]Press ctrl-c to quit program\n");

    while (1) {
        poll_num = poll(fds, 2, -1);
        if (poll_num == -1) {
            if (errno == EINTR)
                continue;
            perror("poll()");
            exit(-1);
        }

        if (poll_num > 0) {
            if (fds[1].revents & POLLIN)
                handle_events(fd);
        }
    }

    return 0;
}

このコードを動かすとこんな感じです。

masami@kerntest:~/prevent_exec$ sudo ./a.out
[+]Press ctrl-c to quit program
[+]Deny execute application /tmp/ls (pid:3058)

実行を止められる側はこんな感じ。

masami@kerntest:~/prevent_exec$ /tmp/ls2
a.out  prevent_exec.c
masami@kerntest:~/prevent_exec$ /tmp/ls
-bash: /tmp/ls: Operation not permitted

Linuxプログラミングインタフェース

Linuxプログラミングインタフェース