¿No permite que un programa de modo de usuario acceda a la memoria de espacio del kernel y ejecute las instrucciones IN y OUT frustra el propósito de tener modos de CPU?

19

Cuando la CPU está en modo de usuario, la CPU no puede ejecutar instrucciones privilegiadas y no puede acceder a la memoria de espacio del kernel.

Y cuando la CPU está en modo kernel, la CPU puede ejecutar todas las instrucciones y puede acceder a toda la memoria.

Ahora en Linux, un programa en modo de usuario puede acceder a toda la memoria (usando /dev/mem) y puede ejecutar las dos instrucciones privilegiadas INy OUT(usando iopl()creo).

Entonces, un programa de modo de usuario en Linux puede hacer la mayoría de las cosas (creo que la mayoría de las cosas) que se pueden hacer en modo kernel.

¿No permite que un programa de modo de usuario tenga toda esta potencia frustra el propósito de tener modos de CPU?

user341099
fuente

Respuestas:

23

Entonces, un programa de modo de usuario en Linux puede hacer la mayoría de las cosas (creo que la mayoría de las cosas) que se pueden hacer en modo kernel.

Bueno, no todos los programas en modo de usuario pueden, solo aquellos con los privilegios apropiados. Y eso lo determina el núcleo.

/dev/memestá protegido por los permisos de acceso habituales del sistema de archivos y la CAP_SYS_RAWIOcapacidad. iopl()y ioperm()también están restringidos a través de la misma capacidad.

/dev/memTambién se puede compilar del núcleo por completo ( CONFIG_DEVMEM).

¿No permite que un programa de modo de usuario tenga toda esta potencia frustra el propósito de tener modos de CPU?

Bien quizás. Depende de lo que desee que puedan hacer los procesos de espacio de usuario privilegiado. Los procesos de espacio de usuario también pueden destruir todo el disco duro si tienen acceso a/dev/sda (o equivalente), aunque eso anula el propósito de tener un controlador de sistema de archivos para manejar el acceso al almacenamiento.

(Luego también está el hecho de que iopl()funciona utilizando los modos de privilegio de CPU en i386, por lo que no se puede decir que anule su propósito).

ilkkachu
fuente
2
Incluso ioplno permite todas las instrucciones privilegiadas, por lo que sigue siendo útil para asegurarse de que un programa de espacio de usuario con errores no se ejecute accidentalmente invdsaltando a través de un puntero de función dañado que apunta a la memoria ejecutable que comienza con 0F 08bytes. Agregué una respuesta con algunas de las razones no relacionadas con la seguridad por las que es útil que los procesos de espacio de usuario eleven sus privilegios.
Peter Cordes
16

Solo de la misma manera que modprobe"derrota" la seguridad al cargar código nuevo en el núcleo.

Por varias razones, a veces tiene más sentido tener un código semi-privilegiado (como los controladores gráficos dentro del servidor X) ejecutándose en el espacio del usuario en lugar de un hilo del núcleo.

  • Siendo capaz de kill hacerlo más fácilmente, a menos que bloquee el HW.
  • Hacer que solicite la página de su código / datos de los archivos en el sistema de archivos. (La memoria del kernel no es paginable)
  • Dándole su propio espacio de dirección virtual donde los errores en el servidor X podrían colapsar el servidor X, sin derribar el núcleo.

No hace mucho por la seguridad, pero existen grandes ventajas de fiabilidad y arquitectura de software.

La incorporación de controladores de gráficos al núcleo podría reducir los cambios de contexto entre los clientes X y el servidor X, como un solo usuario-> núcleo-> usuario en lugar de tener que ingresar datos en otro proceso de espacio de uso, pero los servidores X históricamente son demasiado grandes y con errores. quererlos completamente en el núcleo.


Sí, el código malicioso con estos privs podría hacerse cargo del núcleo si así lo deseara, utilizando /dev/mempara modificar el código del núcleo.

O, por ejemplo, en x86, ejecute una cliinstrucción para deshabilitar las interrupciones en ese núcleo después de hacer una ioplllamada al sistema para establecer su nivel de privilegio de E / S para que suene 0.

Pero incluso x86 iopl"solo" da acceso a algunas instrucciones : entrada / salida (y las versiones de cadena ins / outs) y cli / sti. No le permite usar rdmsro wrmsrleer o escribir "registros específicos del modelo" (p. Ej.IA32_LSTAR que establece la dirección del punto de entrada del kernel para la syscallinstrucción x86-64 ), o usar lidtpara reemplazar la tabla de descriptor de interrupción (que le permitiría tomar totalmente sobre la máquina desde el núcleo existente, al menos en ese núcleo).

Ni siquiera puede leer los registros de control (como CR3, que contiene la dirección física del directorio de página de nivel superior, que un proceso de ataque puede encontrar útil como compensación /dev/mempara modificar sus propias tablas de páginas como alternativa ammap más) /dev/mem. )

invd(¡invalidar todas las memorias caché sin reescritura ! ( caso de uso = BIOS anterior antes de que se configure la RAM)) es otra diversión que siempre requiere CPL 0 completo (nivel de privilegio actual), no solo IOPL. Even wbinvdtiene privilegios porque es muy lento (y no interrumpible) y tiene que vaciar todos los cachés en todos los núcleos. (Consulte ¿Hay alguna forma de vaciar todo el caché de la CPU relacionado con un programa? Y el uso de la instrucción WBINVD )

Los errores que resultan en un salto a una dirección incorrecta ejecutando datos como código, por lo tanto, no pueden ejecutar ninguna de estas instrucciones por accidente en un servidor X de espacio de usuario.


El nivel de privilegio actual (en modo protegido y largo) son los 2 bits bajos de cs(el selector de segmento de código) . mov eax, cs/ and eax, 3funciona en cualquier modo para leer el nivel de privilegio.

Para escribir el nivel de privilegio, debe hacer jmp faro call farconfigurar CS:RIP(pero la entrada GDT / LDT para el segmento de destino puede restringirlo según el nivel de privilegio anterior, razón por la cual el espacio de usuario no puede hacer esto para elevarse). O usa into syscallpara cambiar al anillo 0 en un punto de entrada del núcleo.

Peter Cordes
fuente
En realidad, estoy bastante seguro de que es solo el "selector" de código en Intel parlace. Era un segmento en el 8086/8088, posiblemente en el 80186, pero para el 80286, se lo denominó selector, y no creo que hayan cambiado oficialmente esa terminología desde entonces.
un CVn