Получить IP-адрес клиента NFS в модуле ядра - PullRequest
0 голосов
/ 27 февраля 2020

Я работаю над модулем ядра для отслеживания операций, выполняемых клиентами NFS на моем сервере. Я могу перехватить файловые операции, используя хакерский способ (перехват уровня vfs), но не могу получить IP-адрес клиента.

Существует ли какая-либо информация, которая может быть сохранена в задаче current, которая Я могу использовать, чтобы получить IP-адрес клиента NFS, выполняющего операцию?

Я знаю, что, покопавшись в исходном коде, nfsd сохраняет struct nfsd_net в struct super_block поле s_fs_info, но Я могу получить его только как указатель struct net. И в реализации nfsd net_generic метод используется для получения указателя struct nfsd_net (используя nfsd_net_id, который является pernet_operations s id).

Могу ли я получить этот идентификатор как-нибудь? и если да, могу ли я использовать struct nfsd_net в моем модуле ядра? Он определен где-то, кроме fs/nfsd/netns.h?

Редактировать

Я использую этот подход для захвата функции открытия. Я пишу это для версии ядра 4.15.0. Вот код модуля ядра:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/kobject.h>
#include <linux/string.h>
#include <linux/slab.h>
#include <linux/version.h>
#include <linux/proc_fs.h>
#include <linux/cred.h>
#include <linux/sched.h>
#include <linux/preempt.h>
#include <linux/uaccess.h>
#include <linux/xattr.h>

MODULE_LICENSE("GPL");

#if defined(__i386__)
    #define POFF 1
    #define CSIZE 6
    // push address, addr, ret
    char *jmp_code="\x68\x00\x00\x00\x00\xc3";
    typedef unsigned int PSIZE;
#else
    #define POFF 2
    #define CSIZE 12
    // mov address to register rax, jmp rax. for normal x64 convention
    char *jmp_code="\x48\xb8\x00\x00\x00\x00\x00\x00\x00\x00\xff\xe0";
    typedef unsigned long PSIZE;
#endif

DEFINE_SPINLOCK(root_open_lock);

int (*orig_root_open) (struct inode *, struct file *);
void *orig_root_open_code;

void hook(void *src_func,void *dst_addr){
  barrier();
  write_cr0(read_cr0() & (~0x10000));
  memcpy(src_func,jmp_code,CSIZE);
  *(PSIZE *)&(((unsigned char*)src_func)[POFF])=(PSIZE)dst_addr;
  write_cr0(read_cr0() | 0x10000);
  barrier();
}

void save_and_hook(void **p_reserve,void *src_func,void *dst_addr){
  barrier();
  write_cr0(read_cr0() & (~0x10000));
  *p_reserve=kmalloc(CSIZE,GFP_KERNEL);
  // save origin code
  memcpy(*p_reserve,src_func,CSIZE);
  hook(src_func,dst_addr);
  write_cr0(read_cr0() | 0x10000);
  barrier();
}

void fix(void **p_reserve,void *src_func){
  barrier();
  write_cr0(read_cr0() & (~0x10000));
  memcpy(src_func,*p_reserve,CSIZE);
  write_cr0(read_cr0() | 0x10000);
  barrier();
}

int fake_root_open(struct inode *x, struct file *fp)
{
  int ret;

  printk("vfshijack: hijacked open\n"); // I need to find the client ip here.

  barrier();
  spin_lock(&root_open_lock);
  fix(&orig_root_open_code, orig_root_open);
  ret = orig_root_open(x, fp);
  hook(orig_root_open, fake_root_open);
  spin_unlock(&root_open_lock);
  barrier();
  return ret;
}

int vfs_init(void)
{
  struct file *fp = filp_open("/", O_DIRECTORY|O_RDONLY, 0);
  if (IS_ERR(fp))
    return -1;

  orig_root_open = fp->f_op->open;
  if(orig_root_open)
  {
    save_and_hook(&orig_root_open_code, orig_root_open, fake_root_open);
  }

  filp_close(fp, NULL);

  printk("vfshijack: vfshijack loaded\n");
  return 0;
}

void vfs_exit(void)
{
  if(orig_root_open)
  {
    fix(&orig_root_open_code, orig_root_open);
  }
  printk("vfshijack: vfshijack unloaded\n");
}

module_init(vfs_init);
module_exit(vfs_exit);

1 Ответ

0 голосов
/ 08 марта 2020

Вы можете попытаться получить необходимую информацию из linux инструментов трассировки ядра, не подключая двоичный файл ядра с какой-либо пользовательской сборкой. Существуют perf, ftrace, trace-cmd для большинства версий ядра и stap и lttng для более пользовательских версий. Некоторая документация для начала: https://www.kernel.org/doc/html/v4.18/trace/index.html "Linux Tracing Technologies"

В nfsd определены несколько точек трассировки:

# modprobe nfsd
# modprobe nfs
# perf list tracepoint|grep nfs
# find /sys/kernel/debug/tracing/events -type d|grep nfsd
# trace-cmd list -e nfsd:read_start -F

nfsd / read_start и nfsd / Точки трассировки write_start - хорошие места для начала. Оба должны иметь доступ к структуре запроса rqstp с адресом rq_addr и указателем fh (но некоторые сценарии eBPF или stap могут быть полезны) https://elixir.bootlin.com/linux/v4.15/source/fs/nfsd/vfs.c#L1020

__be32 nfsd_read(struct svc_rqst *rqstp, struct svc_fh *fhp,...)

    trace_read_start(rqstp, fhp, offset, vlen);

    trace_read_opened(rqstp, fhp, offset, vlen);

    trace_read_io_done(rqstp, fhp, offset, vlen);

    trace_read_done(rqstp, fhp, offset, vlen);

У меня нет полного примера trace-cmd или использования stap для трассировки демона nfs.

Systemtap (stap) содержит несколько примеров статистики nfsd: https://github.com/jav/systemtap/blob/master/testsuite/systemtap.examples/index.txt

# stap nfsd_unlink.stp -c "sleep 0.2"
The nfsdtop.stp script gathers and displays NFS lookups

https://github.com/larytet/SystemTap/blob/master/testsuite/systemtap.examples/network/nfsdtop.stp

...