Evitando falhas de seguran�a ao desenvolver uma aplica��o - Parte 5: race conditions

ArticleCategory:

Software Development

AuthorImage:

[image of the authors]

TranslationInfo:

Original in fr Frédéric Raynal, Christophe Blaess, Christophe Grenier

fr to en Georges Tarbouriech

en to en Lorne Bailey

en to pt Bruno Sousa

AboutTheAuthor:

O Christophe Blaess � um engenheiro aeron�utico independente. Ele � um f� do Linux e faz muito do seu trabalho neste sistema. Coordena a tradu��o das p�ginas man publicadas no Projecto de Documenta��o do Linux.

O Christophe Grenier � um estudante no 5� ano na ESIEA, onde, tamb�m trabalha como administrador de sistema. Tem uma paix�o por seguran�a de computadores.

O Frederic Raynal tem utilizado o Linux desde h� alguns anos porque n�o polui, n�o usa hormonas, n�o usa MSG ou farinha animal ... reclama somente o suor e a ast�cia.

Abstract:

Este quinto artigo da nossa s�rie � dedicado a problemas de seguran�a relacionados com a multitarefa. As race condition ocorrem quando v�rios processos utilizam o mesmo recurso ao mesmo tempo (ficheiro, dispositivo, mem�ria), de maneira a que cada um "acredita" que tem acesso exclusivo. Isto leva a bugs dif�ceis de detectar mas tamb�m a falhas de seguran�a reais capazes de comprometer a seguran�a global de um sistema.

ArticleIllustration:[illustration]

[article illustration]

ArticleBody:[The real article: put the text and html-codes here]

Introdu��o

O princ�pio geral das condi��es race � o seguinte: um processo quer aceder a um recurso do sistema exclusivamente. Verifica se o recurso n�o est� j� a ser usado por outro processo, depois toma a sua posse e usa-o como quer. O problema aparece quando um outro processo tenta beneficiar do lapso de tempo entre a verifica��o e o verdadeiro acesso para tomar posse do mesmo recurso. Os resultados podem variar. O exemplo cl�ssico na teoria dos S.O. � a verifica��o infinita de ambos os processos. Em casos mais pr�ticos, isto conduz ao mau funcionamento das aplica��es. ou a falhas de seguran�a quando um processo erradamente, beneficia dos privil�gios de outro.

O que n�s, previamente, chamamos de um recurso pode ter diferentes aspectos. A maioria dos problemas das race conditions por vezes descobertos e corrigidos no pr�prio kernel do Linux, assentam no acesso competitivo �s �reas de mem�ria. Aqui focaremos as aplica��es de sistema e consideraremos que os recursos falados s�o n�s do sistema de ficheiros. Isto n�o s� diz respeito aos ficheiros comuns como tamb�m o acesso a dispositivos atrav�s de entradas especiais a partir do direct�rio /dev/.

A maioria das vezes uma tentativa de ataque � seguran�a do sistema � feito contra as aplica��es Set-UID, visto que o atacante pode correr o programa at� que consiga beneficiar dos privil�gios dados ao dono dos ficheiros execut�veis. Contudo, diferentemente, das falhas de seguran�a j� discutidas (o buffer overflow, formata��o de strings...), as race conditions, normalmente, n�o nos permitem executar o c�digo "personalizado". D�o somente a oportunidade de beneficiar dos recursos de um programa enquanto est� a rodar. Este tipo de ataque tamb�m se aplica a utilit�rios "normais" ( e n�o s� Set-UID), o pirata esperando escondido, � espera de outro utilizador, em especial o root, para correr a aplica��o em quest�o de modo aceder aos seus recursos. Isto tamb�m � verdade para escrever para dentro de um ficheiro (por exemplo ~/.rhost no qual a string "+ +" fornece um acesso directo a partir de qualquer m�quina sem pedir password) ou para ler um ficheiro confidencial (dados comerciais sens�veis, informa��o m�dica pessoal, ficheiro de passwords, chave privada...)

Diferentemente �s falhas de seguran�a discutidas nos artigos anteriores este problema de seguran�a aplica-se a qualquer aplica��o e n�o somente aos utilit�rios Set-UID e servidores de sistema ou dem�nios.

Primeiro Exemplo

Reparemos no comportamento de um programa Set-UID que tem de guardar os seus dados num ficheiro da perten�a do utilizador. Pod�amos, por exemplo, considerar o caso de um software transportador de mail como o sendmail. Suponhamos que o utilizador pode fornecer quer o nome do ficheiro de backup e uma mensagem para escrever dentro desse mesmo ficheiro, o que � plaus�vel sobre determinadas circunst�ncias. A aplica��o deve depois verificar se o ficheiro pertence � pessoa a que iniciou o programa. Deve tamb�m verificar se n�o � um link para um ficheiro de sistema. N�o esque�amos que o programa ao ser Set-UID root, � - lhe permitido modificar qualquer ficheiro na m�quina. Segundo isto o programa comparar� o dono do ficheiro com o seu UID real. Escrevamos algo do g�nero:

1     /* ex_01.c */
2     #include <stdio.h>
3     #include <stdlib.h>
4     #include <unistd.h>
5     #include <sys/stat.h>
6     #include <sys/types.h>
7    
8     int
9     main (int argc, char * argv [])
10    {
11        struct stat st;
12        FILE * fp;
13
14        if (argc != 3) {
15            fprintf (stderr, "usage : %s file message\n", argv [0]);
16            exit(EXIT_FAILURE);
17        }
18        if (stat (argv [1], & st) < 0) {
19            fprintf (stderr, "can't find %s\n", argv [1]);
20            exit(EXIT_FAILURE);
21        }
22        if (st . st_uid != getuid ()) {
23            fprintf (stderr, "not the owner of %s \n", argv [1]);
24            exit(EXIT_FAILURE);
25        }
26        if (! S_ISREG (st . st_mode)) {
27            fprintf (stderr, "%s is not a normal file\n", argv[1]);
28            exit(EXIT_FAILURE);
29        }
30        
31        if ((fp = fopen (argv [1], "w")) == NULL) {
32            fprintf (stderr, "Can't open\n");
33            exit(EXIT_FAILURE);
34        }
35        fprintf (fp, "%s\n", argv [2]);
36        fclose (fp);
37        fprintf (stderr, "Write Ok\n");
38        exit(EXIT_SUCCESS);
39    }

Como explicado no nosso primeiro artigo, seria melhor para uma aplica��o Set-UID perder temporariamente os seus privil�gios e abrir o ficheiro com o UID real do utilizador que o invocou. De facto a situa��o acima raramente corresponde � de um dem�nio que fornece servi�os a qualquer utilizador. Sempre a correr com o ID do root, devia verificar o UID em vez do seu UID verdadeiro. N�o obstante, manteremos este esquema, mesmo que n�o seja t�o real�stico, visto que permite entender o problema, enquanto "explora��o" f�cil da falha de seguran�a.

Como podemos ver, o programa come�a por fazer todos os controles necess�rios, verificando se o ficheiro existe, se pertence ao utilizador e se � um ficheiro normal. De seguida abre, realmente, o ficheiro e escreve a mensagem. � aqui que assenta a falha de seguran�a. Ou mais exactamente no lapso de tempo entre a leitura dos atributos do ficheiro com o stat() e a sua abertura com o fopen(). Este lapso de tempo � geralmente muito curto mas n�o � nulo, ent�o um atacante pode beneficiar disso para alterar as caracter�sticas do ficheiro. Para tornar o nosso ataque ainda mais f�cil adicionemos uma linha que fa�a adormecer o processo entre as opera��es, tendo assim tempo necess�rio para o fazer � m�o. Alteramos a linha 30 (previamente vazia) e inserimos:

30        sleep (20);

Implement�mo-lo, agora; mas fa�amos primeiro aplica��o Set-UID root. Fa�amos de seguida, muito importante, um backup do nosso ficheiro de palavras passe /etc/shadow :

$ cc ex_01.c -Wall -o ex_01
$ su
Password:
# cp /etc/shadow /etc/shadow.bak
# chown root.root ex_01
# chmod +s ex_01
# exit
$ ls -l ex_01
-rwsrwsr-x 1 root  root    15454 Jan 30 14:14 ex_01
$

Est� tudo pronto para o ataque. Estamos num direct�rio da nossa perten�a. Temos um utilit�rio Set-UID root (aqui ex_01) suportando uma falha de seguran�a e sentimo-nos como que a substituir a linha que diz respeito ao root no ficheiro /etc/shadow por uma linha contendo uma password vazia.

Primeiro, criamos um ficheiro fic que nos perten�a:

$ rm -f fic
$ touch fic

De seguida, corremos a nossa aplica��o em background "para manter a lideran�a". Pedimos-lhe para escrever uma string para dentro do ficheiro. Verificando o que cont�m, adormecendo por um pouco antes de realmente aceder ao ficheiro.

$ ./ex_01 fic "root::1:99999:::::" &
[1] 4426

O conte�do da linha do root prov�m da p�gina manual shadow(5), o mais importante � que o segundo campo esteja vazio (sem palavra passe). Enquanto o processo est� adormecido temos, cerca de 20 segundos para remover o ficheiro fic e substitu�-lo por um link (simb�lico ou f�sico, ambos funcionam) ao ficheiro /etc/shadow. Lembremos que um utilizador pode criar um link para um ficheiro mesmo que n�o possa ler o seu conte�do, num direct�rio da sua perten�a ( ou at� mesmo em /tmp, como veremos mais tarde). contudo n�o � poss�vel de criar uma c�pia de tal ficheiro, visto que requeria um modo de leitura completa.

$ rm -f fic
$ ln -s /etc/shadow ./fic

Depois pedimos � shell para trazer o processo ex_01 para foreground com o comando fg, e esperamos que termine:

$ fg
./ex_01 fic "root::1:99999:::::"
Write Ok
$

Voil� ! Est� terminado, o ficheiro /etc/shadow s� cont�m uma linha indicando que o root n�o tem password. N�o acredita ?

$ su
# whoami
root
# cat /etc/shadow
root::1:99999:::::
#

Terminemos a nossa experi�ncia, repondo o velho ficheiro de passwords.

# cp /etc/shadow.bak /etc/shadow
cp: replace `/etc/shadow'? y
#

Sejamos mais realistas

Tivemos sucesso ao explorar uma condi��o num utilit�rio Set-UID root. Claro que este programa era muito "amig�vel" esperando 20 segundos, dando-nos tempo necess�rio de modificar os ficheiros nas suas costas. Mesmo numa aplica��o real, a race condition s� se aplica durante pequenos lapsos de tempo. Como beneficiar disto ?

Normalmente o princ�pio de ataque assenta num ataque brutal, renovando as tentativas de ataque, cem, mil ou dez mil vezes utilizando scripts para automatizar a sequ�ncia. � poss�vel melhorar a chance de "descobrir" a falha de seguran�a com v�rios truques com vista a aumentar o lapso de tempo entre as duas opera��es que o programa, erradamente, considera como atomicamente ligadas. A ideia � abrandar o processo de destino para se administrar mais facilmente o atraso que precede a modifica��o do ficheiro. Diferentes pontos de vista podem ser arquitectados para alcan�ar o nosso objectivo:

O m�todo que nos permite beneficiar de uma falha de seguran�a baseado em race conditions � por isso aborrecido e repetitivo, mas � realmente �til ! Tentemos descobrir solu��es efectivas.

Poss�veis Melhoramentos

O problema acima discutido assenta na habilidade de alterar o conte�do de um objecto durante o lapso de tempo entre as duas opera��es respeitantes ao objecto, sendo uma coisa cont�nua tanto quanto poss�vel. Na situa��o anterior, a modifica��o n�o dizia respeito ao ficheiro em si. Al�m disso, sendo um utilizador normal teria sido um pouco dif�cil, modificar ou at� mesmo ler o ficheiro /etc/shadow. De facto, a altera��o assenta na liga��o entre o n� do ficheiro na �rvore nomeada e o ficheiro em si mesmo como uma entidade f�sica. Lembremos que a maioria dos comandos do sistema (rm, mv, ln, etc.) actuam sobre o nome do ficheiro e n�o no seu conte�do. Mesmo quando se apaga um ficheiro (utilizando o rm e a chamada de sistema unlink()) o conte�do � realmente apagado quando a �ltima liga��o f�sica - �ltima refer�ncia - � removida.

O erro feito no programa anterior � por isso, considerar a associa��o entre o nome do ficheiro e o seu conte�do como inalter�vel, ou pelo menos constante durante o lapso de tempo entre a opera��o stat() e fopen(). Ent�o, � suficiente aplicar um exemplo de uma liga��o f�sica para verificar esta associa��o n�o � de todo permanente. Vejamos um exemplo utilizando este tipo de liga��o. Num direct�rio da nossa perten�a criamos um novo link para um ficheiro de sistema. Claro que o dono e o modo de acesso s�o mantidos. O comando ln com a op��o -f for�a a cria��o mesmo que o dono j� exista:

$ ln -f /etc/fstab ./myfile
$ ls -il /etc/fstab myfile
8570 -rw-r--r--   2 root  root  716 Jan 25 19:07 /etc/fstab
8570 -rw-r--r--   2 root  root  716 Jan 25 19:07 myfile
$ cat myfile
/dev/hda5   /                 ext2    defaults,mand   1 1
/dev/hda6   swap              swap    defaults        0 0
/dev/fd0    /mnt/floppy       vfat    noauto,user     0 0
/dev/hdc    /mnt/cdrom        iso9660 noauto,ro,user  0 0
/dev/hda1   /mnt/dos          vfat    noauto,user     0 0
/dev/hda7   /mnt/audio        vfat    noauto,user     0 0
/dev/hda8   /home/ccb/annexe  ext2    noauto,user     0 0
none        /dev/pts          devpts  gid=5,mode=620  0 0
none        /proc             proc    defaults        0 0
$ ln -f /etc/host.conf ./myfile
$ ls -il /etc/host.conf myfile 
8198 -rw-r--r--   2 root  root   26 Mar 11  2000 /etc/host.conf
8198 -rw-r--r--   2 root  root   26 Mar 11  2000 myfile
$ cat myfile
order hosts,bind
multi on
$ 

A op��o /bin/ls -i apresenta o n�mero do n� no inicio da linha. Ent�o podemos ver que o mesmo nome aponta para dois nodos f�sicos diferentes.

De facto, gostar�amos que as fun��es usadas para verificar e aceder ao ficheiro, apontassem para o mesmo conte�do e para o mesmo nodo. E � poss�vel ! O kernel por si mesmo controla esta associa��o automaticamente, quando nos fornece um descritor de ficheiro. Quando abrimos um ficheiro para leitura, a chamada ao sistema open() retorna um valor inteiro que � o descritor, associando-o ao ficheiro f�sico com uma tabela interna. Toda a leitura que a seguir fizermos ser� relacionada com o conte�do deste ficheiro, sem importar o nome utilizado durante a opera��o de abertura.

Destaquemos este ponto: Logo que um ficheiro for aberto, qualquer opera��o que diga respeito ao nome do ficheiro. incluindo a sua remo��o, n�o ter� efeito no seu conte�do. Logo que exista um processo que tem um descritor para um ficheiro, o conte�do do ficheiro n�o � removido do disco, mesmo que o seu nome desapare�a do direct�rio onde estava armazenada. O kernel assegura que associa��o ao conte�do do ficheiro � mantida durante o lapso de tempo entre a chamada de sistema open() que fornece um descritor de ficheiro e a liberta��o desse descritor usando close() ou quando o processo termina.

Mas depois, obtemos a nossa solu��o ! O suficiente para come�ar a abrir o nosso ficheiro verificando depois as permiss�es, examinando as caracter�sticas do descritor em vez do nome do ficheiro. Isto � feito utilizando a chamada de sistema fstat() (esta �ltima a trabalhar com o stat()), verificando geralmente o descritor do ficheiro em vez da sua path (caminho). Para obter um fluxo de E/S � volta do descritor utilizaremos a fun��o fdopen() (trabalhando como o fopen()) ao assentar no descritor em vez do nome do ficheiro. Ent�o o programa surge:

1    /* ex_02.c */
2    #include <fcntl.h>
3    #include <stdio.h>
4    #include <stdlib.h>
5    #include <unistd.h>
6    #include <sys/stat.h>
7    #include <sys/types.h>
8
9     int
10    main (int argc, char * argv [])
11    {
12        struct stat st;
13        int fd;
14        FILE * fp;
15
16        if (argc != 3) {
17            fprintf (stderr, "usage : %s file message\n", argv [0]);
18            exit(EXIT_FAILURE);
19        }
20        if ((fd = open (argv [1], O_WRONLY, 0)) < 0) {
21            fprintf (stderr, "Can't open %s\n", argv [1]);
22            exit(EXIT_FAILURE);
23        }
24        fstat (fd, & st);
25        if (st . st_uid != getuid ()) {
26            fprintf (stderr, "%s not owner !\n", argv [1]);
27            exit(EXIT_FAILURE);
28        }
29        if (! S_ISREG (st . st_mode)) {
30            fprintf (stderr, "%s not a normal file\n", argv[1]);
31            exit(EXIT_FAILURE);
32        }
33        if ((fp = fdopen (fd, "w")) == NULL) {
34            fprintf (stderr, "Can't open\n");
35            exit(EXIT_FAILURE);
36        }
37        fprintf (fp, "%s", argv [2]);
38        fclose (fp);
39        fprintf (stderr, "Write Ok\n");
40        exit(EXIT_SUCCESS);
41    }

Desta vez, ap�s a linha 20, nenhuma modifica��o ao nome do ficheiro (remo��o, re-nomea��o, liga��o) afectar� o comportamento do nosso programa, o conte�do do ficheiro original ser� mantido.

Linhas de Ajuda

�, ent�o, importante ao manipular um ficheiro, garantir que a associa��o entre a representa��o interna e o conte�do real permanece constante. Preferivelmente, utilizaremos as seguintes chamadas de sistema que manipulam um ficheiro f�sico como um descritor aberto em vez dos seus semelhantes que utilizam o caminho para o ficheiro:

Chamada de Sistema Uso
fchdir (int fd) Vai directamente para o direct�rio representado por fd.
fchmod (int fd, mode_t mode) Altera as permiss�es de acesso ao ficheiro.
fchown (int fd, uid_t uid, gid_t gif) Altera o propriet�rio do ficheiro.
fstat (int fd, struct stat * st) Consulta a informa��o armazenada com o nodo do ficheiro f�sico.
ftruncate (int fd, off_t length) Trunca um ficheiro existente.
fdopen (int fd, char * mode) Obt�m um fluxo de E/S � volta de um descritor j� aberto. � uma rotina da biblioteca stdio e, n�o uma chamada de sistema.

Claro que depois, deve abrir o ficheiro no modo desejado, chamando o open() (n�o se esque�a do terceiro argumento ao criar um ficheiro novo). Falaremos mais acerca do open(), quando falarmos do problema dos ficheiros tempor�rios.

Devemos insistir que � importante verificar os c�digos de retorno das chamadas ao sistema. Por exemplo, mencionemos, mesmo n�o tendo nada haver com as race conditions, um problema numa implementa��o velha do /bin/login devido a uma neglig�ncia de verificar o c�digo de retorno. Esta aplica��o fornecia automaticamente acesso root quando n�o encontrava o ficheiro /etc/passwd. O comportamento at� parece aceit�vel visto tratar-se de o dano de um ficheiro de sistema. Por outro lado, verificar se era imposs�vel abrir o ficheiro em vez de verificar a sua exist�ncia � menos aceit�vel. Bastava chamar o /bin/login, ap�s o n�mero m�ximo de descritores permitidos para um utilizador, para conseguir ter acesso como root... Findemos esta digress�o insistindo em como � importante verificar, n�o s� as chamadas de sistema com sucesso ou falha, bem como os c�digos de erro antes de levar em frente alguma ac��o respeitante � seguran�a do sistema.

Race conditions ao conte�do do ficheiro

Um programa que diga respeito � seguran�a do sistema n�o deve assentar num acesso exclusivo ao conte�do de um ficheiro. Mais precisamente, � preciso assegurar o risco das race conditions ao mesmo ficheiro. O principal perigo vem de um utilizador correndo m�ltiplas inst�ncias de uma aplica��o Set-UID root ou estabelecendo v�rias liga��es ao mesmo tempo com o mesmo dem�nio, esperando criar uma situa��o de race condition, durante a qual o conte�do de um ficheiro de sistema podia ser modificado de um modo anormal.

Para evitar que um programa seja sens�vel a este tipo de situa��o, � necess�rio estabelecer um mecanismo de acesso exclusivo � informa��o do ficheiro. Este � o mesmo problema encontrado nas base de dados quando os utilizadores t�m autoriza��o para consultar ou alterar o conte�do de um ficheiro. O principio de bloqueio de ficheiros permite resolver este problema.

Quando um processo deseja escrever para um ficheiro, pede ao kernel para o bloquear todo, ou parte dele. Logo que o processo mant�m o bloqueio mais nenhum processo pode pedir o bloqueio para o mesmo ficheiro, ou pelo menos a mesma parte do ficheiro. Do mesmo modo que um processo pede para bloquear antes de ler o conte�do de um ficheiro, o que assegura que n�o haver� modifica��es enquanto se mantiver o bloqueio.

De facto o sistema � mais esperto que isto: o kernel diferencia os bloqueios requeridos para escrita dos de leitura. V�rios processos podem, simultaneamente, beneficiar de um bloqueio de leitura, visto que nenhum tentar� alterar o conte�do do ficheiro. Contudo s� um processo � que pode beneficiar de um bloqueio de escrita num dado tempo e a mais nenhum dar� dado bloqueio, nem sequer de leitura no mesmo intervalo de tempo.

Existem dois tipos de bloqueio (na maioria incompat�veis entre si). O primeiro vem do BSD e assenta na chamada de sistema flock(). O seu primeiro argumento � o descritor do ficheiro ao qual pretende aceder em modo exclusivo, o segundo � uma constante simb�lica que representa a opera��o a ser feita. Pode ter valores diferentes: LOCK_SH (bloqueio para leitura), LOCK_EX (para escrita), LOCK_UN (libertar o bloqueio). A chamada de sistema permanece bloqueada at� que a opera��o requerida seja imposs�vel. Contudo, pode adicionar (utilizando um bin�rio OR |) a constante LOCK_NB para a chamada falhar em vez de permanecer bloqueada.

O segundo tipo de bloqueio vem do Sistema V e, assenta na chamada ao sistema fcntl() cuja chamada � um pouco complicada. Existe uma fun��o da biblioteca chamada lockf() muito semelhante � chamada de sistema mas sem tanta performance. O primeiro argumento do fcntl() � o descritor do ficheiro a bloquear. O segundo representa a opera��o a ser feita: F_SETLK e F_SETLKW administram um bloqueio, o segundo permanece bloqueado at� a opera��o ser poss�vel, enquanto que o primeiro retorna imediatamente no caso de erro. O F_GETLK permite consultar o estado do bloqueio de um ficheiro ( o que � in�til para as aplica��es correntes). O terceiro argumento � um ponteiro para uma vari�vel do tipo struct flock, a descrever o bloqueio. Os membros importantes da estrutura flock s�o os seguintes:

Nome Tipo Significado
l_type int Ac��o esperado : F_RDLCK (bloqueio para leitura), F_WRLCK (bloqueio para escrita) e F_UNLCK (libertar o bloqueio).
l_whence int l_start Origem do campo (normalmente SEEK_SET).
l_start off_t Posi��o do inicio do bloqueio (normalmente 0).
l_len off_t Tamanho do bloqueio, 0 para atingir o fim do ficheiro.

Podemos ver que o fcntl() pode bloquear partes limitadas do ficheiro, mas � capaz de mais em compara��o ao flock(). Observemos um pequeno programa que pede um bloqueio para ler os ficheiros em causa cujos nomes s�o dados como argumento e, espera que o utilizador prima a tecla Enter antes de terminar (libertando assim os bloqueios).

1    /* ex_03.c */
2    #include <fcntl.h>
3    #include <stdio.h>
4    #include <stdlib.h>
5    #include <sys/stat.h>
6    #include <sys/types.h>
7    #include <unistd.h>
8 
9    int
10   main (int argc, char * argv [])
11   {
12     int i;
13     int fd;
14     char buffer [2];
15     struct flock lock;
16
17     for (i = 1; i < argc; i ++) {
18       fd = open (argv [i], O_RDWR | O_CREAT, 0644);
19       if (fd < 0) {
20         fprintf (stderr, "Can't open %s\n", argv [i]);
21         exit(EXIT_FAILURE);
22       }
23       lock . l_type = F_WRLCK;
24       lock . l_whence = SEEK_SET;
25       lock . l_start = 0;
26       lock . l_len = 0;
27       if (fcntl (fd, F_SETLK, & lock) < 0) {
28         fprintf (stderr, "Can't lock %s\n", argv [i]);
29         exit(EXIT_FAILURE);
30       }
31     }
32     fprintf (stdout, "Press Enter to release the lock(s)\n");
33     fgets (buffer, 2, stdin);
34     exit(EXIT_SUCCESS);
35   }

Lan�amos este programa a partir de uma primeira consola, onde fica � espera:

$ cc -Wall ex_03.c -o ex_03
$ ./ex_03 myfile
Prima Enter para libertar os bloqueio(s)
De outro terminal...
    $ ./ex_03 myfile
    Can't lock myfile
    $
Premindo Enter na primeira consola, libertamos os bloqueios.

Com este mecanismo, pode-se prevenir das race conditions dos direct�rios e filas de impress�o, como � feito pelo dem�nio lpd, utilizando a flock() para bloquear o ficheiro /var/lock/subsys/lpd, permitindo assim que haja s� uma ocorr�ncia. Pode tamb�m administrar de um modo seguro o acesso a um ficheiro de sistema como /etc/passwd, bloqueando com o fcntl() a partir da biblioteca pam ao alterar os dados de utilizador.

Contudo isto s� protege de interfer�ncias com aplica��es bem comportadas, ou seja, pedir ao kernel para reservar o pr�prio acesso antes de ler ou escrever para um ficheiro importante do sistema. Falamos de bloqueios cooperativos, o que mostra os ricos dos acessos aos dados. Infelizmente um programa mal escrito � capaz de substituir o conte�do de um ficheiro, mesmo apesar do bloqueio para escrita de um processo bem comportado. Aqui est� um exemplo. Escrevemos algumas letras para um ficheiro e bloqueamo-lo utilizando o programa anterior.

$ echo "FIRST" > myfile
$ ./ex_03 myfile
Press Enter to release the lock(s)
De uma outra consola. podemos alterar o ficheiro :
    $ echo "SECOND" > myfile
    $
Voltando � primeira consola, verificamos os "estragos" :
(Enter)
$ cat myfile
SECOND
$ 

Para resolver este problema o kernel do Linux fornece ao administrador de sistema um mecanismo de bloqueio proveniente do sistema V. Assim s� o pode utilizar com o bloqueio do fcntl() e n�o com o flock(). O administrador pode dizer ao kernel que os bloqueios do fcntl() s�o restritos, utilizando uma combina��o particular de direitos de acesso. Assim de um processo bloqueia o ficheiro para escrita, um outro processo n�o ser� capaz de escrever nesse ficheiro (mesmo sendo root). A combina��o especial � a utiliza��o do bit Set-GID enquanto que o bit de execu��o � removido do grupo. Isto obt�m-se com o comando :

$ chmod g+s-x myfile
$
Mas isto n�o � suficiente. Para um ficheiro beneficiar automaticamente de bloqueios cooperativos restritos, o atributo mandatory deve ser activado na parti��o onde pode ser encontrado. Normalmente, precisa de alterar o ficheiro /etc/fstab e adicionar a op��o mand na 4� coluna, ou digitando o comando :
# mount
/dev/hda5 on / type ext2 (rw)
[...]
# mount / -o remount,mand
# mount
/dev/hda5 on / type ext2 (rw,mand)
[...]
#
Agora, a partir de outra consola podemos verificar que � imposs�vel :
$ ./ex_03 myfile
Press Enter to release the lock(s)
De outro terminal :
    $ echo "THIRD" > myfile
    bash: myfile: Resource temporarily not available
    $  
Regressando � primeira consola :
(Enter)
$ cat myfile
SECOND
$

O administrador decidiu e n�o o programador, fazer bloqueios de ficheiros restritos (por exemplo /etc/passwd, ou /etc/shadow). O Programador tem de controlar o modo como os dados s�o acedidos, o que assegura que a aplica��o trabalha com dados coerentes quando l� e n�o � perigoso para outros processos quando escreve, desde que o ambiente esteja devidamente administrado.

Ficheiros Tempor�rios

Muito Frequentemente um programa precisa de armazenar dados temporariamente num ficheiro externo. O caso mais usual � quando se insere um registo no meio de um ficheiro sequencial ordenado, o que implica que seja feita uma c�pia do ficheiro original para um tempor�rio, enquanto se adiciona a informa��o. A seguir a chamada ao sistema unlink() remove o ficheiro original e o rename() renomeia o ficheiro tempor�rio para substituir o anterior.

Abrir um ficheiro tempor�rio, se n�o for feito devidamente �, frequentemente, o ponto de partida para situa��es de race conditions para utilizadores mal intencionados. Falhas de seguran�a, baseadas em ficheiros tempor�rios foram recentemente descobertas em aplica��es como o Apache, Linuxconf, getty_ps, wu-ftpd, rdist, gpm, inn, etc. Lembremos alguns princ�pios para evitar este tipo de problemas.

Normalmente, a cria��o de ficheiros tempor�rios � feita na directoria /tmp. Isto permite ao administrador de sistema saber onde a informa��o mais recente � guardada. E al�m disso � poss�vel o programar uma limpeza peri�dica (utilizando o cron), a utiliza��o de uma parti��o formatada independente no arranque, etc. Normalmente o administrador define a localiza��o reservada para ficheiros tempor�rios nos ficheiros <paths.h> e <stdio.h>, e nas constantes simb�licas _PATH_TMP e P_tmpdir. De facto, utilizar um outro direct�rio por omiss�o diferente do /tmp n�o � assim t�o bom, visto que obriga � compila��o de toda a aplica��o incluindo a biblioteca C. Contudo mencionemos que o comportamento da rotina da GlibC pode ser definida utilizando a vari�vel de ambiente TMPDIR. Assim o utilizador pede que os ficheiros tempor�rios sejam guardados num direct�rio da sua perten�a em vez do /tmp. Isto por vezes � obrigat�rio quando a parti��o dedicada /tmp � demasiado pequena para aplica��es que requerem enorme espa�o de armazenamento.

O direct�rio de sistema /tmp � algo especial dados os seus direitos de acesso :

$ ls -ld /tmp
drwxrwxrwt 7 root  root    31744 Feb 14 09:47 /tmp
$ 

O Sticky-Bit representado pela letra t no fim ou o modo octal 01000, tem um significado particular quando aplicada a um direct�rio : s� o dono do direct�rio (root ), e o dono de um ficheiro que se encontra nesse direct�rio � que s�o capazes de apagar o ficheiro. O direct�rio tem um acesso de escrita total, permite que cada utilizador coloque l� ficheiros, tendo a certeza que est�o protegidos pelo menos at� � pr�xima limpeza feita pelo administrador do sistema.

Contudo, a utiliza��o de direct�rios tempor�rios de armazenamento pode causar alguns problemas. Comecemos com um caso trivial, um aplica��o Set-UID root que fala para um utilizador. Falemos de um programa transportador de mail, Se este processo recebe um sinal a pedir para terminar rapidamente, por exemplo o SIGTERM ou SIGQUIT durante um shutdown, ele pode tentar guardar no momento o mail escrito mas n�o enviado. Com velhas vers�es isto era feito em /tmp/dead.letter. Ent�o um utilizador s� tinha de criar (visto que � capaz de escrever no direct�rio /tmp) uma liga��o f�sica para /etc/passwd com o nome dead.letter para o agente de mail ( a correr sobre um root efectivo) escrever para este ficheiro o conte�do do mail ainda n�o acabado (acidentalmente contendo uma linha "root::1:99999:::::").

O primeiro problema com este comportamento � a natureza previs�vel do nome do ficheiro. Bastando observar uma s� vez para deduzir que tal aplica��o utilizar� o nome de /tmp/dead.letter. Ent�o primeiro passo � utilizar o nome definido para a inst�ncia do processo correcto. Existem v�rias fun��es de biblioteca capazes de fornecer um nome de ficheiro personalizado.

Suponhamos que temos tal fun��o que nos fornece um nome �nico para o nosso ficheiro tempor�rio. O software livre, estando dispon�vel com o c�digo fonte (o mesmo para a biblioteca C), o nome do ficheiro � tamb�m previs�vel apesar de dif�cil. Um atacante podia criar um link simb�lico para o nome fornecido pela biblioteca C. A nossa primeira reac��o � de verificar se o ficheiro existe antes de o abrir. Ingenuamente pod�amos escrever algo do g�nero:

  if ((fd = open (filename, O_RDWR)) != -1) {
    fprintf (stderr, "%s already exists\n", filename);
    exit(EXIT_FAILURE);
  }
  fd = open (filename, O_RDWR | O_CREAT, 0644);
  ...

Obviamente que estamos num caso t�pico de race condition, onde a falha de seguran�a reside no primeiro open() segundo possibilitando a um utilizador ter sucesso ao criar um link para /etc/passwd. Estas duas opera��es t�m de ser feitas num modo at�mico, que nenhuma manipula��o seja poss�vel entre elas. Isto � poss�vel utilizando uma op��o espec�fica da chamada de sistema open(). chamada com O_EXCL, e usada em conjunto com O_CREAT, esta op��o faz com que o open() falhe se o ficheiro j� existir, mas a verifica��o da exist�ncia est� ligada atomicamente � cria��o.

Al�m disso, a extens�o Gnu 'x' para os modos de abertura da fun��o fopen(), requer uma cria��o de ficheiro exclusiva, falhando no caso de existir:

  FILE * fp;

  if ((fp = fopen (filename, "r+x")) == NULL) {
    perror ("Can't create the file.");
    exit (EXIT_FAILURE);
  }

As permiss�es dos ficheiros tempor�rios s�o bastante importantes tamb�m. Se tiver de escrever informa��o confidencial para um ficheiro no modo 644 (leitura/escrita para o dono e leitura para o resto do mundo) pode ser uma fonte de problemas. A

	#include <sys/types.h>
	#include <sys/stat.h>

        mode_t umask(mode_t mask);
fun��o permite fixar as permiss�es para um ficheiro na altura ca cria��o. Ent�o seguindo a chamada umask(077), o ficheiro ser� aberto no modo 600 (leitura/escrita para o dono, nenhum direitos para os restantes).

Normalmente, a cria��o de ficheiros tempor�rios � feita em tr�s passos:

  1. um nome de cria��o �nico (aleat�rio) ;
  2. abertura do ficheiro utilizando O_CREAT | O_EXCL, com permiss�es restritivas;
  3. verificar o resultado quando se abre o ficheiro e reagindo de acordo (quer tentando novamente ou abortando).

Como obter um ficheiro tempor�rio ? As

      #include <stdio.h>

      char *tmpnam(char *s);
      char *tempnam(const char *dir, const char *prefix);

fun��es retornam ponteiros para os nomes de ficheiros criados aleatoriamente.

A primeira fun��o aceita um argumento NULL, retornando de seguida o endere�o do buffer est�tico. O seu conte�do � alterado na pr�xima chamada tmpnam(NULL). Se o argumento � uma string alocada, o nome � copiado daqui, o que requer pelo menos L-tmpnam bytes. Tenha cuidado com buffer overflows ! A p�gina man informa-o acerca de problemas quando a fun��o � utilizada com um par�metro a NULL, se forem definidas _POSIX_THREADS ou _POSIX_THREAD_SAFE_FUNCTIONS.

A fun��o tempnam() retorna um ponteiro. O direct�rio dir deve ser "apropriado" (a p�gina man descreve o significado correcto de "suitable"). Esta fun��o verifica se o ficheiro existe antes de retornar o seu nome. Contudo, mais uma vez, a p�gina man n�o recomenda o seu uso, visto que "suitable" pode ter significados diferentes de acordo com as implementa��es da fun��o. Mencionemos que o Gnome recomenda o seu uso deste modo :

  char *filename;
  int fd;

  do {
    filename = tempnam (NULL, "foo");
    fd = open (filename, O_CREAT | O_EXCL | O_TRUNC | O_RDWR, 0600);
    free (filename);
  } while (fd == -1);
O ciclo utilizado aqui, reduz os riscos criando novos. O que � que aconteceria se a parti��o onde quer criar os ficheiros tempor�rios, estiver cheio ou sistema tivesse j� aberto o n�mero m�ximo de ficheiros dispon�veis...

A

       #include <stdio.h>

       FILE *tmpfile (void);
fun��o cria um nome de ficheiro �nico e abre-o. Este ficheiro � automaticamente apagado na altura em que � fechado.

Com a GlibC-2.1.3, este fun��o utiliza um mecanismo semelhante ao tmpnam() para gerar o nome do ficheiro e abrir o descritor correspondente. O ficheiro � depois apagado, mas o Linux remove-o, realmente, quando mais nenhum recurso o est� a utilizar, sendo ent�o o descritor do ficheiro liberto, utilizando a chamada ao sistema close().

  FILE * fp_tmp;

  if ((fp_tmp = tmpfile()) == NULL) {
    fprintf (stderr, "Can't create a temporary file\n");
    exit (EXIT_FAILURE);
  }

  /* ... use of the temporary file ... */

  fclose (fp_tmp);  /* real deletion from the system */

Os casos mais simples n�o requerem que o nome do ficheiro seja alterado, ou que seja feita a transmiss�o do mesmo para outro processo, mas somente o armazenamento e a re-leitura da informa��o na �rea tempor�ria. Assim n�o precisamos de saber o nome do ficheiro tempor�rio pois s� queremos aceder ao seu conte�do. A fun��o tmpfile() permite fazer isto.

A p�gina man nada diz, mas o Secure-Programs-HOWTO n�o o recomenda. Segundo o autor, as especif�ca��es n�o garantem a cria��o do ficheiro e ele n�o teve tempo de testar todas as suas implementa��es. Apesar disto esta fun��o � muito eficaz.

Por �ltimo, as

       #include <stdlib.h>

       char *mktemp(char *template);
       int mkstemp(char *template);
fun��es criam um nome �nico a partir do modelo de strings terminadas em "XXXXXX". Estes 'X's s�o substitu�dos para obter o nome do ficheiro �nico.

Segundo as vers�es, o mktemp() substitui os primeiros cinco 'X' pelo ID do Processo (PID) ... o que torna o nome f�cil de advinhar: s� o �ltimo 'X' � que aleat�rio. Algumas vers�es permitem mais do que seis 'X's.

A fun��o mkstemp() � recomendada no Secure-Programs-HOWTO. Eis aqui o m�todo :

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>

 void failure(msg) {
  fprintf(stderr, "%s\n", msg);
  exit(1);
 }

/*
 * Creates a temporary file and returns it.
 * This routine removes the filename from the filesystem thus 
 * it doesn't appear anymore when listing the directory.
 */
FILE *create_tempfile(char *temp_filename_pattern)
{
  int temp_fd;
  mode_t old_mode;
  FILE *temp_file;

  /* Create file with restrictive permissions */
  old_mode = umask(077);  
  temp_fd = mkstemp(temp_filename_pattern);
  (void) umask(old_mode);
  if (temp_fd == -1) {
    failure("Couldn't open temporary file");
  }
  if (!(temp_file = fdopen(temp_fd, "w+b"))) {
    failure("Couldn't create temporary file's file descriptor");
  }
  if (unlink(temp_filename_pattern) == -1) {
    failure("Couldn't unlink temporary file");
  }
  return temp_file;
}

Estas fun��es mostram os problemas acerca da abstrac��o e portabilidade. Ou seja, espera-se que as fun��es das bibliotecas standard providenciem aspectos (abstrac��o) ... mas o modo da sua implementa��o varia de sistema para sistema (portabilidade). Por exemplo, a fun��o tmpfile() abre um ficheiro tempor�rio de diferentes modos (algumas vers�es n�o usam O_EXCL), ou o mkstemp() que suporta um n�mero vari�vel de 'X' de acordo com as implementa��es

Conclus�o

Vo�mos sobre muitos problemas de seguran�a respeitantes �s race conditions de um mesmo recurso. Lembremos que nunca deve considerar duas opera��es sobre uma c�lula ligadas, a n�o ser que o kernel trate disso. Se as race conditions geram falhas de seguran�a, n�o deve neglicenciar as falhas assentes noutros recursos, como vari�veis comuns a threads diferentes, ou a segmentos de mem�ria partilhados a partir do shmget(). Os mecanismos de selec��o de acesso (sem�foros por exemplo) devem ser usados para evitar bugs dif�ceis de descobrir.

Liga��es