Implementing a Distributed Firewall

Вопросы программного кода и архитектуры Linux

Модератор: Olej

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: Implementing a Distributed Firewall

Непрочитанное сообщение Olej » 08 июл 2015, 21:54

Все символы ядра (функции, переменные, структуры) доступны в Linux в псевдофайле /proc/kallsyms:

Код: Выделить всё

$ cat /proc/kallsyms | wc -l
112100
Как видно, число таких символов ядра составляет несколько сот тысяч (в зависимости от версии ядра). Но не все имена могут использоваться в коде модуля, а только те, которые в ядре объявлены как экспортируемые.

Все системные вызовы, в том числе и нужные нам, в ядре косвенно вызываются через sys_call_table. Первая сложность проекта состоит в том, что начиная с ядра Linux 2.6 его авторы исключили имя таблицы системных вызовов sys_call_table из числа экспортируемых. Они сделали это из соображений безопасности … как ониеё понимают. Некоторые публикации вообще утверждают, что использовать в коде модуля ядра sys_call_table невозможно. Но мы можем воспользоваться для поиска адреса sys_call_table такими kernel API как kallsyms_on_each_symbol() или kallsyms_lookup_name() (эта новая функция появилась только в ядрах после 2.6.32). Во всех вариантах наших кодов для поиска адреса не экспортируемого имени ядра мы будем использовать собственную функцию:

Код: Выделить всё

static void* find_sym( const char *sym ) {  // find address kernel symbol sym
   static unsigned long faddr = 0;          // static !!!
   // ----------- nested functions are a GCC extension ---------
   int symb_fn( void* data, const char* sym, struct module* mod, unsigned long addr ) {
      if( 0 == strcmp( (char*)data, sym ) ) {
         faddr = addr;
         return 1;
      }
      else return 0;
   };
   // --------------------------------------------------------
   kallsyms_on_each_symbol( symb_fn, (void*)sym );
   return (void*)faddr;
}


Таким образом во всех кодах модулей ядра мы будем начинать с поиска адреса таблицы системных вызовов:

Код: Выделить всё

taddr = find_sym( "sys_call_table" ) ;

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: Implementing a Distributed Firewall

Непрочитанное сообщение Olej » 08 июл 2015, 21:56

Теперь мы можем найти адрес любой функции обработки любого системного вызова просто по смещению на номер этого системного вызова в массиве taddr, например для системного вызова write() - (void*)taddr[ __NR_write ]

Нам предстоит найти адреса интересующих нас системных вызовов в sys_call_table и заменить на свои функции обработки. Здесь возникает следующая (2-я) не очень большая трудность: как нам объявить синтаксический прототип своей функции на языка C (мы знаем только её физический адрес)? Если мы запишем в коде некорректный прототип функции обработки, то у нас будет разрушено ядро операционной системы при первом же таком вызове! Мы должны подсмотреть синтаксис каждого системного вызова в заголовочном файле ядра <linux/syscalls.h> и безукоризненно им следовать когда создавать свои функции обработки, например:
asmlinkage long new_sys_write ( unsigned int, const char __user*, size_t );
asmlinkage long new_sys_connect( int, struct sockaddr __user *, int );
asmlinkage long new_sys_accept( int, struct sockaddr __user *, int __user * );
asmlinkage long new_sys_socketcall( int call, unsigned long __user *args );

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: Implementing a Distributed Firewall

Непрочитанное сообщение Olej » 08 июл 2015, 21:59

Следующая (3-я) трудность состоит в том, что когда мы попытаемся записать новый адрес своей функции обработки (подмена) мы получим ошибку операции с памятью на процессорах архитектуры i686 или X86_64. Это происходит потому, что разработчики ядра поместили таблицу sys_call_table в физическую страницу RAM отмеченную как readonly аппаратными средствами MMU (memory management unit).

Для записи адреса в sys_call_table мы должны сначала снять флаг readonly, а после записи восстановить его обратно. Делается это аппаратно, записью в скрытый системный управляющий регистр CR0 соответствующего бита. Это действие у нас осуществляют макросы rw_enable() и rw_disable() соответсвенно. Их вид будет отличаться для 32-бит (i686) и 64-бит (X86_64) архитектур. Для 32-бит архитектуры это будет:
// page write protect - on
#define rw_enable() \
asm( "pushl %eax \n" \
"movl %cr0, %eax \n" \
"andl $0xfffeffff, %eax \n" \
"movl %eax, %cr0 \n" \
"popl %eax" );

// page write protect - off
#define rw_disable() \
asm( "pushl %eax \n" \
"movl %cr0, %eax \n" \
"orl $0x00010000, %eax \n" \
"movl %eax, %cr0 \n" \
"popl %eax" );

Этот код написан на инлайновом ассемблере компилятора GCC.

Для 64-бит архитектуры это, предположительно, будет другой код:
// page write protect - on
asm("pushq %rax");
asm("movq %cr0, %rax");
asm("andq $0xfffffffffffeffff, %rax");
asm("movq %rax, %cr0");
asm("popq %rax");

// page write protect - off
asm("pushq %rax");
asm("movq %cr0, %rax");
asm("xorq $0x0000000000001000, %rax");
asm("movq %rax, %cr0");
asm("popq %rax");

Но этот вариант нами не тестировался выполнением.

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: Implementing a Distributed Firewall

Непрочитанное сообщение Olej » 08 июл 2015, 22:01

Следующая (4-я) трудность состоит в том, что мы отрабатываем проект на виртуальной машине. А показанный выше наш код производит запись в скрытые аппараты регистры процессора. Мы должны проверит доступность таких возможностей в среде виртуальной машины. Для этого нами сделан 1-й пример — каталог wrlog.

Он подменяет системный вызов write() как самый безопасный для тестирования. После подмены вывод на терминал из любой программы Linux будет дублироваться в журнал сообщений ядра (что в обычном состоянии не делается).

Ниже показано тестирование примера.

Код: Выделить всё

$ sudo insmod wrlog.ko

$ sudo rmmod wrlog

$ dmesg | tail -n25
[ 1220.476585] device eth0 entered promiscuous mode
[ 7850.815396] ! set new sys_write syscall [f8bff000]
[ 7850.815399] ! CR0 = 8005003b
[ 7850.815401] ! CR0 = 8004003b
[ 7850.815402] ! CR0 = 8005003b
[ 7850.817121] ! {0049} /home/olej/2015_WORK/in.WORK/FWall/drivers/wrlog
[ 7850.817483] ! {0075} \x1b[01;32molej@nvidia\x1b[01;34m ~/2015_WORK/in.WORK/FWall/drivers/wrlog $\x1b[00m 
[ 7853.997031] ! {0001} s
[ 7854.301131] ! {0001} u
[ 7854.541040] ! {0001} d
[ 7854.901050] ! {0001} o
[ 7857.934642] ! {0001}  
[ 7858.317047] ! {0001} r
[ 7859.061051] ! {0001} m
[ 7859.284676] ! {0001} m
[ 7860.268919] ! {0001} o
[ 7860.884974] ! {0001} d
[ 7862.149085] ! {0001}  
[ 7863.748656] ! {0001} w
[ 7864.005039] ! {0001} r
[ 7865.141581] ! {0001} l
[ 7865.453121] ! {0001} o
[ 7865.925047] ! {0001} g
[ 7866.910978] ! {0002} 
[ 7866.917365] ! restore old sys_write syscall [c1179f70]
После удаления модуля работа системы восстанавливается в исходное состояние.

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: Implementing a Distributed Firewall

Непрочитанное сообщение Olej » 08 июл 2015, 22:07

Следующая (5-я) трудность состоит в том, что все 17 сетевых системных вызовов в архитектуре i686 обрабатываются особым, странным образом, отличным от всех прочих системных вызовов. Вот здесь: http://isomerica.net/~dpn/socketcall1.pdf — один из авторов сетевого стека Linux описывает этот способ и сам называет его странным. Все 17 сетевых системных вызовов мультиплексируются в user space через один малоизвестный системный вызов (см. man socketcall(2)):

Код: Выделить всё

int socketcall(int call, unsigned long *args);
Здесь 1-й параметр — это номер сетевого системного вызова (SYS_CONNECT, SYS_ACCEPT etc.)

Это сильно усложняет перехват этих системных вызовов, потому что для восстановления параметров вызова нужно копировать их массив из user space и производить разбор параметров в собственном коде. Для демонстрации соотношений между адресами обработчиков сетевых операций в ядре был сделан 2-й демонстрирующий тест (директория fwtest):

Код: Выделить всё

$ sudo insmod fwtest.ko
address of sys_call_table  = c15b4000
size of sys_call_table  = 349
__NR_socketcall = 102
symbol: sys_write address=c1144d20 found in position 4
symbol: sys_accept address=c149a070 not found
symbol: sys_connect address=c149a0a0 not found
symbol: sys_socketcall address=c149acd0 found in position 102
insmod: error inserting 'fwtest.ko': -1 Operation not permitted
Для отработки перехвата мультиплексированных сетевых системных вызовов был сделан второй демонстрирующий тест (каталог fwtest):

В архитектуре X86_64 обработка 17 сетевых системных вызовов сделана уже напрямую через sys_call_table, единообразно как и для всех остальных системных вызовов. В библиотеках X86_64 даже нет такого системного вызова socketcall() в user space.

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: Implementing a Distributed Firewall

Непрочитанное сообщение Olej » 08 июл 2015, 22:09

Результатом 1-го этапа реализации проекта фаервола стал fwnet.ver1. Он использует в реализации все описанные выше моменты. Это полнофункциональный фильтр сетевых системных вызовов в пространстве ядра. Он реализует функциональность распределенного фаервола, но ещё не имеет интерфейса для конфигурирования и управления разрешениями из user space.

Модуль ядра (драйвер) может при загрузке получить параметрами командной строки IP адрес и TCP порт, соединение по которым будет запрещено. Соединение будет запрещено как со стороны попыток удаленной стороны (accept()), так и со стороны локального хота (connect()).

Вот примеры:

Код: Выделить всё

$ sudo insmod fwnet.ko debug=1 port=10000
$ sudo insmod fwnet.ko deny=192.168.1.1 debug=1
$ sudo insmod fwnet.ko deny=192.168.1.1 port=30000 debug=1
После удаления модуля восстанавливается естественная работа сетевого стека Linux без какой-либо фильтрации:

Код: Выделить всё

$ sudo rmmod fwnet

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: Implementing a Distributed Firewall

Непрочитанное сообщение Olej » 08 июл 2015, 22:25

На этом месте отчёт заканчивается... ;-)

Добавлю, что в каталоге cliserv находятся тестовые клиент и сервер TCP, с которыми делаются испытания.

Код: Выделить всё

olej@nvidia ~/2015_WORK/in.WORK/FWall/disfw $ ls -l cliserv 
итого 36
-rw-rw-r-- 1 olej olej 5987 июля   5 14:27 cliserv.hist
-rw-r--r-- 1 olej olej  497 июля   5 14:33 common.h
-rw-r--r-- 1 olej olej  382 июля   5 14:41 Makefile
-rw-r--r-- 1 olej olej 1062 июля   5 14:30 readline.c
-rw-r--r-- 1 olej olej  529 июня  24 22:47 strsock.c
-rw-r--r-- 1 olej olej 2678 июля   5 14:30 tcpcli.c
-rw-r--r-- 1 olej olej 2940 июля   5 14:41 tcpserv.c
-rw-r--r-- 1 olej olej  444 июня  24 22:45 writen.c
Они несколько отличаются от классики сокетного программирования ... хотя бы тем, что в обычной практике accept() сервера не может возвратить ошибку EIO:

Код: Выделить всё

      int pid,
          newsockfd = accept( sockfd, (struct sockaddr*)&serv_addr, &clilen );
      if( newsockfd < 0 ) {
         if( EIO == errno )
            ERR( "denied TCP port: %m\n" );
         if( EACCES == errno ) {
            fprintf( stdout, "connect from denied IP: %m\n" );
            usleep( 500000 );              // pause 0.5 sec. before repeating
            continue;
         }
         ERR( "accept error: %m\n" );
      }
В нашем случае это означает что это именно фаервол запрещает серверу accept() на этом TCP порту.
И т.д.

Во всех каталогах есть протокольные файлы с терминала .hist, выполнения программ этого каталога... Кому будет интересно - разберётся.

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: Implementing a Distributed Firewall

Непрочитанное сообщение Olej » 10 июл 2015, 14:43

Olej писал(а):На этом месте отчёт заканчивается... ;-)
Заказчик вновь проснулся ;-)
Видно, в том что уже есть и здесь было показано, он и по готовому коду разобраться не может.
И бабки сразу снова нашлись ... если сильно захотеть. :lol:
Так что:
The show must go on!
Проект продолжается, и всё что будет новое (а обещает быть много любопытного) я буду кратко (по мере остатков свободного времени ;-) ) фиксировать здесь.

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: Implementing a Distributed Firewall

Непрочитанное сообщение Olej » 15 июл 2015, 12:05

Olej писал(а): Проект продолжается,
Следующий шаг в развитии:
1. драйвер перехватывает accept() & connect()
2. но кроме этого он создаёт символьное устройство /dev/policy для управления сетвой политикой хоста
3. демон политики (приложение пользовательского пространства) открывает /dev/policy и блокируется на read() в ожидании сетевых syscall-ов
4. как только приходит accept() или connect() драйвер обслуживает операцию read() и возвращает (передаёт) демону параметры сетевого запроса: IP и TCP порт
5. демон должен принять решение да/нет и сообщить его по операции write() для /dev/policy
6. в зависимости от да/нет, полученного в write(), драйвер либо передаёт сетевой запрос на исполнение оригинальному обработчику стека TCP/IP, или отвергает возвращая код ошибки.

На этом шаге (№2) реализуется всё кроме п.6, т.е. фильтрации syscall-ов в модуле ядра драйвера (что в принципе было проверено на шаге №1 и должно быть интегрировано а шаге №3).

Проблема здесь в том, что сетевые запросы accept() и connect() могут идти плотным потоком, практически одновременно: если работают TCP клиент и TCP сервер через loopback интерфейс (127.0.0.1) то connect() от клиента и соединённый accept() от сервера приходят практически одновременно. А операции read() / write() с демоном - дело длинное. Кроме того, неизвестно как долго демон будет "обсуждать" тот или иной запрос, и одинаковая ли задержка для разных запросов. Поэтому в драйвере а). нужна очередь ожидающих разрешения запросов, б). запросам должны присваиваться сериальные идентифицирующие ID, в). запросы должны разрешаться не в порядке поступления, а в порядке их обслуживания демоном.

Это типичная очередь обслуживания. И она реализована в этом варианте, как циклический список на struct list_head, на которых строятся все динамические структуры в ядре современного (>2.6) Linux.

В архиве несколько каталогов. Это разные задачи использующие общие фрагменты кода.

Код: Выделить всё

olej@nvidia ~/2015_WORK/in.WORK/FWall/disfw $ ls -l | grep ^d
drwxr-xr-x 2 olej olej  4096 июля  13 22:38 cliserv
drwxr-xr-x 2 olej olej  4096 июля  13 09:30 fwnet.ver1
drwxr-xr-x 2 olej olej  4096 июля  13 22:38 fwnet.ver2
drwxr-xr-x 2 olej olej  4096 июля  13 22:25 fwtest
drwxr-xr-x 2 olej olej  4096 июля  12 00:08 wrlog
В частности, fwnet.ver1 - это результат шага №1, а fwnet.ver2 - шага №2, fwnet.ver2 не есть прямым развитием fwnet.ver1 - это разные вещи.
cliserv - это комплект тестовых TCP клиента и сервера, которыми тестируется проект, их код несколько отличается от типовых клиент-сервера под специфику задачи.
Вложения
disfw.tgz
(116.93 КБ) 350 скачиваний

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: Implementing a Distributed Firewall

Непрочитанное сообщение Olej » 20 июл 2015, 00:02

Первый вариант, но уже полноценно работающего фаервола (фильтр в ядре fwnet.ko) с демоном политики (fwdaemon).
Установка фильтра:

Код: Выделить всё

olej@mint1 ~/2015_WORK/FWall/disfw.15/fwnet.ver3 $ sudo insmod fwnet.ko debug=2
2 - это только уровень детализации отладочных логов модуля.
Появляется устройство управления политикой разрешений:

Код: Выделить всё

olej@mint1 ~/2015_WORK/FWall/disfw.15/fwnet.ver3 $ ls -l /dev/policy
crw-rw-rw- 1 root root 10, 55 Июл 19 23:12 /dev/policy
До тех пор, пока /dev/policy не открыт, фильтр прозрачен и никакой фильтрации не делает.
Открывает /dev/policy демон политики, который при запуске получает список IP и TCP портов, использвание которых для этого хоста запрещено:

Код: Выделить всё


olej@mint1 ~/2015_WORK/FWall/disfw.14/fwnet.ver3 $ ./fwdaemon -vv -w100 -p30000 -h192.168.56.1
add 0x9939008 rule: -1:30000
add 0x9939020 rule: 20490432:-1
delay before answer 3 msec.
...
Запрещённых хостов (-h) и портов (-p) может быть указано сколько угодно - они увязываются в динамический связный список. -w - это задержка (в мсек.) выработки разрешения, для тестирование фильтра на "тупого" демона политики, когда фильтру валит плотный поток сетевых запросов (в WEB странице).

Ну, вот, собственно, и всё:

- запрет подключения к IP (это всё, конечно, смотрим в разных терминалах):

Код: Выделить всё

lej@mint1 ~/2015_WORK/FWall/disfw.15/cliserv $ ./tcpcli -vv -h 192.168.56.1
can't connect to server: Permission denied

olej@mint1 ~/2015_WORK/FWall/disfw.15/fwnet.ver3 $ ./fwdaemon -vv -w100 -p30000 -h192.168.56.1
...
[23:18.677] connect: sequence 0008 | IP=   192.168.56.1 | TCP PORT=60000 | connect : remote_address=192.168.56.1 remote_port=60000
found in position 0x90de020: 20490432:65535
[23:18.777] write 8 bytes: deny

olej@mint1 ~/2015_WORK/FWall/disfw.15/fwnet.ver3 $ dmesg | tail -n6
[12273.741813] ! connect (8): allocate request block f800e000 - [ 1: 8, ]
[12273.741813] !  ---- read: unblocked
[12273.843951] !  ---- write: find in list block f800e000 sequence 8, write: deny
[12273.844014] !  ---- connect (8): pause in 104 msec.
[12273.844017] ! connect (8): connect : remote_address=192.168.56.1 remote_port=60000 : deny
[12273.844085] ! connect (8): erase request block f800e000 - [ 0: ]
- запрет подключений к запрещённому порту:

Код: Выделить всё

olej@mint1 ~/2015_WORK/FWall/disfw.15/cliserv $ ./tcpcli -vv -h 192.168.1.104 -p 30000
can't connect to server: Permission denied

olej@mint1 ~/2015_WORK/FWall/disfw.15/fwnet.ver3 $ ./fwdaemon -vv -w100 -p30000 -h192.168.56.1
...
[25:28.608] connect: sequence 0009 | IP=  192.168.1.104 | TCP PORT=30000 | connect : remote_address=192.168.1.104 remote_port=30000
found in position 0x90de008: -1:30000
[25:28.709] write 8 bytes: deny

olej@mint1 ~/2015_WORK/FWall/disfw.15/fwnet.ver3 $ dmesg | tail -n6
[12403.674871] ! connect (9): allocate request block f8038000 - [ 1: 9, ]
[12403.674923] !  ---- read: unblocked
[12403.775419] !  ---- write: find in list block f8038000 sequence 9, write: deny
[12403.775490] !  ---- connect (9): pause in 100 msec.
[12403.775493] ! connect (9): connect : remote_address=192.168.1.104 remote_port=30000 : deny
[12403.775563] ! connect (9): erase request block f8038000 - [ 0: ]
- запрет запуска сервера для такого порта:

Код: Выделить всё

olej@mint1 ~/2015_WORK/FWall/disfw.15/cliserv $ ./tcpserv -vv -p30000
listening on the TCP port 30000
denied TCP port: Input/output error

olej@mint1 ~/2015_WORK/FWall/disfw.15/fwnet.ver3 $ ./fwdaemon -vv -w100 -p30000 -h192.168.56.1
add 0x90de008 rule: -1:30000
add 0x90de020 rule: 20490432:-1
delay before answer 100 msec.
[13:42.850] accept wait: sequence 0001 | IP=        0.0.0.0 | TCP PORT=30000 | accept wait : local_address=0.0.0.0 local_port=30000
found in position 0x90de008: -1:30000
[13:42.953] write 8 bytes: deny

olej@mint1 ~/2015_WORK/FWall/disfw.15/fwnet.ver3 $ dmesg | tail -n17
[   28.234209] snd_intel8x0 0000:00:05.0: white list rate for 1028:0177 is 48000
[   32.988889] init: plymouth-upstart-bridge main process ended, respawning
[   39.542677] vboxsf: Successfully loaded version 4.3.10_Ubuntu (interface 0x00010004)
[   40.813676] init: plymouth-stop pre-start process (1940) terminated with status 1
[11624.079310] ! registered device /dev/policy
[11624.083928] !  ---- sys_call_table address = c169a140
[11624.084475] !  ---- CR0 = 8005003b
[11624.084475] !  ---- CR0 = 8004003b
[11624.084475] !  ---- CR0 = 8005003b
[11624.084475] ! install new sys_socketcall handler: f84d25d0
[11681.550441] ! open: filtering set on
[11697.912579] ! accept wait (1): allocate request block f800e000 - [ 1: 1, ]
[11697.912579] !  ---- read: unblocked
[11698.019222] !  ---- write: find in list block f800e000 sequence 1, write: deny
[11698.019435] !  ---- accept wait (1): pause in 108 msec.
[11698.019438] ! accept wait (1): accept wait : local_address=0.0.0.0 local_port=30000 : deny
[11698.019492] ! accept wait (1): erase request block f800e000 - [ 0: ]
Всё это - при сохранении прозрачной работы всех прочих сетевых средств.
Вложения
disfw.15.tgz
(132.15 КБ) 329 скачиваний

Ответить

Вернуться в «Linux изнутри»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 9 гостей