Проверка подписи общего объекта Linux перед загрузкой

Цель: Нагрузка .so или исполняемый файл, который был проверен на подпись (или проверен против произвольного алгоритма).

Я хочу иметь возможность проверить a .so / executable, а затем загрузите / выполните это .Итак / исполняемый файл с dlopen/...

ключ в этом заключается в том, что, похоже, нет программного способа проверить-затем-загрузить. Можно проверить файл вручную, а затем загрузить его после.. однако есть окно возможности, в котором кто-то может поменять этот файл на другой.

одно из возможных решений, которое я могу придумать, - загрузить двоичный файл, проверить подпись, а затем dlopen / execvt /proc/$PID/fd.... однако я не знаю, является ли это жизнеспособным решением.

поскольку блокировки файловой системы рекомендательны в Linux, они не так полезны для этой цели... (ну, есть mount -o mand ... но это что-то для userlevel, а не для корневого использования).

4 ответов


проблема по существу неразрешима в том виде, который вы дали, потому что общие объекты загружаются mmap()ing для обработки пространства памяти. Так что даже если вы может убедитесь, что файл, на котором работает dlopen (), был тем, который вы рассмотрели и объявили OK, любой, кто может записать в файл, может изменить загруженный объект в любой время после того, как вы загрузили его. (Вот почему вы не обновляете запущенные двоичные файлы, записывая в них-вместо этого вы удаляете-затем-устанавливаете, потому что запись в них, вероятно, приведет к сбою любых запущенных экземпляров).

лучше всего убедиться, что только пользователь, которого вы используете, может записать в файл, затем изучить его, а затем dlopen (). Ваш пользователь (или root) все еще может проникать в другой код, но процессы с этими разрешениями могут просто ptrace () вы все равно делаете свои ставки.


многие динамические компоновщики (включая Glibc) поддерживают настройку LD_AUDIT переменная среды в список разделенных двоеточием общих библиотек. Эти библиотеки могут подключаться к различным местоположениям в процессе динамической загрузки библиотеки.

#define _GNU_SOURCE
#include <dlfcn.h>
#include <link.h>
unsigned int la_version(unsigned int v) { return v; }
unsigned int la_objopen(struct link_map *l, Lmid_t lmid, uintptr_t *cookie) {
    if (!some_custom_check_on_name_and_contents(l->l_name, l->l_addr))
        abort();
    return 0;
}

скомпилируйте это cc -shared -fPIC -o test.so test.c или аналогичные.

вы можете ознакомиться glibc/elf/tst-auditmod1.c или latrace дополнительные примеры или читать линкеры и библиотеки руководство.


очень очень специфично для внутренних органов Glibc, но вы все равно можете подключиться к libdl во время выполнения.

#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdio.h>

extern struct dlfcn_hook {
    void *(*dlopen)(const char *, int, void *);
    int (*dlclose)(void *);
    void *(*dlsym)(void *, const char *, void *);
    void *(*dlvsym)(void *, const char *, const char *, void *);
    char *(*dlerror)(void);
    int (*dladdr)(const void *, Dl_info *);
    int (*dladdr1)(const void *, Dl_info *, void **, int);
    int (*dlinfo)(void *, int, void *, void *);
    void *(*dlmopen)(Lmid_t, const char *, int, void *);
    void *pad[4];
} *_dlfcn_hook;
static struct dlfcn_hook *old_dlfcn_hook, my_dlfcn_hook;

static int depth;
static void enter(void) { if (!depth++) _dlfcn_hook = old_dlfcn_hook; }
static void leave(void) { if (!--depth) _dlfcn_hook = &my_dlfcn_hook; }

void *my_dlopen(const char *file, int mode, void *dl_caller) {
    void *result;
    fprintf(stderr, "%s(%s, %d, %p)\n", __func__, file, mode, dl_caller);
    enter();
    result = dlopen(file, mode);
    leave();
    return result;
}

int my_dlclose(void *handle) {
    int result;
    fprintf(stderr, "%s(%p)\n", __func__, handle);
    enter();
    result = dlclose(handle);
    leave();
    return result;
}

void *my_dlsym(void *handle, const char *name, void *dl_caller) {
    void *result;
    fprintf(stderr, "%s(%p, %s, %p)\n", __func__, handle, name, dl_caller);
    enter();
    result = dlsym(handle, name);
    leave();
    return result;
}

void *my_dlvsym(void *handle, const char *name, const char *version, void *dl_caller) {
    void *result;
    fprintf(stderr, "%s(%p, %s, %s, %p)\n", __func__, handle, name, version, dl_caller);
    enter();
    result = dlvsym(handle, name, version);
    leave();
    return result;
}

char *my_dlerror(void) {
    char *result;
    fprintf(stderr, "%s()\n", __func__);
    enter();
    result = dlerror();
    leave();
    return result;
}

int my_dladdr(const void *address, Dl_info *info) {
    int result;
    fprintf(stderr, "%s(%p, %p)\n", __func__, address, info);
    enter();
    result = dladdr(address, info);
    leave();
    return result;
}

int my_dladdr1(const void *address, Dl_info *info, void **extra_info, int flags) {
    int result;
    fprintf(stderr, "%s(%p, %p, %p, %d)\n", __func__, address, info, extra_info, flags);
    enter();
    result = dladdr1(address, info, extra_info, flags);
    leave();
    return result;
}

int my_dlinfo(void *handle, int request, void *arg, void *dl_caller) {
    int result;
    fprintf(stderr, "%s(%p, %d, %p, %p)\n", __func__, handle, request, arg, dl_caller);
    enter();
    result = dlinfo(handle, request, arg);
    leave();
    return result;
}

void *my_dlmopen(Lmid_t nsid, const char *file, int mode, void *dl_caller) {
    void *result;
    fprintf(stderr, "%s(%lu, %s, %d, %p)\n", __func__, nsid, file, mode, dl_caller);
    enter();
    result = dlmopen(nsid, file, mode);
    leave();
    return result;
}

static struct dlfcn_hook my_dlfcn_hook = {
    .dlopen   = my_dlopen,
    .dlclose  = my_dlclose,
    .dlsym    = my_dlsym,
    .dlvsym   = my_dlvsym,
    .dlerror  = my_dlerror,
    .dladdr   = my_dladdr,
    .dlinfo   = my_dlinfo,
    .dlmopen  = my_dlmopen,
    .pad      = {0, 0, 0, 0},
};

__attribute__((constructor))
static void init(void) {
    old_dlfcn_hook = _dlfcn_hook;
    _dlfcn_hook = &my_dlfcn_hook;
}

__attribute__((destructor))
static void fini(void) {
    _dlfcn_hook = old_dlfcn_hook;
}
$ cc -shared -fPIC -o hook.so hook.c
$ cat > a.c
#include <dlfcn.h>
int main() { dlopen("./hook.so", RTLD_LAZY); dlopen("libm.so", RTLD_LAZY); }
^D
$ cc -ldl a.c
$ ./a.out
my_dlopen(libm.so, 1, 0x80484bd)

к сожалению, мои исследования приводят меня к выводу, что даже если вы могли бы подключиться к glibc/elf/dl-load.c:open_verify() (чего вы не можете), невозможно сделать эту гонку свободной от кого-то, кто пишет над сегментами вашей библиотеки.


этот проект предположительно решает это на уровне ядра.

DigSig в настоящее время предлагает:

  • проверка подписи времени выполнения двоичных файлов ELF и общих библиотек.
  • поддержка отзыва подписи файла.
  • механизм кэширования подписи для повышения производительности.

Я предлагаю следующее решение, которое должно работать без библиотек *)

int memfd = memfd_create("for-debugging.library.so", MFD_CLOEXEC | MFD_ALLOW_SEALING);
assert(memfd != -1);

// Use any way to read the library from disk and copy the content into memfd
// e.g. write(2) or ftruncate(2) and mmap(2)
// Important! if you use mmap, you have to unmap it before the next step
// fcntl( , , F_SEAL_WRITE) will fail if there exists a writeable mapping

int seals_to_set = F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE | F_SEAL_SEAL;
int sealing_err = fcntl(memfd, F_ADD_SEALS, seals_to_set);
assert(sealing_err == 0);

// Only now verify the contents of the loaded file
// then you can safely *) dlopen("/proc/self/fd/<memfd>");

*) фактически не тестировал его против атак. Не используйте в производстве без дальнейшего исследования.