Saeshnikov / Linux-monitoring-utility

MIT License
2 stars 0 forks source link

The problem with finding the full path to the file #104

Closed anutatesl closed 2 months ago

anutatesl commented 4 months ago
#ifndef BPFTRACE_HAVE_BTF
#include <linux/sched.h>
#endif

BEGIN
{
    printf("Tracing file system syscalls... Hit Ctrl-C to end.\n");
    printf("%-6s %-16s %-16s %4s %-60s %-4s %-4s\n", "PID", "COMM", "COMMTASK", "FD", "PATH", "R", "W");
}

tracepoint:syscalls:sys_enter_open,
tracepoint:syscalls:sys_enter_openat
{
    @filename[tid] = args.filename;
}

tracepoint:syscalls:sys_exit_open,
tracepoint:syscalls:sys_exit_openat
/@filename[tid]/
{
    $ret = args.ret;
    @fd[tid] = $ret >= 0 ? $ret : -1;
}

tracepoint:syscalls:sys_exit_write,
tracepoint:syscalls:sys_exit_writev,
tracepoint:syscalls:sys_exit_pwritev
/@fd[tid]/
{
    $task = (struct task_struct *)curtask;

    $ret = args.ret;
    $nbyte = $ret >= 0 ? $ret : -1;
    $nothing = "-";
    //printf("%-6d %-16s %-16 %4d %-60s %-4s %-4d\n", pid, comm, ((struct mm_struct *)$task->mm)->exe_file->f_path, @fd[tid], str(@filename[tid]), $nothing, $ret);

    //1.Вывод того, что выдает структура f_path
    //Вывод: { .mnt = 0xffff973af64f31a0, .dentry = 0xffff973ad1517240 }
    print(((struct mm_struct *)$task->mm)->exe_file->f_path);

    //2.Попытка получить путь к файлу через функцию path
    //Ошибка: The path function can only be used with 'kfunc', 'kretfunc', 'iter' probes
    //print(path(((struct mm_struct *)$task->mm)->exe_file->f_path));

    delete(@filename[tid]);
    delete(@fd[tid]);
}

//Аналогично tracepoint:syscalls:sys_exit_write
tracepoint:syscalls:sys_exit_read,
tracepoint:syscalls:sys_exit_readv,
tracepoint:syscalls:sys_exit_preadv
/@fd[tid]/
{

    $task = (struct task_struct *)curtask;

    $ret = args.ret;
    $nbyte = $ret >= 0 ? $ret : -1;
    $nothing = "-";
    //printf("%-6d %-16s %-16s %4d %-60s %-4d %-4s\n", pid, comm, str($task->nameidata->name->name), @fd[tid], str(@filename[tid]), $ret, $nothing);

    //1.Вывод того, что выдает структура f_path
    //Вывод: { .mnt = 0xffff973af64f31a0, .dentry = 0xffff973ad1517240 }
    //print(((struct mm_struct *)$task->mm)->exe_file->f_path);

    //2.Попытка получить путь к файлу через функцию path
    //Ошибка: The path function can only be used with 'kfunc', 'kretfunc', 'iter' probes
    //print(path(((struct mm_struct *)$task->mm)->exe_file->f_path));

    delete(@filename[tid]);
    delete(@fd[tid]);
}

/*
Попытались использовать kfunc.
Проблема: Невозможно сделать фильтрацию ни по имени файла, ни по файловому дескриптору,
    так как kfunc:vmlinux:vfs_write имеет следующие аргументы:
                                        struct file * file
                                        const char * buf
                                        size_t count
                                        loff_t * pos
                                        ssize_t retval
    ,ни один из которых не может служить фильтром
kfunc:vfs_write
/???/
{
    //Опускали момент с фильтрацией и просто пытались вывести путь хоть как-то
    //ERROR: helper bpf_get_current_task not supported in probe    $task = (struct task_struct *)curtask;
    $task = (struct task_struct *)curtask;
    printf("%s", path(((struct mm_struct *)$task->mm)->exe_file->f_path));

    //Пробовали вывести путь используя аргумент kfunc:vmlinux:vfs_write - struct file * file
    //Ошибка:ERROR: Unknown identifier: 'file' printf("%s", path(file->f_path));
    printf("%s", path(file->f_path));

}*/

END
{
    clear(@filename);
    clear(@fd);
}
denis-koptev commented 4 months ago

После просмотра хедеров ядра пока удалось вывести из dentry имя файла, номер inode, имя parent директории. Пока совсем "втупую" без проверок и прочего.

printf("%-6d %-16s %s %lu %s\n", pid, comm,
          str($task->mm->exe_file->f_path.dentry->d_name.name),
          $task->mm->exe_file->f_path.dentry->d_inode->i_ino,
          str($task->mm->exe_file->f_path.dentry->d_parent->d_name.name));

Подключенные хедера в скрипте:

#include <linux/sched.h>
#include <linux/mm.h>

В итоге:

dkoptev@build:~$ sudo bpftrace test.bt | grep prometheus
758    prometheus-node  prometheus-node-exporter 1052741 bin
758    prometheus-node  prometheus-node-exporter 1052741 bin
...

dkoptev@build:~$ ls -i /usr/bin/prometheus-node-exporter
1052741 /usr/bin/prometheus-node-exporter

Покопайте дальше. Наверняка и через kprobe, и через tracepoint как-то напечатать/вычислить полный путь можно.

anutatesl commented 4 months ago
#ifndef BPFTRACE_HAVE_BTF
#include <linux/sched.h>
#include <linux/limits.h>
#endif

struct my_spa_t {
  char spa_name[64];
}

BEGIN
{
    printf("Tracing file system syscalls... Hit Ctrl-C to end.\n");
    printf("%-6s %-16s %-16s %4s %-60s %-4s %-4s\n", "PID", "COMM", "COMMTASK", "FD", "PATH", "R", "W");
}

tracepoint:syscalls:sys_enter_open,
tracepoint:syscalls:sys_enter_openat
{
    @filename[tid] = args.filename;
}

tracepoint:syscalls:sys_exit_open,
tracepoint:syscalls:sys_exit_openat
/@filename[tid]/
{
    $ret = args.ret;
    @fd[tid] = $ret >= 0 ? $ret : -1;
}

tracepoint:syscalls:sys_exit_write,
tracepoint:syscalls:sys_exit_writev,
tracepoint:syscalls:sys_exit_pwritev
/@fd[tid]/
{
    $task = (struct task_struct *)curtask;

        $ret = args.ret;
    $nbyte = $ret >= 0 ? $ret : -1;
    $nothing = "-";
    //printf("%-6d %-16s %-16 %4d %-60s %-4s %-4d\n", pid, comm, ((struct mm_struct *)$task->mm)->exe_file->f_path, @fd[tid], str(@filename[tid]), $nothing, $ret);

    /* Ваши комментарии
    printf("%-6d %-16s %s %lu %s\n", pid, comm,
          str($task->mm->exe_file->f_path.dentry->d_name.name),
          $task->mm->exe_file->f_path.dentry->d_inode->i_ino,
          str($task->mm->exe_file->f_path.dentry->d_parent->d_name.name));
    */

    /* Нашли реализацию ф-ции d_path (https://android.googlesource.com/kernel/common/+/baa23246e93f/fs/d_path.c) и по аналогии попытались выводить у себя:

    $buf = (struct my_spa_t *)args0;
    $res = $task->mm->exe_file->f_path->dentry->d_op->d_dname($task->mm->exe_file->f_path->dentry, $buf, PATH_MAX);
    print(str($task->mm->exe_file->f_path->dentry->d_op->d_dname($task->mm->exe_file->f_path->dentry, $buf, PATH_MAX)));

    Ругается: ERROR: syntax error, unexpected (, expecting ) or ","
                print(str($task->mm->exe_file->f_path.dentry->d_op->d_dname($task->mm->exe_file->f_path.dentry, (struct my_spa_t *)args0, PATH_MAX));
    */

    // Решили получить путь рекурсивно через d_parent
    // Здесь упираемся в момент того, что не понимаем, какое условие задать для while
    // Вывод сейчас:
    //              bin 5810 usr 264 snapshot 256 1 257 .snapshots 256 @ 256 / 256 / 256 / 256 / 256 / 256
    //              pipe 58366 bpftrace 5866 Desktop 274 anna 257 home 256 @ 256 / 256 / 256 / 256 / 256 / 256
    $part_path = $task->mm->exe_file->f_path.dentry->d_parent;
    $i = 1;
    while ($i < 12) {
        printf("%s %lu ", str($part_path->d_name.name), $part_path->d_inode->i_ino);
        $part_path = $part_path->d_parent;
        $i = $i + 1;
    }
    print("\n");

        delete(@filename[tid]);
    delete(@fd[tid]);

}

//Аналогично tracepoint:syscalls:sys_exit_write
/*
tracepoint:syscalls:sys_exit_read,
tracepoint:syscalls:sys_exit_readv,
tracepoint:syscalls:sys_exit_preadv
/@fd[tid]/
{

    $task = (struct task_struct *)curtask;

        $ret = args.ret;
    $nbyte = $ret >= 0 ? $ret : -1;
    $nothing = "-";
    //printf("%-6d %-16s %-16s %4d %-60s %-4d %-4s\n", pid, comm, str($task->nameidata->name->name), @fd[tid], str(@filename[tid]), $ret, $nothing);

    //1.Вывод того, что выдает структура f_path
    //Вывод: { .mnt = 0xffff973af64f31a0, .dentry = 0xffff973ad1517240 }
    //print(((struct mm_struct *)$task->mm)->exe_file->f_path);

    //2.Попытка получить путь к файлу через функцию path
    //Ошибка: The path function can only be used with 'kfunc', 'kretfunc', 'iter' probes
    //print(path(((struct mm_struct *)$task->mm)->exe_file->f_path));

        delete(@filename[tid]);
    delete(@fd[tid]);
}
*/

END
{
    clear(@filename);
    clear(@fd);
}
anutatesl commented 3 months ago

Мы определили, что inode равный 256 является inod корневой директории (есть проблема с snapshots - у него получается следующая ситуация с inode и папками: snapshot 256 /1 257/.snapshots 256)

Есть проблема с тем, что из-за рекурсии путь выводится в обратном порядке (от файла к корневой директории), попробовали через массив, не получилось (пометки сделала к скрипте)

Сам скрипт

#ifndef BPFTRACE_HAVE_BTF
#include <linux/sched.h>
#include <linux/limits.h>
#endif

//struct MyStruct {
//  s64 p[1000];
//}

BEGIN
{
    printf("Tracing file system syscalls... Hit Ctrl-C to end.\n");
    printf("%-16s %-16s %4s %-60s %-4s %-4s\n", "COMM", "COMMTASK", "FD", "PATH", "R", "W");
}

tracepoint:syscalls:sys_enter_open,
tracepoint:syscalls:sys_enter_openat
{
    @filename[tid] = args.filename;
}

tracepoint:syscalls:sys_exit_open,
tracepoint:syscalls:sys_exit_openat
/@filename[tid]/
{
    $ret = args.ret;
    @fd[tid] = $ret >= 0 ? $ret : -1;
}

tracepoint:syscalls:sys_exit_write,
tracepoint:syscalls:sys_exit_writev,
tracepoint:syscalls:sys_exit_pwritev
/@fd[tid]/
{
    $task = (struct task_struct *)curtask;

        $ret = args.ret;
    $nbyte = $ret >= 0 ? $ret : -1;
    $nothing = "-";

    // Двумя разными способами пробовали создавать массив, чтобы выводить путь в правильном порядке, но безуспешно

    // 1. через струткуру (ошибка, не дает ей ничего присвоить)
    //$full_path_comm = (struct MyStruct *)arg0;
    //$full_path_comm->p[0] = comm;

    // 2. следующим образом: (ошибка, ругается на цикл по данному массиву (строки 66 - 68))
    //@full_path_comm[comm] = "";

    $part_path = $task->mm->exe_file->f_path.dentry->d_parent;
    $i = 1;
    printf("%s",comm);

    //Значение 1000 взято из головы
    while ($i != 1000) {

        //@full_path_comm[comm] = str($part_path->d_name.name);

        printf("/%s", str($part_path->d_name.name));
        $part_path = $part_path->d_parent;
        if ((uint64)$part_path->d_inode->i_ino == 256) {
            printf("/%s", str($part_path->d_name.name));
            break;
        }
        $i = $i + 1;
    }

    //for ($kv : @full_path_comm) {
    //  print($kv.1); // value
    //}

    printf(" %d %s %s %d\n", @fd[tid], str(@filename[tid]), $nothing, $ret);

        delete(@filename[tid]);
    delete(@fd[tid]);
    //delete(@full_path_comm)

}

tracepoint:syscalls:sys_exit_read,
tracepoint:syscalls:sys_exit_readv,
tracepoint:syscalls:sys_exit_preadv
/@fd[tid]/
{

    $task = (struct task_struct *)curtask;

        $ret = args.ret;
    $nbyte = $ret >= 0 ? $ret : -1;
    $nothing = "-";

    $part_path = $task->mm->exe_file->f_path.dentry->d_parent;
    $i = 1;
    printf("%s",comm);
    while ($i != 1000) {
        printf("/%s", str($part_path->d_name.name));
        $part_path = $part_path->d_parent;
        if ((uint64)$part_path->d_inode->i_ino == 256) {
            printf("/%s", str($part_path->d_name.name));
            break;
        }
        $i = $i + 1;
    }

    printf(" %d %s %d %s\n", @fd[tid], str(@filename[tid]), $ret, $nothing);

       delete(@filename[tid]);
    delete(@fd[tid]);
}

END
{
    clear(@filename);
    clear(@fd);
    //clear(@$full_path_comm);
}

Вывод имеет следующий вид:

anna@localhost:~/Desktop/bpftrace/FileSystem> sudo bpftrace fsorw.bt 
[sudo] password for root: 
Attaching 12 probes...
Tracing file system syscalls... Hit Ctrl-C to end.
COMM             COMMTASK           FD PATH                                                         R    W   
KIO::WorkerThre/bin/usr/snapshot -1 /dev/disk/by-label - 8
KIO::WorkerThre/bin/usr/snapshot 26 /proc/self/mountinfo 1024 -
X/bin/usr/snapshot 62 /usr/bin/VBoxClient 31 -
VBoxClient/bin/usr/snapshot 8 /run/user/1000/xauth_TyieTb 111 -
X/bin/usr/snapshot 62 /usr/bin/VBoxClient 31 -
fifo2.out/pipe/bpftrace/Desktop/anna/home 3 /lib64/libc.so.6 832 -
fifo1.out/pipe/bpftrace/Desktop/anna/home 3 /lib64/libc.so.6 832 -
fifo1.out/pipe/bpftrace/Desktop/anna/home 3 myfifo2 8 -
fifo2.out/pipe/bpftrace/Desktop/anna/home 3 myfifo2 9 -
denis-koptev commented 3 months ago

Корень файловой системы далеко не всегда будет иметь inode 256. Наоборот, в простых сценариях / имеет inode=2.

В любом случае, завязываться на конкретный номер нельзя. Где-то в структурах должна храниться рутовая inode или её номер. Для референса можно внимательно изучить, как устроена функция тут: https://sourceware.org/git/gitweb.cgi?p=systemtap.git;a=blob;f=tapset/linux/dentry.stp;h=4e73532f24b96ee67be3adb4f7c16d5608f8f608;hb=HEAD#l184

И ещё вопрос: на какой системе это тестируется? Т.е. это локальная ВМка, где-то в облаке, докер, WSL или что-то ещё? inode 256 для / выглядит крайне нетипично

denis-koptev commented 3 months ago

FYI: ссылка, которую я скинул, это про реализацию для systemtap. Systemtap - это ещё одна утилита для трейсинга Linux. Мне в это не верится, но, если мы с концами упремся в то, что через bpftrace получить путь к бинарнику нельзя, можно будет попробовать systemtap.

anutatesl commented 3 months ago

Из того, что мы нашли, есть следующие вариации хоть каких-то массивов в bpftrace:

  1. Ассоциативные массивы, индексируемыe по ключу: мы сделали мапу fullpath, где key - просто индекс (1, 2, и тд), а value - кусочек пути (имя файла или папки), на каждом этапе рекурсии. После чего в цикле for пытались выводить элементы мапы.

Скрипт

tracepoint:syscalls:sys_exit_write,
tracepoint:syscalls:sys_exit_writev,
tracepoint:syscalls:sys_exit_pwritev
/@fd[tid]/
{
    $task = (struct task_struct *)curtask;

        $ret = args.ret;
    $nbyte = $ret >= 0 ? $ret : -1;
    $nothing = "-";

    $part_path = $task->mm->exe_file->f_path.dentry->d_parent;
    $i = 0;

    //Значение 1000 взято из головы
    while ($i != 1000) {

        @full_path_comm[$i] = str($part_path->d_name.name);

               /* Этот пример взят из документации и даже тут он ругается на for
        @map[10] = 20;
        for ($kv : @map) {
            print($kv.0); // key
            print($kv.1); // value
        }*/

        //printf("/%s %lu", str($part_path->d_name.name), $part_path->d_inode->i_ino);
        $part_path = $part_path->d_parent;
        if ((uint64)$part_path->d_inode->i_ino == 256) {
            //printf("/%s", str($part_path->d_name.name));
            break;
        }
        $i = $i + 1;
    }

        for ($kv : @full_path_comm) {
        print($kv.1); // value
    }
    print("\n");

    //printf(" %d %s %s %d\n", @fd[tid], str(@filename[tid]), $nothing, $ret);

        delete(@filename[tid]);
    delete(@fd[tid]);
    delete(@full_path_comm)

}

Ошибка

anna@localhost:~/Desktop/bpftrace/FileSystem> sudo bpftrace fsorw.bt 
fsorw.bt:59:3-6: ERROR: syntax error, unexpected for, expecting }
        for ($kv : @full_path_comm) {
        ~~~
  1. Кортежи Есть следующий пример

    i:s:1 {
    $a = (1,2);
    $b = (3,4, $a);
    печать($a);
    печать($b);
    печать($b.0);
    }
    
    Печать:
    
    (1, 2)
    (3, 4, (1, 2))
    3

    Мы решили, что его использовать не можем, так как при выводе всего кортежа получаем что-то подобное (3, 4, (1, 2)), а не нормальный путь и это придется в go-коде обрабатывать. С таким же успехом можно получать просто перевернутый путь (что мы сейчас и делаем) и в go-коде обрабатывать его.

  2. Массивы bpftrace поддерживает доступ к одномерным массивам, подобным тем, которые можно найти в C. Построение массивов с нуля, таких как int a[] = {1,2,3} в C, не поддерживается. Они могут быть считаны в переменную только из указателя.

Пример

 struct MyStruct {
 int y[4];
 }

 kprobe:dummy {
 $s = (struct MyStruct *) arg0;
 print($s->y[0]);
}

Мы не сообразили, как это адаптировать в нашем случае и записывать в массив свои кусочки имени полного пути.

anutatesl commented 3 months ago

Получилось выводить абсолютный путь к исполняемому файлу в нужной последовательности

Осталась проблема с тем, что если это файл из snapshots (у него получается следующая ситуация с inode и папками: /.snapshots(256)/1(257) /snapshot(256)/usr/bin/.....), то путь начинается со snapshot (пример вывода 1), с остальным такого не наблюдалось. Пробовала прям в скрипте убирать snapshot - ругается (пример вывода 2), можно будет убирать это в go в конце концов.

Пример вывода 1:

Attaching 9 probes...
Tracing file system syscalls... Hit Ctrl-C to end.
COMM       FD             PATH              R    W   
snapshot/usr/bin/plasmashell -1 / - 408
snapshot/usr/bin/X 63 /usr/bin/VBoxClient - 8
snapshot/usr/bin/konsole 18 pipe : fifo1.out - 52
snapshot/usr/bin/VBoxClient 8 /run/user/1000/xauth_BkPaXD - 48
snapshot/usr/bin/X 63 /usr/bin/VBoxClient - 8
home/anna/Desktop/bpftrace/pipe/fifo1.out 3 myfifo2 - 7
home/anna/Desktop/bpftrace/pipe/fifo2.out 3 myfifo2 - 13

Пример вывода 2:

Attaching 9 probes...
WARNING: Error loading program: tracepoint:syscalls:sys_exit_write (try -v), skipping.
ioctl(PERF_EVENT_IOC_SET_BPF): Bad file descriptor

Часть, где были внесены изменения:

tracepoint:syscalls:sys_exit_write,
tracepoint:syscalls:sys_exit_writev,
tracepoint:syscalls:sys_exit_pwritev
/@fd[tid]/
{
        $ret = args.ret;
    $nbyte = $ret >= 0 ? $ret : -1;
    $nothing = "-";

    $task = (struct task_struct *)curtask;
    $part_path = $task->mm->exe_file->f_path.dentry->d_parent;
    $i = 0;
    @full_path_comm[$i] = $part_path->d_name.name;
    $i = 1;

    while ($i != 1000) {
        $part_path = $part_path->d_parent;
        @full_path_comm[$i] = $part_path->d_name.name;
        if ((uint64)$part_path->d_inode->i_ino == 256) {
            break;
        }
        $i = $i + 1;
    }
    printf("/");
    while ($i != -1) {
        $str_ = @full_path_comm[$i];
        printf("%s/", str($str_));
        $i = $i - 1;
    }
    printf("%s",comm);
    printf(" %d %s %s %d\n", @fd[tid], str(@filename[tid]), $nothing, $ret);

    delete(@filename[tid]);
    delete(@fd[tid]);
}