LINUX.ORG.RU

глобальная переменная и fork()


0

1

привет.

реализовал хук для system()/fork()/execve() в виде .so библиотеки. внутри хука для system(), я проверяю всякие условия/командную_строку, и если все гут - зову реальный system(). но, т.к. в .so реализованы хуки еще и для fork() и execve(), то когда реальный system() начинает выполняться, он зовет мой хуковый fork() и из него execve(). тут проблема в том, что для приведенного тут примера с system(), условия проверяются несколько раз подряд. от этого и хочу избавиться.

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

и тут я наткнулся на такую странную проблему... суть проблемы в том, что когда из хуковой system() зовется fork(), при входе в хуковый fork(), та самая глобальная переменная уже ровняется нулю, хотя из вызова выше(system()) возвращения не произошло. уточняю: глобальная переменная при входе в хуковый fork() ровняется нулю, и это еще до того, как вызываем реальный fork(). далее, из реального fork() (глобальную переменную мы снова устанавливаем в единицу(ибо при входе она почему-то ровняется нулю), чтоб исключить последующие проверки условий) происходит вызов хуковой execve(). в этом хуке все повторяется, глобальная переменная снова ровняется нулю!

в псевдокоде это выглядит так:

volatile int in_hook = 0;

...

int system(const char *cmd) {
   if ( ! in_hook  ) {
      in_hook = 1; // "говорим" что мы в хуке
      if ( ! { проверяем условия } ) {
         errno = EPERM;
         in_hook = 0;
         return -1;
      }
      int rc = { вызываем реальный system() };
      in_hook = 0;
      return rc;
   } else {
      // сюда мы приходим только в том случае, если эта функция вызвана из хука.
      return { вызываем реальный system() };
   }
}

pid_t fork() {
   // тут 'in_hook' уже равен нулю!
   if ( ! in_hook  ) { 
      in_hook = 1; // "говорим" что мы в хуке
      if ( ! { проверяем условия } ) {
         errno = EPERM;
         in_hook = 0;
         return -1;
      }
      int rc = { вызываем реальный fork() };
      in_hook = 0;
      return rc;
   } else {
      // сюда мы приходим только в том случае, если эта функция вызвана из хука.
      return { вызываем реальный fork() };
   }
}
подскажите, где ступил?

благодарен.

★★★

Ответ на: комментарий от tailgunner

касательно трейса..

выполнил. получил другую странность:

clone(child_stack=0, flags=CLONE_PARENT_SETTID|SIGCHLD, parent_tidptr=0x7fff4a3ecd80) = 26735
wait4(26735)

т.е. system() не зовет fork(), а напрямую зовет clone(), как и предположил gv.

но странность заключается в том, что в выводе все равно присутствует вывод из хукового fork():

ctor called!
>>> in function "fork()", before set in_hook=0, pid=26735
before call fork() in_hook=1, pid=26735
after call fork() in_hook=1, pid=26735
after call fork() in_hook=1, pid=26736
>>> in function "execve()", before set in_hook=0, pid=26736
before call execve() in_hook=1, pid=26736
ctor called!
но кто его вызывает? clone()?

niXman ★★★
() автор топика
Ответ на: комментарий от tailgunner

ИМХО после форка нулевой in_hook - в новой копии библиотеки.

Надо проверять, но лень :)

gv
()
Ответ на: комментарий от niXman

system() не зовет fork(), а напрямую зовет clone()

Думаю, что системный вызов fork давно не используется :)

но странность заключается в том, что в выводе все равно присутствует вывод из хукового fork():

А вот библиотечная функция fork должна быть.

tailgunner ★★★★★
()
Ответ на: комментарий от niXman

я думаю это немного прояснит ситуацию:

#ifndef _GNU_SOURCE
#       define _GNU_SOURCE (1)
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <dlfcn.h>

void *_system = 0;
void *_fork = 0;
void *_execve = 0;

char *name[] = {
        [0] = "init (0)",
        [1] = "system set (1)",
        [2] = "system release (0)",
        [3] = "fork set (1)",
        [4] = "fork child release (0)",
        [5] = "fork parent release (0)",
        [6] = "execve set (1)",
        /* there is no execve release */
};

volatile int in_hook = 0;

void __attribute__ ((constructor)) my_init(void) {
        fprintf(stderr, ">>> %s\n", __func__);
        in_hook = 0;
        _system = dlsym(RTLD_NEXT, "system");
        _fork          = dlsym(RTLD_NEXT, "fork");
        _execve = dlsym(RTLD_NEXT, "execve");
        if ( !_system || !_fork || !_execve ) {
                fprintf(stderr, "dlsym() failed. terminate.\n");
                exit(1);
        }
        fprintf(stderr, "<<< %s\n", __func__);
}

int system(const char *cmd) {
        fprintf(stderr, ">>> %s\n", __func__);
        fprintf(stderr, "\tbefore set in_hook = %s\n", name[in_hook]);
        in_hook = 1;
        fprintf(stderr, "\tbefore call system() in_hook = %s\n", name[in_hook]);
        int rc = ((typeof(&system))_system)(cmd);
        fprintf(stderr, "\tafter call system() in_hook = %s\n", name[in_hook]);
        in_hook = 2;
        fprintf(stderr, "<<< %s\n", __func__);
        return rc;
}

pid_t fork() {
        fprintf(stderr, ">>> %s\n", __func__);
        fprintf(stderr, "\tbefore set in_hook = %s\n", name[in_hook]);
        in_hook = 3;
        fprintf(stderr, "\tbefore call fork() in_hook = %s\n", name[in_hook]);
        pid_t rc = ((typeof(&fork))_fork)();
        fprintf(stderr, "\tafter call fork() in_hook = %s\n", name[in_hook]);
        switch (rc) {
        case 0: /* child */
                in_hook = 4;
                break;
        default:
                in_hook = 5;
                break;
        }
        fprintf(stderr, "<<< %s\n", __func__);
        return rc;
}

int execve(const char *filename, char *const argv[], char *const envp[]) {
        fprintf(stderr, ">>> %s\n", __func__);
        fprintf(stderr, "\tbefore set in_hook = %s\n", name[in_hook]);
        in_hook = 6;
        fprintf(stderr, "\tbefore call execve() in_hook = %s\n", name[in_hook]);
        int rc = ((typeof(&execve))_execve)(filename, argv, envp);
        /* NOTREACHED */
        return rc;
}

output:

$ env LD_LIBRARY_PATH=. ./main 
>>> my_init
<<< my_init
>>> system
        before set in_hook = init (0)
        before call system() in_hook = system set (1)
>>> fork
        before set in_hook = system set (1)
        before call fork() in_hook = fork set (1)
        after call fork() in_hook = fork set (1)
<<< fork
        after call fork() in_hook = fork set (1)
<<< fork
>>> execve
        before set in_hook = fork child release (0)
        before call execve() in_hook = execve set (1)
total 192
# ---8<--- output stripped
        after call system() in_hook = fork parent release (0)
<<< system
system(): Undefined error: 0

beastie ★★★★★
()
Ответ на: комментарий от tailgunner

библиотечная функция fork должна быть.

зачем? и где ее искать?

ладно, в список рассылки glibc я напишу, но хотелось бы услышать мнение участвующих в теме о том, какими другими способами я могу разрешать/запрещать использование system()/fork()/execve()/clone() ?

сейчас, реализована большая часть хуков(а не только для сабжевых. но проблема возникла только с сабжевыми) и вся инфраструктура политик/мониторинга/учета. не хотелось бы все это выкидывать на помойку...

я немогу «железно» запретить использование этих функций. мне нужно разрешать/запрещать их для разных пользователей.

какие будут идеи?

зы: от systrace отказался, с ним много проблем, и он дико затормаживает программы.

niXman ★★★
() автор топика
Ответ на: комментарий от niXman

библиотечная функция fork должна быть.

зачем?

POSIX

и где ее искать?

В libc

какими другими способами я могу разрешать/запрещать использование system()/fork()/execve()/clone() ?

Тебе нужно точно выяснить, как реализованы перехватываемые функции.

от systrace отказался, с ним много проблем

Почему не использовать SELinux/SMACK/AppArmor? Ты понимаешь, что техника «напишем врапперы» - это профанация с точки зрения безопасности?

tailgunner ★★★★★
()
Последнее исправление: tailgunner (всего исправлений: 1)
Ответ на: комментарий от tailgunner

В libc

в libc, все что я нашел касательно форка для posix, так это только заглушку устанавливающую errno в ENOSYS: http://code.metager.de/source/xref/glibc/posix/fork.c

Тебе нужно точно выяснить, как реализованы перехватываемые функции.

я и пытаюсь. тупик случился с форком, точнее с клоном %)

Почему не использовать SELinux/SMACK/AppArmor?

я не админ, я программист. админские дела мне даются в разы тяжелее чем что-то закодить. я уверен, что изучение/настройка чего-либо из перечисленного, у меня бы заняло много больше времени, чем реализация «как я понимаю».

техника «напишем врапперы» - это профанация с точки зрения безопасности?

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

niXman ★★★
() автор топика
Ответ на: комментарий от beastie

немогу осилить этот вывод(время позднее сказывается). подскажите, что из него нужно понять?

niXman ★★★
() автор топика
Ответ на: комментарий от niXman

в libc, все что я нашел касательно форка для posix

Кхм.

 
    42: 0009ae20   688 FUNC    GLOBAL DEFAULT   12 __libc_fork@@GLIBC_PRIVATE
    83: 0009ae20   688 FUNC    GLOBAL DEFAULT   12 __fork@@GLIBC_2.0
   431: 0009ae20   688 FUNC    WEAK   DEFAULT   12 fork@@GLIBC_2.0
  1833: 0009b0d0    81 FUNC    WEAK   DEFAULT   12 vfork@@GLIBC_2.0
  2137: 0009b0d0    81 FUNC    GLOBAL DEFAULT   12 __vfork@@GLIBC_2.1.2

юзер выполняет программы через очень урезанный интерфейс

Если у него есть доступ к компилятору или возможность выполнять свой код, он может обойти твои ограничения.

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

AppArmor

а оно уже входит в состав ядра? или все еще самому нужно патчить/собирать?

niXman ★★★
() автор топика
Ответ на: комментарий от niXman

деталированная подсказка, что где кем выставляется. на линукс (debian) воспроизвести к сожалению не могу (ни fork, ни execve почемуто не всплывают), но думаю, что таким образом расписанный код может помочь осмыслить проблему.

beastie ★★★★★
()
Ответ на: комментарий от tailgunner

Кхм.

я в исходниках искал.

Если у него есть доступ к компилятору или возможность выполнять свой код, он может обойти твои ограничения.

в теории - да. но думаю не каждый юзер способен на такое. другим такое может быть не интересно)

niXman ★★★
() автор топика
Ответ на: комментарий от beastie

в частности видно, что флаг снимается уже родительским fork'ом перед возвращением к system. у ребёнка же флаг менять смысла нет, т.к. он всё равно не вернётся.

итого решение проблемы — не бинарный флаг (0/1), а конечный автомат: system (0 → 1), fork (ребёнок: если 1 → 2, родитель: 1 → 0), execve (если 2, то выполнить).

при этом стоит учесть, что этот путь исполнения только один из возможных (clone???, vfork @ obsd, etc.)

beastie ★★★★★
()
Последнее исправление: beastie (всего исправлений: 2)
Ответ на: комментарий от niXman

какие будут идеи?

Написать свою реализацию system().
Либо воспроизвести в своем коде поведение версии из glibc, чтобы понять, в чем дело.

я не админ, я программист. админские дела мне даются в разы тяжелее чем что-то закодить. я уверен, что изучение/настройка чего-либо из перечисленного, у меня бы заняло много больше времени, чем реализация «как я понимаю».

ИМХО, зря, и правильно в треде предлагают воспользоваться существующими решениями. Лень - главная добродетель программиста :)

gv
()
Ответ на: комментарий от niXman

Если у него есть доступ к компилятору или возможность выполнять свой код, он может обойти твои ограничения.

в теории - да. но думаю не каждый юзер способен на такое. другим такое может быть не интересно)

С таким подходом можно просто прикрепить листик бумаги к компьютеру (или в /etc/motd написать) «не ломайте, пожалуйста». Эффект тот же.

hexdump01010101
()
Ответ на: комментарий от Boy_from_Jungle

мысли вслух: в shared-memory мы можем хранить инфу заспарсенную из политики, и сабжевую переменную in_hook. в конструкторе, мы можем проверять наличие этой самой shared-memory, и таким образом, несколько вызовов конструктора нам не помешают. переменная in_hook тоже перестанет терять свое значение.

получается, что shared-memory поможет с данной ситуации. но скажите, какие новые проблемы можно поиметь?

niXman ★★★
() автор топика
Ответ на: комментарий от hexdump01010101

чисто теоретически, каким бы ты образом сломал такую защиту? приведи пример для system(). подумаем, возможно есть способ этого не допустить...

niXman ★★★
() автор топика

Если религия позволяет использовать плюсы, разнеси по разным неймспейсам и дергай те функции, которые хочешь использовать

EugeneBas ★★
()
Ответ на: комментарий от niXman

я уверен, что изучение/настройка чего-либо из перечисленного, у меня бы заняло много больше времени, чем реализация «как я понимаю».

apparmor точно с пол пинка даже без док поднимается. Я сейчас посмотрел tomoyo, там тоже полиси простые, хотя толком не разбирался.

true_admin ★★★★★
()
Ответ на: комментарий от true_admin

В смысле настраивается. На счёт поднимается не знаю.

true_admin ★★★★★
()
Ответ на: комментарий от niXman

чисто теоретически, каким бы ты образом сломал такую защиту?

Обойти shared library hooks?... Вызывать системные вызовы напрямую. Не использовать shared libraries.

hexdump01010101
()
Ответ на: комментарий от niXman

чисто теоретически, каким бы ты образом сломал такую защиту? приведи пример для system(). подумаем, возможно есть способ этого не допустить...

В реализации system() есть макрос INLINE_SYSCALL - вот и пример :)

gv
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.