
Workshop de escalada local de privilégios Looney Tunables (CVE-2023-4911)
Looney Tunables: workshop de escalada de privilégio local (CVE-2023-4911) (apenas para fins educacionais)
Em computação, um vinculador dinâmico é a parte de um sistema operacional que carrega e vincula as bibliotecas compartilhadas necessárias para um executável quando ele é executado, copiando o conteúdo das bibliotecas do armazenamento persistente para a memória RAM, preenchendo tabelas de desvio e realocando ponteiros.
Por exemplo, temos um programa que usa a biblioteca openssl para calcular o hash md5:``` $ head md5_hash.c #include <stdio.h> #include <string.h> #include <openssl/md5.h>
ld.so analisa o binário e tenta encontrar a biblioteca relacionada a <openssl/md5.h>```
$ ldd md5_hash
linux-vdso.so.1 (0x00007fffa530b000)
libcrypto.so.3 => /lib/x86_64-linux-gnu/libcrypto.so.3 (0x00007f19cda00000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f19cd81e000)
/lib64/ld-linux-x86-64.so.2 (0x00007f19ce032000)
Como podemos ver, ela encontra a biblioteca de criptografia necessária em /lib/x86_64-linux-gnu/libcrypto.so.3 Durante a inicialização do programa, ela coloca o código dessa biblioteca na RAM do processo e vincula todas as referências a essa biblioteca.
Quando um programa é iniciado, este carregador primeiro examina o programa para determinar as bibliotecas compartilhadas de que necessita. Em seguida, ele procura por essas bibliotecas, carrega-as na memória e as vincula ao executável em tempo de execução. No processo, o carregador dinâmico resolve referências de símbolos, como referências a funções e variáveis, garantindo que tudo esteja pronto para a execução do programa. Devido ao seu papel, o carregador dinâmico é altamente sensível à segurança, pois seu código é executado com privilégios elevados quando um usuário local executa um programa set-user-ID ou set-group-ID.
Tunables são um recurso da GNU C Library que permite que autores de aplicativos e mantenedores de distribuições alterem o comportamento da biblioteca em tempo de execução para corresponder à sua carga de trabalho. Eles são implementados como um conjunto de interruptores que podem ser modificados de diferentes maneiras. O método padrão atual para fazer isso é por meio da variável de ambiente GLIBC_TUNABLES, definindo-a como uma string de pares name=value separados por dois-pontos. Por exemplo, o exemplo a seguir ativa a verificação de malloc e define o limite de trim do malloc para 128 bytes:``` GLIBC_TUNABLES=glibc.malloc.trim_threshold=128:glibc.malloc.check=3 export GLIBC_TUNABLES
Passar `--list-tunables` ao carregador dinâmico para imprimir todos os tunables com valores mínimos e máximos:```
$ /lib64/ld-linux-x86-64.so.2 --list-tunables
glibc.rtld.nns: 0x4 (min: 0x1, max: 0x10)
glibc.elision.skip_lock_after_retries: 3 (min: 0, max: 2147483647)
glibc.malloc.trim_threshold: 0x0 (min: 0x0, max: 0xffffffffffffffff)
glibc.malloc.perturb: 0 (min: 0, max: 255)
glibc.cpu.x86_shared_cache_size: 0x100000 (min: 0x0, max: 0xffffffffffffffff)
glibc.pthread.rseq: 1 (min: 0, max: 1)
glibc.cpu.prefer_map_32bit_exec: 0 (min: 0, max: 1)
glibc.mem.tagging: 0 (min: 0, max: 255)