En pocas palabras sobre los privilegios de Linux (capacidades)

Se preparó una traducción del artículo específicamente para estudiantes del curso de Administrador de Linux .


Las capacidades se utilizan cada vez más gracias en gran parte a SystemD, Docker y orquestadores como Kubernetes. Pero, como me parece, la documentación es un poco difícil de entender y algunas partes de la implementación de privilegios resultaron ser algo confusas para mí, así que decidí compartir mi conocimiento actual en este breve artículo.



El enlace de privilegio más importante es la página de manual de capacidades (7) . Pero ella no es muy adecuada para una relación inicial.

Capacidades de proceso


Los derechos de los usuarios comunes son muy limitados, mientras que los derechos del usuario "root" son muy amplios. Aunque los procesos que se ejecutan como "root" a menudo no requieren todos los privilegios de root.

Para reducir los privilegios de usuario root, los permisos POSIX (capacidades POSIX) proporcionan una forma de limitar los grupos de operaciones privilegiadas del sistema que un proceso y sus descendientes pueden realizar. En esencia, dividen todos los derechos de "raíz" en un conjunto de privilegios separados. La idea de capacidades se describió en 1997 en un borrador de POSIX 1003.1e.

En Linux, cada proceso (tarea) tiene cinco números (conjuntos) de 64 bits que contienen bits de permiso (antes de Linux 2.6.25 eran de 32 bits), que se pueden ver en
  / proc / <pid> / status 
.

CapInh: 00000000000004c0 CapPrm: 00000000000004c0 CapEff: 00000000000004c0 CapBnd: 00000000000004c0 CapAmb: 0000000000000000 

Estos números (que se muestran aquí en notación hexadecimal) son mapas de bits en los que se representan conjuntos de permisos. Aquí están sus nombres completos:

  • Heredable : permisos que los descendientes pueden heredar
  • Permitido : permisos que puede utilizar la tarea.
  • Efectivo : permisos efectivos actuales
  • Límite : antes de Linux 2.6.25, el conjunto delimitador era un atributo de todo el sistema común a todos los subprocesos, diseñado para describir un conjunto más allá del cual los permisos no podían expandirse. Este es actualmente un conjunto para cada tarea y es solo una parte de la lógica ejecutiva, detalles a continuación.
  • Ambient (externo desde Linux 4.3): agregado para facilitar el proporcionar permisos no root al usuario, sin usar setuid o permisos de archivo (más sobre eso más adelante).

Si la tarea solicita una operación privilegiada (por ejemplo, enlace a puertos <1024), el núcleo verifica el conjunto de límites actual para CAP_NET_BIND_SERVICE . Si está instalado, la operación continúa. De lo contrario, la operación se rechaza con EPERM (operación no permitida). Estos CAP_ en el código fuente del núcleo y están numerados secuencialmente, por lo que CAP_NET_BIND_SERVICE , igual a 10, significa bit 1 << 10 = 0x400 (este es el dígito hexadecimal "4" en mi ejemplo anterior).

Se puede encontrar una lista completa de privilegios legibles por humanos actualmente definidos en la página del manual de capacidades actuales (7) (la lista aquí es solo para referencia).

Además, hay una biblioteca libcap para simplificar las verificaciones de administración y autorización. Además de la API de la biblioteca , el paquete incluye la utilidad capsh , que, entre otras cosas, le permite mostrar su autoridad.

 # capsh --print Current: = cap_setgid,cap_setuid,cap_net_bind_service+eip Bounding set = cap_setgid,cap_setuid,cap_net_bind_service Ambient set = Securebits: 00/0x0/1'b0 secure-noroot: no (unlocked) secure-no-suid-fixup: no (unlocked) secure-keep-caps: no (unlocked) secure-no-ambient-raise: no (unlocked) uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video) 

Aquí hay algunos puntos confusos:

  • Actual : muestra los privilegios efectivos, heredados y disponibles del proceso de capsh en el formato cap_to_text (3) . En este formato, los derechos se enumeran como grupos de permisos “capability[,capability…]+(e|i|p)” , donde “e” significa efectivo, “i” heredado y “p” disponible. La lista no está separada por el símbolo “,” , como habrás adivinado (cap_setgid+eip, cap_setuid+eip) . Una coma divide los permisos en un grupo de acción. La lista real de grupos de acción se separa por espacios. Otro ejemplo con dos grupos de acción sería “= cap_sys_chroot+ep cap_net_bind_service+eip” . Y también los siguientes dos grupos de acciones “= cap_net_bind_service+e cap_net_bind_service+ip” codificarán el mismo valor que un “cap_net_bind_service+eip” .
  • Conjunto delimitador / conjunto ambiental . Para confundir aún más, estas dos líneas contienen solo una lista de permisos definidos en estos conjuntos, separados por espacios. El formato cap_to_text no se usa aquí, porque no contiene conjuntos de permisos disponibles, efectivos y heredados, sino solo un conjunto (límite / ambiente).
  • Securebits : muestra los indicadores de Securebits de la tarea en formato decimal / hexadecimal / en formato Verilog (sí, todos lo esperan aquí, y esto está perfectamente claro desde el punto de vista de que cada administrador del sistema programa su propio FPGA y ASIC ). El siguiente es el estado de los bits seguros. Los indicadores reales se definen como SECBIT_* en securebits.h , y también se describen en las capacidades (7) .
  • Esta utilidad carece de la visualización de la información "NoNewPrivs" , que se puede ver en
      / proc / <pid> / status 
    . Se menciona solo en prctl (2), aunque afecta directamente a los derechos cuando se usa junto con los permisos de archivo (con más detalle a continuación). NoNewPrivs se describe de la siguiente manera: “Con no_new_privs en 1, execve (2) promete no otorgar privilegios a lo que no se podría haber hecho sin llamar a execve (2) (por ejemplo, procesando la set-user-ID set-group-ID , set-group-ID bits del set-group-ID y deshabilitar el procesamiento de permisos de archivos) . Después de la instalación, el atributo no_new_privs no se puede restablecer. El valor de este atributo lo heredan los descendientes creados a través de fork (2) y clone (2) y almacenados a través de execve (2) ". Kubernetes establece este indicador en 1 cuando allowPrivilegeEscalation es falso en el pod securityContext.


Al iniciar un nuevo proceso a través de execve (2), los permisos para el proceso secundario se convierten utilizando la fórmula especificada en las capacidades (7) :

 P'(ambient) = (file is privileged) ? 0 : P(ambient) P'(permitted) = (P(inheritable) & F(inheritable)) | (F(permitted) & P(bounding)) | P'(ambient) P'(effective) = F(effective) ? P'(permitted) : P'(ambient) P'(inheritable) = P(inheritable) [ie, unchanged] P'(bounding) = P(bounding) [ie, unchanged] where: P() denotes the value of a thread capability set before the execve(2) -      execve(2) P'() denotes the value of a thread capability set after the execve(2) -      execve(2) F() denotes a file capability set -   


Estas reglas describen las acciones realizadas para cada bit en todos los conjuntos de permisos (ambiente / permitido / efectivo / heredable / límite). Se utiliza la sintaxis estándar de C (& - para AND lógico, | - para OR lógico). P 'es un proceso hijo. P es el proceso actual que llama a execve (2). F son los llamados "permisos de archivo" de un archivo lanzado a través de execve.

Además, un proceso puede cambiar mediante programación sus conjuntos heredados, accesibles y eficientes con libcap en cualquier momento de acuerdo con las siguientes reglas:

  • Si la persona que llama no tiene CAP_SETPCAP , el nuevo conjunto heredado debe ser un subconjunto de P (heredado) y P (disponible)
  • (con Linux 2.6.25) El nuevo conjunto heredado debe ser un subconjunto de P (heredado) y P (limitante)
  • El nuevo conjunto disponible debe ser un subconjunto de P (disponible)
  • El nuevo conjunto eficiente debe ser un subconjunto de P (efectivo)


Permisos de archivo


A veces, un usuario con un conjunto limitado de derechos necesita ejecutar un archivo que requiere más privilegios. Anteriormente, esto se logró estableciendo el bit setuid ( chmod + s ./executable ) en un archivo binario. Dicho archivo, si pertenece a la raíz, tendrá todos los derechos de raíz cuando lo ejecute cualquier usuario.

Pero este mecanismo otorga demasiados privilegios a un archivo, por lo que los permisos POSIX han implementado un concepto llamado "permisos de archivo". Se almacenan como un atributo de archivo extendido llamado "security.capability", por lo que necesita un sistema de archivos con soporte para atributos extendidos (ext *, XFS, Raiserfs, Brtfs, overlay2, ...). Para cambiar este atributo, se CAP_SETFCAP permiso CAP_SETFCAP (en el conjunto disponible de permisos de proceso).

 $ getfattr -m - -d `which ping` # file: usr/bin/ping security.capability=0sAQAAAgAgAAAAAAAAAAAAAAAAAAA= $ getcap `which ping` /usr/bin/ping = cap_net_raw+ep 


Casos especiales y comentarios


Por supuesto, en realidad, no todo es tan simple, y hay varios casos especiales descritos en la página de manual de capacidades (7) . Probablemente los más importantes de ellos son:

  • El bit setuid y los permisos de archivo se ignoran si NoNewPrivs está instalado o si el sistema de archivos está montado con nosuid o si ptrace rastrea el proceso que llama a execve. Los permisos de archivo también se ignoran cuando el núcleo arranca con la opción no_file_caps .
  • Un archivo "tonto" (capacidad-tonto) es un archivo binario convertido de un archivo setuid a un archivo con permisos de archivo, pero sin cambiar su código fuente. Dichos archivos a menudo se obtienen estableciendo permisos + ep en ellos, por ejemplo, “setcap cap_net_bind_service+ep ./binary” . La parte importante es "e" - efectiva. Después de execve, estos permisos se agregarán tanto a los disponibles como a los existentes, por lo que el ejecutable estará listo para usar la operación privilegiada. Por el contrario, un archivo de "capacidad inteligente" que usa libcap o una funcionalidad similar puede usar cap_set_proc (3) (o capset ) para establecer los bits "efectivos" o "heredados" en cualquier momento si ese permiso ya está en " asequible ". Por lo tanto, " setcap cap_net_bind_service+p ./binary” será suficiente para un archivo "inteligente", ya que podrá establecer los permisos necesarios en un conjunto efectivo antes de invocar una operación privilegiada. Ver código de muestra .
  • Los archivos con setuid-root continúan funcionando, otorgando todos los privilegios de root cuando el usuario comienza como no root. Pero si tienen permisos de archivo establecidos, solo se les otorgarán. También puede crear un archivo setuid con un conjunto vacío de permisos, lo que hará que se ejecute como un usuario con UID 0 sin ningún permiso. Hay casos especiales para el usuario root cuando ejecuta un archivo con setuid-root y configura varios indicadores de securebits (consulte man).
  • Un conjunto delimitador enmascara los permisos disponibles, pero no los heredados. Recuerde P '(disponible) = F (disponible) y P (limitante). Si una secuencia tiene un permiso en su conjunto heredado que no está en su conjunto delimitador, aún puede obtener este permiso en su conjunto disponible ejecutando un archivo que tenga permiso en su conjunto heredado - P '(disponible) = P ( heredado) y F (heredado).
  • La ejecución de un programa que cambia el UID o GID a través de los bits set-user-ID, set-group-ID, o la ejecución de un programa para el cual se establecen permisos de archivo, borrará el conjunto ambiente . Los permisos se agregan al conjunto circundante mediante PR_CAP_AMBIENT prctl . Estos permisos ya deberían estar presentes en los conjuntos de procesos heredados y accesibles .
  • Si un proceso con un UID distinto de 0 ejecuta execve (2) , se eliminarán todos los derechos en sus conjuntos disponibles y activos.
  • Si SECBIT_KEEP_CAPS (o el SECBIT_NO_SETUID_FIXUP más SECBIT_NO_SETUID_FIXUP ) no está configurado, y cambiar el UID de 0 a distinto de cero elimina todos los permisos de los conjuntos heredados, accesibles y efectivos .


Entonces ...


Si el contenedor oficial de nginx, ingress-nginx o el suyo se detiene o reinicia con un error:

bind() to 0.0.0.0:80 failed (13: Permission denied)

... esto significa que hubo un intento de escuchar en el puerto 80 como un usuario sin privilegios (no 0), y no había CAP_NET_BIND_SERVICE en el CAP_NET_BIND_SERVICE permisos actual. Para obtener estos derechos, debe usar xattr y set (usando setcap ) para el permiso del archivo nginx al menos cap_net_bind_service+ie . Este permiso de archivo se combinará con el conjunto heredado (especificado con el conjunto delimitador de pod SecurityContext / capacidad / add / NET_BIND_SERVICE), y también se colocará en el conjunto de permisos disponibles. El resultado es cap_net_bind_service+pie .

Todo esto funciona siempre que securityContext / allowPrivilegeEscalation esté establecido en true y el controlador de almacenamiento docker / rkt (consulte la documentación de docker) sea compatible con xattrs.

Si nginx fuera inteligente con respecto a los permisos, entonces cap_net_bind_service+i sería suficiente. Entonces podría usar libcap para expandir los derechos del conjunto disponible a efectivo. Habiendo recibido como resultado cap_net_bind_service+pie .

Además de usar xattr, la única forma de obtener cap_net_bind_service en un contenedor no root es dejar que Docker establezca sus capacidades externas (capacidades ambientales). Pero a partir de abril de 2019, esto aún no se ha implementado .

Ejemplos de código


Aquí hay un código de muestra que usa libcap para agregar CAP_NET_BIND_SERVICE a un conjunto de permisos eficiente. Requiere el permiso CAP_BIND_SERVICE+p para el archivo binario.

Referencias (ing.):

Source: https://habr.com/ru/post/471802/


All Articles