LINUX.ORG.RU

История изменений

Исправление GPFault, (текущая версия) :

Тут пока никто не упоминал про практический опыт с eEBF до этого компилятора, и не сразу понимают что он может)

Дам тот пример с коорым сталкивался. На одной железке оказался глючноватый EFI, и определённое обращение про оценку размера (то которые делает команда df и всякие мониторинги) к efivarfs подвешивало всю систему на 500мс. При этом примонтированный efivarfs был нужен для других задач.

В результате для того чтоб не тюнить все мониторинги было сделана заглушка на eBPF, которая будет игнорировать подвешивающий систему запрос.

То есть ниже минимальный рабочий жизненный цикл программы eBPF, реализованный на инструментах ~2022года, без всякого KernelScript.

Исходник на языке, выглядящем как С, реализующий фильтрацию системных вызовов на уровне LSM (много предопределённых точек фильтрации, но отнюдь не произвольная ифункция ядра):

// Any inclusions avoided and all structs declared manually in compiler terms to avoid build system mantainence
#define ERROR_EACCES 13
#define EFIVARFS_MAGIC 0xde5e81e4

__attribute__((section("license")))
const char gpl_license_declaration_required_for_BPF_programs[] = "GPL";

// BPF in "co-re" compile mode binds offsets by struct name+field name at load time, so declare only needed fields
struct super_block {
  long unsigned int s_magic;
} __attribute__((preserve_access_index));

struct dentry {
  struct super_block *d_sb;
} __attribute__((preserve_access_index));


// structure representing BPF context pointing args passed by LSM to sb_statfs callback
struct StatfsArgs {
  struct dentry* dir_entry;
  long ret;
};

__attribute__((section("lsm/sb_statfs")))
int extra_efivar_filter_statfs(struct StatfsArgs* ctx)
{
  if (ctx->dir_entry->d_sb->s_magic == EFIVARFS_MAGIC) {
    // statfs for efivarfs on some firmwares leads to slow irq processing, mark it unsupported
    // EACCES error code is used to make it more expected to programs, for example `df` tool just silence it
    return -ERROR_EACCES;
  }
  // return result from previous LSM, typically 0 - operation allowed
  return (int)ctx->ret;
}

По нему можно заметить странную для С вещь - в структурах объявлены только те поля, которые используются, а остальные пропущены. Это сделано потому что набор полей завсит от версии ядра, и опции компиляции ниже дают .o файл, который привязывается не к смещениям полей, а к их именам. То есть несмотря на выглядящий как С исходник - результат работы компилятора по своей сути больше похож не на бинарный файл для линкера, а промежутоное представление кода. Собственно вот команда компиляции на сборочном сервере:

bpf-gcc -mco-re -gbtf -c efivar-filter-statfs.bpf.c

Которая порождает efivar-filter-statfs.bpf.o файл, совместимый с любыми версиями ядра где в используемых в исхожнике структурах есть поля с соответсвующими именами.

И уже на целевой машине - просто загружаем его в ядро

/usr/lib/@LINUX_TOOLS_VERSION@/bpftool prog loadall efivar-filter-statfs.bpf.o /sys/fs/bpf autoattach 

Никакого специфичного для задачи userspace-кода на целевой машине не исполняется, только универсальный инструмент загрузки bpftool. В eBPF есть всякие продвинутые механизмы где userspace-код обязателен/необходим, но пример выше как раз для того чтоб показать что иожно и без него, просто добавить чуть-чуть кода в ядро, не привязывась к ABI конкретной версии.

Исправление GPFault, :

Тут пока никто не упоминал про практический опыт с eEBF до этого компилятора, и не сразу понимают что он может)

Дам тот пример с коорым сталкивался. На одной железке оказался глючноватый EFI, и определённое обращение про оценку размера (то которые делает команда df и всякие мониторинги) к efivarfs подвешивало всю систему на 500мс. При этом примонтированный efivarfs был нужен для других задач.

В результате для того чтоб не тюнить все мониторинги было сделана заглушка на eBPF, которая будет игнорировать подвешивающий систему запрос.

То есть ниже минимальный рабочий жизненный цикл программы eBPF, реализованный на инструментах ~2022года.

Исходник на языке, выглядящем как С, реализующий фильтрацию системных вызовов на уровне LSM (много предопределённых точек фильтрации, но отнюдь не произвольная ифункция ядра):

// Any inclusions avoided and all structs declared manually in compiler terms to avoid build system mantainence
#define ERROR_EACCES 13
#define EFIVARFS_MAGIC 0xde5e81e4

__attribute__((section("license")))
const char gpl_license_declaration_required_for_BPF_programs[] = "GPL";

// BPF in "co-re" compile mode binds offsets by struct name+field name at load time, so declare only needed fields
struct super_block {
  long unsigned int s_magic;
} __attribute__((preserve_access_index));

struct dentry {
  struct super_block *d_sb;
} __attribute__((preserve_access_index));


// structure representing BPF context pointing args passed by LSM to sb_statfs callback
struct StatfsArgs {
  struct dentry* dir_entry;
  long ret;
};

__attribute__((section("lsm/sb_statfs")))
int extra_efivar_filter_statfs(struct StatfsArgs* ctx)
{
  if (ctx->dir_entry->d_sb->s_magic == EFIVARFS_MAGIC) {
    // statfs for efivarfs on some firmwares leads to slow irq processing, mark it unsupported
    // EACCES error code is used to make it more expected to programs, for example `df` tool just silence it
    return -ERROR_EACCES;
  }
  // return result from previous LSM, typically 0 - operation allowed
  return (int)ctx->ret;
}

По нему можно заметить странную для С вещь - в структурах объявлены только те поля, которые используются, а остальные пропущены. Это сделано потому что набор полей завсит от версии ядра, и опции компиляции ниже дают .o файл, который привязывается не к смещениям полей, а к их именам. То есть несмотря на выглядящий как С исходник - результат работы компилятора по своей сути больше похож не на бинарный файл для линкера, а промежутоное представление кода. Собственно вот команда компиляции на сборочном сервере:

bpf-gcc -mco-re -gbtf -c efivar-filter-statfs.bpf.c

Которая порождает efivar-filter-statfs.bpf.o файл, совместимый с любыми версиями ядра где в используемых в исхожнике структурах есть поля с соответсвующими именами.

И уже на целевой машине - просто загружаем его в ядро

/usr/lib/@LINUX_TOOLS_VERSION@/bpftool prog loadall efivar-filter-statfs.bpf.o /sys/fs/bpf autoattach 

Никакого специфичного для задачи userspace-кода на целевой машине не исполняется, только универсальный инструмент загрузки bpftool. В eBPF есть всякие продвинутые механизмы где userspace-код обязателен/необходим, но пример выше как раз для того чтоб показать что иожно и без него, просто добавить чуть-чуть кода в ядро, не привязывась к ABI конкретной версии.

Исходная версия GPFault, :

Тут пока никто не упоминал про практический опыт с eEBF до этого компилятора, и не сразу понимают что он может)

Дам тот пример с коорым сталкивался. На одной железке оказался глючноватый EFI, и определённое обращение про оценку размера (то которые делает команда df и всякие мониторинги) к efivarfs подвешивало всю систему на 500мс. При этом примонтированный efivarfs был нужен для других задач.

В результате для того чтоб не тюнить все мониторинги было сделана заглушка на eBPF, которая будет игнорировать подвешивающий систему запрос.

То есть ниже минимальный рабочий жизненный цикл программы eBPF, реализованный на инструментах ~2022года.

Исходник на языке, выглядящем как С, реализующий фильтрацию системных вызовов на уровне LSM (много предопределённых точек фильтрации, но отнюдь не произвольная ифункция ядра):

// Any inclusions avoided and all declared anually in compiler terms to avoid build system mantainence
#define ERROR_EACCES 13
#define EFIVARFS_MAGIC 0xde5e81e4

__attribute__((section("license")))
const char gpl_license_declaration_required_for_BPF_programs[] = "GPL";

// BPF in "co-re" compile mode binds offsets by struct name+field name at load time, so declare only needed fields
struct super_block {
  long unsigned int s_magic;
} __attribute__((preserve_access_index));

struct dentry {
  struct super_block *d_sb;
} __attribute__((preserve_access_index));


// structure representing BPF context pointing args passed by LSM to sb_statfs callback
struct StatfsArgs {
  struct dentry* dir_entry;
  long ret;
};

__attribute__((section("lsm/sb_statfs")))
int extra_efivar_filter_statfs(struct StatfsArgs* ctx)
{
  if (ctx->dir_entry->d_sb->s_magic == EFIVARFS_MAGIC) {
    // statfs for efivarfs on some firmwares leads to slow irq processing, mark it unsupported
    // EACCES error code is used to make it more expected to programs, for example `df` tool just silence it
    return -ERROR_EACCES;
  }
  // return result from previous LSM, typically 0 - operation allowed
  return (int)ctx->ret;
}

По нему можно заметить странную для С вещь - в структурах объявлены только те поля, которые используются, а остальные пропущены. Это сделано потому что набор полей завсит от версии ядра, и опции компиляции ниже дают .o файл, который привязывается не к смещениям полей, а к их именам. То есть несмотря на выглядящий как С исходник - результат работы компилятора по своей сути больше похож не на бинарный файл для линкера, а промежутоное представление кода. Собственно вот команда компиляции на сборочном сервере:

bpf-gcc -mco-re -gbtf -c efivar-filter-statfs.bpf.c

Которая порождает efivar-filter-statfs.bpf.o файл, совместимый с любыми версиями ядра где в используемых в исхожнике структурах есть поля с соответсвующими именами.

И уже на целевой машине - просто загружаем его в ядро

/usr/lib/@LINUX_TOOLS_VERSION@/bpftool prog loadall efivar-filter-statfs.bpf.o /sys/fs/bpf autoattach 

Никакого специфичного для задачи userspace-кода на целевой машине не исполняется, только универсальный инструмент загрузки bpftool. В eBPF есть всякие продвинутые механизмы где userspace-код обязателен/необходим, но пример выше как раз для того чтоб показать что иожно и без него, просто добавить чуть-чуть кода в ядро, не привязывась к ABI конкретной версии.