Saltar al contenido

¿Qué son Ring 0 y Ring 3 en el contexto de los sistemas operativos?

Este team de especialistas despúes de días de trabajo y de recopilar de datos, dieron con la respuesta, nuestro deseo es que te sea de utilidad para tu trabajo.

Solución:

Descripción general del uso del anillo de Linux x86

Comprender cómo se usan los anillos en Linux le dará una buena idea de para qué están diseñados.

En el modo protegido x86, la CPU siempre está en uno de los 4 anillos. El kernel de Linux solo usa 0 y 3:

  • 0 para kernel
  • 3 para usuarios

Esta es la definición más dura y rápida de kernel vs userland.

Por qué Linux no usa los anillos 1 y 2: Anillos de privilegios de la CPU: ¿Por qué no se usan los anillos 1 y 2?

¿Cómo se determina el anillo actual?

El anillo actual se selecciona mediante una combinación de:

  • tabla de descriptor global: una tabla en memoria de entradas GDT, y cada entrada tiene un campo Privl que codifica el anillo.

    La instrucción LGDT establece la dirección en la tabla de descriptores actual.

    Véase también: http://wiki.osdev.org/Global_Descriptor_Table

  • el segmento registra CS, DS, etc., que apuntan al índice de una entrada en el GDT.

    Por ejemplo, CS = 0 significa que la primera entrada del GDT está actualmente activa para el código de ejecución.

¿Qué puede hacer cada anillo?

El chip de la CPU está construido físicamente para que:

  • ring 0 puede hacer cualquier cosa

  • el anillo 3 no puede ejecutar varias instrucciones y escribir en varios registros, en particular:

    • no puede cambiar su propio anillo! De lo contrario, podría configurarse para que suene 0 y los anillos serían inútiles.

      En otras palabras, no se puede modificar el descriptor de segmento actual, que determina el anillo actual.

    • no se pueden modificar las tablas de páginas: ¿Cómo funciona la paginación x86?

      En otras palabras, no se puede modificar el registro CR3 y la propia paginación evita la modificación de las tablas de la página.

      Esto evita que un proceso vea la memoria de otros procesos por razones de seguridad / facilidad de programación.

    • no se pueden registrar controladores de interrupciones. Estos se configuran escribiendo en ubicaciones de memoria, lo que también se evita mediante la paginación.

      Los controladores se ejecutan en el anillo 0 y romperían el modelo de seguridad.

      En otras palabras, no puede usar las instrucciones LGDT y LIDT.

    • no puedo hacer instrucciones IO como in y out, y así tener accesos arbitrarios al hardware.

      De lo contrario, por ejemplo, los permisos de archivo serían inútiles si cualquier programa pudiera leer directamente desde el disco.

      Más precisamente gracias a Michael Petch: en realidad es posible que el sistema operativo permita instrucciones IO en el anillo 3, esto en realidad está controlado por el segmento de estado de la tarea.

      Lo que no es posible es que el anillo 3 se dé permiso para hacerlo si no lo tenía en primer lugar.

      Linux siempre lo rechaza. Consulte también: ¿Por qué Linux no usa el cambio de contexto de hardware a través del TSS?

¿Cómo cambian los programas y los sistemas operativos entre anillos?

  • cuando la CPU está encendida, comienza a ejecutar el programa inicial en el anillo 0 (bueno, pero es una buena aproximación). Puede pensar que este programa inicial es el kernel (pero normalmente es un gestor de arranque que luego llama al kernel aún en el anillo 0).

  • cuando un proceso de usuario quiere que el kernel haga algo por él, como escribir en un archivo, usa una instrucción que genera una interrupción como int 0x80 o syscall para señalar el kernel. x86-64 Linux syscall hello world ejemplo:

    .data hello_world: .ascii “hola mundo n” hello_world_len =. – hello_world .text .global _start _start: / * escribir * / mov $ 1,% rax mov $ 1,% rdi mov $ hello_world,% rsi mov $ hello_world_len,% rdx syscall

       /* exit */
       mov $60, %rax
       mov $0, %rdi
       syscall
    
compile and run:
    
    as -o hello_world.o hello_world.S
    ld -o hello_world.out hello_world.o
    ./hello_world.out

 [GitHub upstream](https://github.com/cirosantilli/x86-assembly-cheat/blob/d221ebec3a87ad811bbab48c4073e0bb2cc0df58/x86-64/gas/linux/hello_world.S).

 When this happens, the CPU calls an interrupt callback handler which the kernel registered at boot time. Here is a [concrete baremetal example that registers a handler and uses it](https://github.com/cirosantilli/x86-bare-metal-examples/blob/18772b1403133b2328d5ad44791445f9859de320/interrupt.S).

 This handler runs in ring 0, which decides if the kernel will allow this action, do the action, and restart the userland program in ring 3. x86_64 
  • cuando el exec se utiliza la llamada al sistema (o cuándo se iniciará el kernel /init), el kernel prepara los registros y la memoria del nuevo proceso de área de usuario, luego salta al punto de entrada y cambia la CPU al anillo 3

  • Si el programa intenta hacer algo malo como escribir en un registro prohibido o en una dirección de memoria (debido a la paginación), la CPU también llama a algún controlador de devolución de llamada del kernel en el anillo 0.

    Pero dado que el espacio de usuario era malo, el kernel podría matar el proceso esta vez, o darle una advertencia con una señal.

  • Cuando el kernel arranca, configura un reloj de hardware con una frecuencia fija, que genera interrupciones periódicamente.

    Este reloj de hardware genera interrupciones que ejecutan el anillo 0 y le permiten programar qué procesos de usuario deben despertarse.

    De esta manera, la programación puede ocurrir incluso si los procesos no están realizando ninguna llamada al sistema.

¿Cuál es el punto de tener múltiples anillos?

Hay dos ventajas principales de separar kernel y userland:

  • Es más fácil crear programas, ya que está más seguro de que uno no interferirá con el otro. Por ejemplo, un proceso de área de usuario no tiene que preocuparse por sobrescribir la memoria de otro programa debido a la paginación, ni por poner hardware en un estado no válido para otro proceso.
  • es más seguro. Por ejemplo, los permisos de archivos y la separación de la memoria podrían evitar que una aplicación de piratería lea sus datos bancarios. Esto supone, por supuesto, que confía en el kernel.

¿Cómo jugar con él?

He creado una configuración básica que debería ser una buena forma de manipular anillos directamente: https://github.com/cirosantilli/x86-bare-metal-examples

Desafortunadamente, no tuve la paciencia para hacer un ejemplo de zona de usuario, pero fui tan lejos como la configuración de la paginación, por lo que la zona de usuario debería ser factible. Me encantaría ver una solicitud de extracción.

Alternativamente, los módulos del kernel de Linux se ejecutan en el anillo 0, por lo que puede usarlos para probar operaciones privilegiadas, por ejemplo, leer los registros de control: ¿Cómo acceder a los registros de control cr0, cr2, cr3 desde un programa? Obteniendo falla de segmentación

Aquí hay una configuración conveniente de QEMU + Buildroot para probarlo sin matar a su host.

La desventaja de los módulos del kernel es que se están ejecutando otros kthreads y podrían interferir con sus experimentos. Pero, en teoría, puede hacerse cargo de todos los controladores de interrupciones con su módulo del kernel y ser dueño del sistema, ese sería un proyecto interesante en realidad.

Anillos negativos

Si bien no se hace referencia a los anillos negativos en el manual de Intel, en realidad hay modos de CPU que tienen más capacidades que el anillo 0 en sí, por lo que se ajustan bien al nombre de “anillo negativo”.

Un ejemplo es el modo de hipervisor utilizado en la virtualización.

Para obtener más detalles, consulte:

  • https://security.stackexchange.com/questions/129098/what-is-protection-ring-1
  • https://security.stackexchange.com/questions/216527/ring-3-exploits-and-existence-of-other-rings

BRAZO

En ARM, los anillos se denominan niveles de excepción, pero las ideas principales siguen siendo las mismas.

Existen 4 niveles de excepción en ARMv8, comúnmente usados ​​como:

  • EL0: tierra de usuarios

  • EL1: kernel (“supervisor” en terminología ARM).

    Entrado con el svc instrucción (SuperVisor Call), anteriormente conocida como swi antes del ensamblaje unificado, que es la instrucción utilizada para realizar llamadas al sistema Linux. Ejemplo de Hello world ARMv8:

    hola. S

    .text
    .global _start
    _start:
        /* write */
        mov x0, 1
        ldr x1, =msg
        ldr x2, =len
        mov x8, 64
        svc 0
    
        /* exit */
        mov x0, 0
        mov x8, 93
        svc 0
    msg:
        .ascii "hello syscall v8n"
    len = . - msg
    

    GitHub en sentido ascendente.

    Pruébelo con QEMU en Ubuntu 16.04:

    sudo apt-get install qemu-user gcc-arm-linux-gnueabihf
    arm-linux-gnueabihf-as -o hello.o hello.S
    arm-linux-gnueabihf-ld -o hello hello.o
    qemu-arm hello
    

    Aquí hay un ejemplo concreto de baremetal que registra un controlador SVC y realiza una llamada SVC.

  • EL2: hipervisores, por ejemplo Xen.

    Entrado con el hvc instrucción (llamada de HyperVisor).

    Un hipervisor es para un sistema operativo, lo que un sistema operativo es para el espacio de usuario.

    Por ejemplo, Xen le permite ejecutar varios sistemas operativos, como Linux o Windows, en el mismo sistema al mismo tiempo, y aísla los sistemas operativos entre sí para mayor seguridad y facilidad de depuración, tal como lo hace Linux para los programas de usuario.

    Los hipervisores son un key parte de la infraestructura de nube actual: permiten que varios servidores se ejecuten en un solo hardware, manteniendo el uso del hardware siempre cerca del 100% y ahorrando mucho dinero.

    AWS, por ejemplo, usó Xen hasta 2017, cuando su cambio a KVM fue noticia.

  • EL3: otro nivel más. Ejemplo de TODO.

    Entrado con el smc instrucción (llamada en modo seguro)

El modelo de referencia de arquitectura ARMv8 DDI 0487C.a – Capítulo D1 – Modelo del programador a nivel de sistema AArch64 – La figura D1-1 ilustra esto de manera hermosa:

ingrese la descripción de la imagen aquí

La situación de ARM cambió un poco con la llegada de ARMv8.1 Virtualization Host Extensions (VHE). Esta extensión permite que el kernel se ejecute en EL2 de manera eficiente:

ingrese la descripción de la imagen aquí

VHE se creó porque las soluciones de virtualización en el kernel de Linux, como KVM, han ganado terreno sobre Xen (consulte, por ejemplo, el cambio de AWS a KVM mencionado anteriormente), porque la mayoría de los clientes solo necesitan máquinas virtuales Linux y, como puede imaginar, estar todo en una sola proyecto, KVM es más simple y potencialmente más eficiente que Xen. Así que ahora el kernel de Linux del host actúa como hipervisor en esos casos.

En la imagen podemos ver que cuando el bit E2H de registro HCR_EL2 es igual a 1, entonces VHE está habilitado y:

  • el kernel de Linux se ejecuta en EL2 en lugar de EL1
  • cuando HCR_EL2.TGE == 1, somos un programa de usuario de host habitual. Utilizando sudo puede destruir el anfitrión como de costumbre.
  • cuando HCR_EL2.TGE == 0 somos un sistema operativo invitado (por ejemplo, cuando ejecuta un sistema operativo Ubuntu dentro de QEMU KVM dentro del host Ubuntu. sudo no puede destruir el host a menos que haya un error en el kernel de QEMU / host.

Observe cómo ARM, quizás debido al beneficio de la retrospectiva, tiene una mejor convención de nomenclatura para los niveles de privilegios que x86, sin la necesidad de niveles negativos: 0 es el más bajo y 3 el más alto. Los niveles superiores tienden a crearse con más frecuencia que los inferiores.

El EL actual se puede consultar con el MRS instrucción: ¿cuál es el modo de ejecución actual / nivel de excepción, etc.?

ARM no requiere que estén presentes todos los niveles de excepción para permitir implementaciones que no necesitan la función para ahorrar área de chip. ARMv8 “Niveles de excepción” dice:

Es posible que una implementación no incluya todos los niveles de excepción. Todas las implementaciones deben incluir EL0 y EL1. EL2 y EL3 son opcionales.

QEMU, por ejemplo, tiene como valor predeterminado EL1, pero EL2 y EL3 se pueden habilitar con las opciones de línea de comando: qemu-system-aarch64 ingresando el1 al emular el encendido de a53

Fragmentos de código probados en Ubuntu 18.10.

Los procesadores Intel (x86 y otros) permiten aplicaciones con poderes limitados. Para restringir (proteger) recursos críticos como IO, memoria, puertos, etc., la CPU en enlace con el sistema operativo (Windows en este caso) proporciona niveles de privilegios (0 es el mayor privilegio y 3 es el menor) que se asignan al modo kernel y al modo de usuario respectivamente.

Entonces, el sistema operativo se ejecuta código del núcleo en el anillo 0 – nivel de privilegio más alto (de 0) proporcionado por la CPU – y código de usuario en el anillo 3.

Para obtener más detalles, consulte http://duartes.org/gustavo/blog/post/cpu-rings-privilege-and-protection/

valoraciones y comentarios

Si te sientes a gusto, tienes la habilidad dejar una reseña acerca de qué te ha parecido esta división.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *