name keywords etiquetas content x86-64 abi red-zone

x86-64 - keywords - meta title seo



¿Por qué el código del kernel no puede usar una Zona Roja? (3)

Citando de la AMD64 ABI:

El área de 128 bytes más allá de la ubicación señalada por% rsp se considera reservada y no debe ser modificada por los manejadores de señales o interrupciones. Por lo tanto, las funciones pueden usar esta área para datos temporales que no son necesarios entre llamadas a funciones. En particular, las funciones de hoja pueden usar esta área para todo su marco de pila, en lugar de ajustar el puntero de pila en el prólogo y el epílogo. Esta zona es conocida como la zona roja.

Esencialmente, es una optimización: el compilador de la zona del usuario sabe exactamente la cantidad de la Zona Roja que se usa en un momento dado (en la implementación más simple, el tamaño completo de las variables locales) y puede ajustar el %rsp consecuencia antes de llamar a una subfunción.

Especialmente en las funciones de hoja, esto puede proporcionar algunos beneficios de rendimiento al no tener que ajustar %rsp ya que podemos estar seguros de que no se ejecutará ningún código desconocido mientras esté en la función. (POSIX Signal Handlers puede verse como una forma de co-rutina, pero puede indicar al compilador que ajuste los registros antes de usar las variables de pila en un controlador de señales).

En el espacio del kernel, una vez que comience a pensar en las interrupciones, si esas interrupciones hacen algunas suposiciones sobre %rsp , probablemente serán incorrectas, no hay certeza con respecto a la utilización de la Zona Roja. Por lo tanto, asume que todo está sucio, y desperdicia innecesariamente espacio de pila (ejecutando efectivamente con una variable local garantizada de 128 bytes en cada función), o garantizas que las interrupciones no hacen suposiciones sobre %rsp , lo cual es complicado.

En el espacio de usuario, los cambios de contexto + la asignación de 128 bytes de la pila lo manejan por usted.

Se recomienda encarecidamente al crear un kernel de 64 bits (para la plataforma x86_64), para indicar al compilador que no use la Zona Roja de 128 bytes que utiliza la ABI de espacio de usuario. (Para GCC, el indicador del compilador es -mno-red-zone ).

El kernel no sería seguro contra interrupciones si está habilitado.

Pero ¿por qué es eso?


En el espacio del kernel, estás usando la misma pila que interrumpe el uso. Cuando ocurre una interrupción, la CPU empuja una dirección de retorno y RFLAGS . Esto golpea 16 bytes por debajo de rsp . Incluso si quisiera escribir un controlador de interrupciones que asumiera que los 128 bytes completos de la zona roja eran valiosos, sería imposible.

Tal vez podría tener un ABI interno al kernel que tuviera una pequeña zona roja desde rsp-16 a rsp-48 o algo así. (Pequeño porque la pila del kernel es valiosa, y la mayoría de las funciones no necesitan mucha zona roja de todos modos).

Los manejadores de interrupciones tendrían que sub rsp, 32 antes de empujar cualquier registro. (y restaurarlo antes de iret ).

Esta idea no funcionará si un controlador de interrupciones puede ser interrumpido antes de que se ejecute sub rsp, 32 , o después de que restaure rsp antes de un iret . Habría una ventana de vulnerabilidad donde los datos valiosos están en rsp .. rsp-16 .

Otro problema práctico con este esquema es que AFAIK gcc no tiene parámetros configurables de zona roja. Está encendido o apagado. Así que tendrías que agregar soporte para un sabor de kernel de zona roja a gcc / clang si quisieras aprovecharlo.

Incluso si estuviera a salvo de interrupciones anidadas, los beneficios son bastante pequeños. La dificultad de demostrar que es seguro en un núcleo puede hacer que no valga la pena. (Y como dije, no estoy seguro de que pueda implementarse de manera segura, porque creo que las interrupciones anidadas son posibles).

(Por cierto, consulte la wiki de la etiqueta x86 para obtener enlaces a la ABI que documenta la zona roja y otras cosas).


Es posible utilizar la zona roja en contextos de tipo kernel. IDTentry puede especificar un índice de pila (ist) de 0..7, donde 0 es un poco especial. El TSS contiene una tabla de estas pilas. 1..7 se cargan, y se utilizan para los registros iniciales guardados por la excepción / interrupción, y no se anidan. Si divide las distintas entradas de excepción por prioridades (por ejemplo, NMI es la más alta y puede suceder en cualquier momento) y trata estas pilas como trampolines, puede manejar con seguridad las zonas rojas en contextos tipo kernel. Es decir, puede restar 128 del puntero de pila guardado para obtener una pila de kernel utilizable antes de habilitar interrupciones o código que puede causar excepciones.

La pila de índice cero se comporta de una manera más convencional, al empujar la pila, las banderas, la PC, el error en la pila existente cuando no hay una transición de privilegio.

El código en el trampolín debe ser cuidadoso (duh, es un kernel) para no generar otras excepciones mientras desinfecta el estado de la máquina, pero proporciona un lugar agradable y seguro para detectar el anidamiento patológico del kernel, la corrupción de la pila, etc ... [ Lamento responder tan tarde, noté esto mientras buscaba algo más].