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
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.
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.
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 #
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:
nice -n 20
;
while (1);
);
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.
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.
�, 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.
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.
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:
O_CREAT | O_EXCL
, com
permiss�es restritivas;
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
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.