Cambiar de nivel cero a tres en IA-32e

Objeto del documento

Brindar recomendaciones para el pasaje de nivel de privilegio cero a tres en IA-32e. A partir del ejemplo de programa en IA-32e aportado por el Ing. Dario Alpern.

Punto de partida

El programa long.asm (disponible haciendo click aquí) propone lo siguiente:

; Hacer un programa que muestre la cantidad de decimas de segundo que estuvo 
; corriendo en modo largo (64 bits) y la cantidad de teclas presionadas.
; Utilizar para ello las interrupciones en modo protegido 8 (reloj) y 9 (teclado).
; El cuerpo principal de ejecución se detiene con hlt
; 
; Hecho por Dario Alejandro Alpern el 11 de marzo de 2008. 
; 

Que dice "info gdt"

  • Entrada nula
  • Segmento de código de 64 bits con DPL=0 (cs_sel_64)
  • Segmento de código de 32 bits con DPL=0 (cs_sel_32)
  • Segmento de datos de 32 bits (ds_sel)

Que dice "info idt"

  • Puerta de interrupción en 8 con DPL=0 (cs_sel_64:int8han)
  • Puerta de interrupción en 9 con DPL=0 (cs_sel_64:int9han)

Con esto podemos deducir que el PIC no está siendo reubicado.

Que dice "info tab"

<bochs:4> info tab
cr3: 0x00090000
0x00000000-0x001fffff -> 0x00000000-0x001fffff
<bochs:5> 

Esto nos dice que tenemos una página de dos MB con identity mapping

¿Cual es la idea?

Utilizar “int 30h” para bajar el código de nivel cero a tres.
La paginación permanece invariable.
El timer tick se usa para actualizar pantalla ejecutando código de nivel 0 como antes.
Lo mismo para el manejo del teclado.
Probar “int 80h” para acceder a nivel cero desde el tres.
El programa para detenerse debe utilizar “jmp $” en vez de “halt” (Está claro que esto consume CPU, es sólo porque está en nivel tres).

Manos a la obra

  • En la GDT declaramos tres segmentos nuevos. Uno para código de 64 bits con DPL=3, otro de datos de 32 bits también con DPL=3 y otro para TSS (Task State Segment) también de 64 bits con DPL=0.
  • En la IDT agregamos una puerta de interrupción en 30h con DPL=0, que será para hacer el cambio de privilegio (de cero a tres). También agregamos otra puerta de interrupción en 80h, esta será para acceso a servicios del kernel por código que corre en nivel tres.
  • Dado que describimos una TSS en la GDT debemos reservar espacio en memoria a tal efecto.

Esto se logra con:

TSS: times tss64_struc_size               db  0

La estructura de TSS de 64 bits está disponible mas abajo.

  • Asegurarse que la página esté accesible a nivel USER.
  • Definir pila de nivel cero (porque no me gusta como está en long.asm)
mov rsp,pila_0 
mov ax,ds_sel
mov ss,ax

Donde pila_0 de define como:

align 256 
times 256 db 0
pila_0:
  • Definir pila de nivel tres.
  • Inicializar la base del descriptor de TSS en la GDT
mov     eax,TSS
mov     [TSS_sel+gdt+2],ax
  • Guardar rsp de nivel 0 en la TSS
mov     [TSS+4],rsp
  • Cargar TSS en TR
mov	eax,TSS_sel
ltr	ax
  • Invocar INT 30h para pasar de nivel cero a tres
  • Luego de esto se debe verificar que estamos en nivel 3.
  • Hacer pruebas de manejo de pila de nivel 3 y uso de int 80h.
  • Finalmente “colgar” la ejecución con un jmp $.

La magia está en int 30h

Al ejecutar la interrupción se guarda en la pila de nivel 0 lo siguiente: RIP, CS, RFLAGS, RSP y SS.
La idea es alterar CS, RSP y SS de la pila. Para que luego al hacer IRETQ se cambie de nivel.

push rbp
mov rbp,rsp
push rax               ; para trabajar
mov rax,cs_sel3_64
mov [rbp+16],rax
mov rax,pila_3
mov [rbp+32],rax
mov rax,ds_sel3
mov [rbp+40],rax
pop rax
pop rbp
iretq

Estructura de TS de 64 bits

struc tss64_struc
                     resd 1      ; Reservada
      .reg_RSP0      resq 1
      .reg_RSP1      resq 1
      .reg_RSP2      resq 1
		     resq 1      ; Reservada
      .reg_IST1      resq 1
      .reg_IST2      resq 1
      .reg_IST3      resq 1
      .reg_IST4      resq 1
      .reg_IST5      resq 1
      .reg_IST6      resq 1
      .reg_IST7      resq 1
                     resd 1      ; Reservada
                     resd 1      ; Reservada
                     resw 1      ; Reservada
      .reg_IOMAP     resw 1
endstruc

Si todo sale bien ...

El programa debe lucir igual que el original (long.asm), sólo que al hacer break el código debe estar en el “jmp $” de nivel 3, a menos que tengamos la suerte de detenerlo justo en alguno de los handler de interrupción (8 o 9) donde estaría corriendo en anillo 0.

Agradecimientos

A Dario Alpern por el aporte de long.asm. A Alejandro Furfaro por meternos en este baile. A Mariano Gonzalez y Juan Carlos Cuttitta que colaboran con ideas todo el tiempo. Al cuerpo docente de la cátedra de TDIII.
Se aceptan críticas y comentarios.

Marcelo Doallo 2012/07/05 21:03