
Looney Tunables: taller de escalada de privilegios local (CVE-2023-4911)
Taller de escalada de privilegios local Looney Tunables (CVE-2023-4911) (solo con fines educativos)
En informática, un enlazador dinámico es la parte de un sistema operativo que carga y enlaza las bibliotecas compartidas necesarias para un ejecutable cuando este se ejecuta, copiando el contenido de las bibliotecas desde el almacenamiento persistente a la RAM, rellenando tablas de saltos y reubicando punteros.
Por ejemplo, tenemos un programa que utiliza la biblioteca openssl para calcular el hash md5:``` $ head md5_hash.c #include <stdio.h> #include <string.h> #include <openssl/md5.h>
ld.so analiza el binario e intenta encontrar la biblioteca relacionada con <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, encuentra la biblioteca criptográfica necesaria en /lib/x86_64-linux-gnu/libcrypto.so.3 Durante el inicio del programa, coloca el código de esta biblioteca en la RAM del proceso y enlaza todas las referencias a esta biblioteca.
Cuando se inicia un programa, este cargador primero examina el programa para determinar las bibliotecas compartidas que requiere. Luego busca estas bibliotecas, las carga en memoria y las enlaza con el ejecutable en tiempo de ejecución. En el proceso, el cargador dinámico resuelve las referencias a símbolos, como referencias a funciones y variables, asegurando que todo esté listo para la ejecución del programa. Dado su papel, el cargador dinámico es altamente sensible a la seguridad, ya que su código se ejecuta con privilegios elevados cuando un usuario local lanza un programa set-user-ID o set-group-ID.
Los Tunables son una característica de la Biblioteca C de GNU que permite a los autores de aplicaciones y a los mantenedores de distribuciones alterar el comportamiento de la biblioteca en tiempo de ejecución para ajustarse a su carga de trabajo. Estos se implementan como un conjunto de conmutadores que pueden modificarse de diferentes maneras. El método predeterminado actual para hacerlo es mediante la variable de entorno GLIBC_TUNABLES, estableciéndola en una cadena de pares nombre=valor separados por dos puntos. Por ejemplo, el siguiente ejemplo habilita la comprobación de malloc y establece el umbral de recorte de malloc en 128 bytes:``` GLIBC_TUNABLES=glibc.malloc.trim_threshold=128:glibc.malloc.check=3 export GLIBC_TUNABLES
Pasar `--list-tunables` al cargador dinámico para imprimir todos los tunables con valores mínimos y 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)