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() };
   }
}
подскажите, где ступил?

благодарен.

★★★

Что-то у вас не так, покажите *минимальный* неработающий код.

Вот минимальный работающий:

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

int var = 0;

pid_t fork()
{
        printf("fork: var = %d, setting to 1\n", var);
        var = 1;

        void *libc = dlopen("/lib/x86_64-linux-gnu/libc.so.6", RTLD_LAZY);

        pid_t (* libc_fork)() = dlsym(libc, "fork");

        return libc_fork();
}

int pipe(int fd[2])
{
        printf("pipe: pid = %d, var = %d\n", getpid(), var);
        return 0;
}
#include <unistd.h>
#include <stdlib.h>

int main()
{
        int fd[2];

        fork();
        pipe(fd);

        return 0;
}
$ LD_PRELOAD=`pwd`/libcheck.so ./a.out
fork: var = 0, setting to 1
pipe: pid = 27358, var = 1
pipe: pid = 27360, var = 1
gv
()

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

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

переменная-флаг зануляется еще до вызова fork(). в этом-то и странность.

сейчас попробую воспроизвести.

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

переменная-флаг зануляется еще до вызова fork().

valgrind на всякий случай натрави

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

да, ситуация полностью воспроизводится.

код .so:


#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;

int in_hook = 0;

void __attribute__ ((constructor)) my_init(void) {
	_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);
	}
}

int system(const char *cmd) {
	fprintf(stdout, ">>> in function \"%s()\", before set in_hook=%d\n", __func__, in_hook);
	in_hook = 1;
	fprintf(stdout, "before call system() in_hook=%d\n", in_hook);
	int rc = ((typeof(&system))_system)(cmd);
	fprintf(stdout, "after call system() in_hook=%d\n", in_hook);
	in_hook = 0;
	return rc;
}

pid_t fork() {
	fprintf(stdout, ">>> in function \"%s()\", before set in_hook=%d\n", __func__, in_hook);
	in_hook = 1;
	fprintf(stdout, "before call fork() in_hook=%d\n", in_hook);
	pid_t rc = ((typeof(&fork))_fork)();
	fprintf(stdout, "after call fork() in_hook=%d\n", in_hook);
	in_hook = 0;
	return rc;
}

int execve(const char *filename, char *const argv[], char *const envp[]) {
	fprintf(stdout, ">>> in function \"%s()\", before set in_hook=%d\n", __func__, in_hook);
	in_hook = 1;
	fprintf(stdout, "before call execve() in_hook=%d\n", in_hook);
	int rc = ((typeof(&execve))_execve)(filename, argv, envp);
	fprintf(stdout, "after call execve() in_hook=%d\n", in_hook);
	in_hook = 0;
	return rc;
}

код тестовой проги:
#include <errno.h>
#include <stdio.h>
#include <unistd.h>

int main() {
	int r = system("ls -l");
	perror("system()");
	return r;
}
вывод:
>> in function "system()", before set in_hook=0
before call system() in_hook=1
>>> in function "fork()", before set in_hook=0
before call fork() in_hook=1
after call fork() in_hook=1
after call fork() in_hook=1
>>> in function "execve()", before set in_hook=0
before call execve() in_hook=1

тут вывод 'ls'
идеи?

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

в мане к execve() говорится следующее:

execve() does not return on success, and the text, data, bss, and stack of the calling process are overwritten by that of the program loaded.

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

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

Вполне может быть, что ето шутки компилятора. Он ведь может на своё усмотрение переставлять куски кода как ему вздумается. Проверь интересса ради с -O0 и -O2. volatile в таком случае тоже должен помочь.

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

пробовал и с volatile и с '-O0'. без разницы. причина в чем-то другом.

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

Если вызывать из main fork(), то значение переменной сохраняется.

Если вызывать system(), то:
1) Хук на fork() не вызывается
2) После clone и после execve заново вызывается конструктор библиотеки (my_init), то есть еще 2 раза.

В чем особенность system() пока не понял.

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

ну, мало ли… ☺

в любом случае у меня твой код не воспроизводится:

>>> in function "system()", before set in_hook=0
before call system() in_hook=1
>>> in function "execve()", before set in_hook=1
before call execve() in_hook=1
total 156
-rw-r--r--  1 demon  demon     49 Nov  5 21:17 Makefile
-rw-r--r--  1 demon  demon     58 Nov  5 21:24 Makefile.main
-rw-r--r--  1 demon  demon   1545 Nov  5 21:20 lib.c
-rw-r--r--  1 demon  demon   7041 Nov  5 21:31 lib.o
-rw-r--r--  1 demon  demon   7146 Nov  5 21:31 lib.po
-rw-r--r--  1 demon  demon   7430 Nov  5 21:31 lib.so
-rw-r--r--  1 demon  demon   7264 Nov  5 21:31 liblib.a
-rwxr-xr-x  1 demon  demon  10226 Nov  5 21:31 liblib.so.1.0
-rw-r--r--  1 demon  demon   7368 Nov  5 21:31 liblib_p.a
-rw-r--r--  1 demon  demon   7678 Nov  5 21:31 liblib_pic.a
-rwxr-xr-x  1 demon  demon   7019 Nov  5 21:31 main
-rw-r--r--  1 demon  demon    197 Nov  5 21:26 main.c
-rw-r--r--  1 demon  demon   1004 Nov  5 21:31 main.o
-rw-r--r--  1 demon  demon     16 Nov  5 21:29 shlib_version
after call system() in_hook=1
system(): Undefined error: 0

с `in_hook' в принципе ясно, т.к. execve возвращается только в случае ошибки. (ну и заметь, что в моём случае fork'а нет ☺)

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

1) Хук на fork() не вызывается

в выводе же видно, что вызывается.

После clone и после execve

наоборот. сначала вызывается execve() и потом clone()

вызывается конструктор библиотеки (my_init), то есть еще 2 раза.

да, но только если из main() звать system(). если fork() - такого не происходит.

В чем особенность system() пока не понял.

вот реализация system: http://code.metager.de/source/xref/eglibc/libc/sysdeps/posix/system.c

в самом конце, устанавливается алиас system на __libc_system

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

Я может чего-то не понимаю, но всё работает как надо. Т.е. после установки in_hook в 1 оно так и остаётся, что и видно по выводу. Или я чего-то не понял?

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

по поводу fork'а — execve — это системный вызов. т.ч. fork делает сам кернел, а не userland fork. в этом плане glibc реализация с багом — fork лишний.

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

что ты этим пытаешься сказать? не понимаю...

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

да, ты прав.

nixman@nixman-pc:~/tests$ LD_PRELOAD=./hook.so ./hooktest ctor called!

in function «system()», before set in_hook=0, pid=26264

before call system() in_hook=1, pid=26264 ctor called!

in function «fork()», before set in_hook=0, pid=26265

before call fork() in_hook=1, pid=26265 after call fork() in_hook=1, pid=26265 after call fork() in_hook=1, pid=26266

in function «execve()», before set in_hook=0, pid=26266

before call execve() in_hook=1, pid=26266 ctor called! total 260

... ...

after call system() in_hook=1, pid=26264 system(): Success

теперь осталось понять, что происходит..

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

ни чего там 2 раза не вызывается:

>>> enter "my_init()"
<<< leave "my_init()"
>>> enter "system()"
>>> enter "vfork()"
<<< leave "vfork()"
>>> enter "execve()"
<<< leave "vfork()"
# output stipped
<<< leave "system()"
system(): Undefined error: 0
in_hook: 0

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

Если коротко, это похоже на гонку. Кто участвует - сказать трудно, не зная графа вызовов. Что у тебя что вызывает?

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

да, на OpenBSD похоже не вызывается)

но мне нужно для линукс %)

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

Что у тебя что вызывает?

'main() -> system() -> fork() -> execve()' а дальше наверное clone(), но на него хука у меня нет.

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

вывод перепощу, а то форум все помержил %)

nixman@nixman-pc:~/tests$ LD_PRELOAD=./hook.so ./hooktest
ctor called!
>>> in function "system()", before set in_hook=0, pid=26264
before call system() in_hook=1, pid=26264
ctor called!
>>> in function "fork()", before set in_hook=0, pid=26265
before call fork() in_hook=1, pid=26265
after call fork() in_hook=1, pid=26265
after call fork() in_hook=1, pid=26266
>>> in function "execve()", before set in_hook=0, pid=26266
before call execve() in_hook=1, pid=26266
ctor called!

ls
...

after call system() in_hook=1, pid=26264
system(): Success

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

Короче, я написал вначале int in_hook = 666 и получил удивительный результат. При fork оно опять принимает это значение. Т.е. тут не обнуление а какая-то двойная инициализация что-ли... Пришло время смотреть исходники fork?

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

ключевой момент вот где:

before call system() in_hook=1, pid=26264
ctor called!

почему конструктор вызывается еще до форка?

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

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

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

«правильную» реализацию fork() я так и не смог найти. вот(http://code.metager.de/source/xref/glibc/sysdeps/posix/system.c) реализация system(). в 118 строке кликни на идентификатор __fork(). тебе покажут заглушку, реализацию для какого-то hurd, и заглушку для sysv.

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

Насколько я могу судить, в потомке in_hook может обнулиться до возврата из «вызываем реальный system» первой ветки. В этом заключается проблема?

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

В этом заключается проблема?

нет.

смотри:

1: before call system() in_hook=1, pid=26264
2: ctor called!
3: in function «fork()», before set in_hook=0, pid=26265

(добавил номерки)

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

вопрос в том, почему при вызове из system() моего хукового fork(), значение переменной in_hook потеряно? и почему еще раз вызывается конструктор?

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

вопрос в том, почему при вызове из system() моего хукового fork(), значение переменной in_hook потеряно?

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

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

Гм, просто при вызове реального system() в дочернем процессе библиотека инициализируется заново.

ИМХО здесь нет гонки, а вопрос в том, почему происходит эта инициализация.

Насколько я понял, в system() вызывается не fork(), а clone() напрямую.

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

здесь нет гонки, а вопрос в том, почему происходит эта инициализация.

именно!

Насколько я понял, в system() вызывается не fork(), а clone() напрямую.

но никакие исходники этого не подтверждают..

как вариант, можно попробовать установить хук и на clone()...

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

Вот самый минимальный пример :)

$ cat 1.c
void __attribute__ ((constructor)) my_init(void) {
        printf("my_init\n");
}
$ cat 2.c
int main()
{
        system("date");
        return 0;
}
$ gcc -shared 1.c -o lib1.so -fPIC
$ gcc 2.c
$ LD_PRELOAD=`pwd`/lib1.so ./a.out
my_init
my_init
my_init
Tue Nov  6 01:49:11 MSK 2012
gv
()
Ответ на: комментарий от niXman

еще один из вариантов - предотвратить загрузку/вызов_конструктора второй раз. но тут встает вопрос: как?

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

но никакие исходники этого не подтверждают..

define FORK() \
   INLINE_SYSCALL (clone, 3, CLONE_PARENT_SETTID | SIGCHLD, 0, &pid)

В system() вызывается FORK()

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

Я думаю самый разумный вариант - спросить в рассылке glibc, почему этот конструктор вызывается.

Наверняка из-за каких-нибудь соображений по поводу безопасности..

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

этот пример не совсем подходит к моей ситуации, ибо, в моем случае, второй вызов конструктора происходит еще до вызова fork().

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

вопрос в том, почему при вызове из реального system() моего хукового fork(), значение переменной in_hook, потеряно?

Единственное более-менее логичное объяснение, которое я могу дать в это позднее время: system вызывает не fork, а vfork (что логично), за ним execve, и к моменту вызова fork in_hook приходит в дефолтное состояние, 0. Я понимаю, что это объясняет не всё, но дальше уже нужно смотреть ptrace'ом, какие конкретно системные вызовы делаются.

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

Гм, в моей системе:
1. Хук для fork не вызывается
2. Во время вызова system вызывается clone
3. Затем вызывается инициализация библиотеки
3. Затем новый процесс делает exec, вызывается хук и читает обнуленное значение in_hook

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