linux assembly x86-64 system-calls

¿Por qué las llamadas al sistema Linux x86-64 modifican RCX, y qué significa el valor?



assembly system-calls (1)

Estoy tratando de asignar algo de memoria en Linux con sys_brk syscall. Esto es lo que probé:

BYTES_TO_ALLOCATE equ 0x08 section .text global _start _start: mov rax, 12 mov rdi, BYTES_TO_ALLOCATE syscall mov rax, 60 syscall

La cuestión es que, según la convención de llamadas de Linux, esperaba que el valor de retorno estuviera en el registro rax (puntero a la memoria asignada). sys_brk esto en gdb y después de hacer sys_brk syscall noté los siguientes contenidos de registro

Antes de syscall

rax 0xc 12 rbx 0x0 0 rcx 0x0 0 rdx 0x0 0 rsi 0x0 0 rdi 0x8 8

Después de syscall

rax 0x401000 4198400 rbx 0x0 0 rcx 0x40008c 4194444 ; <---- What does this value mean? rdx 0x0 0 rsi 0x0 0 rdi 0x8 8

No entiendo muy bien el valor en el registro rcx en este caso. ¿Cuál usar como puntero al comienzo de 8 bytes que sys_brk con sys_brk ?


El valor de retorno de la llamada del sistema está en rax , como siempre. Consulte Cuáles son las convenciones de llamadas para las llamadas al sistema UNIX y Linux en i386 y x86-64 .

Tenga en cuenta que sys_brk tiene una interfaz ligeramente diferente que las funciones POSIX de brk / sbrk ; consulte la sección de diferencias de la biblioteca C / kernel de la página del brk(2) man brk(2) Linux . Específicamente, Linux sys_brk establece el salto del programa ; el argumento y el valor de retorno son ambos punteros. Ver Ensamblaje x86 uso de la llamada brk () . Esa respuesta necesita votos positivos porque es la única buena en esa pregunta.

La otra parte interesante de su pregunta es:

No entiendo bien el valor en el registro RCX en este caso

Está viendo la mecánica de cómo las instrucciones syscall / sysret están diseñadas para permitir que el núcleo reanude la ejecución en el espacio del usuario pero que sea rápido.

syscall no carga ni almacena nada, solo modifica los registros. En lugar de usar registros especiales para guardar una dirección de retorno, simplemente usa registros enteros regulares.

No es una coincidencia que RCX=RIP y R11=RFLAGS después de que el núcleo vuelva a su código de espacio de usuario . La única forma de que este no sea ​​el caso es si una ptrace sistema ptrace modifica el valor rcx o r11 guardado del proceso mientras estaba dentro del núcleo. ( ptrace es la llamada al sistema que usa gdb). En ese caso, Linux usaría iret lugar de sysret para volver al espacio del usuario, porque el iret caso general más lento puede hacer eso. (Consulte ¿Qué sucede si utiliza la ABI de Linux int 0x80 de 32 bits en el código de 64 bits? Para conocer algunos de los puntos de entrada de llamadas al sistema de Linux. Principalmente los puntos de entrada de procesos de 32 bits, no de syscall en un 64 proceso de bits, sin embargo.)

En lugar de insertar una dirección de retorno en la pila del kernel (como lo hace int 0x80 ), syscall :

  • establece RCX = RIP, R11 = RFLAGS (por lo que es imposible que el núcleo vea los valores originales de esos registros antes de ejecutar syscall ).
  • enmascara RFLAGS con una máscara preconfigurada de un registro de configuración (el IA32_FMASK MSR). Esto permite que el kernel deshabilite las interrupciones (IF) hasta que swapgs y configurar rsp para que apunte a la pila del kernel. Incluso con cli como la primera instrucción en el punto de entrada, habría una ventana de vulnerabilidad. También puede obtener cld de forma gratuita al enmascarar DF para que los rep movs / rep movs vayan hacia arriba incluso si el espacio de usuario ha usado std .

    Dato syscall : el primer diseño de syscall / swapgs propuesto por swapgs no enmascaraba RFLAGS, pero lo cambiaron después de los comentarios de los desarrolladores del kernel en la lista de correo amd64 (en ~ 2000, un par de años antes del primer silicio).

  • salta al punto de entrada de syscall configurado (configuración CS: RIP = IA32_LSTAR ). El antiguo valor CS no se guarda en ningún lado, creo.

  • No hace nada más, el núcleo tiene que usar swapgs para obtener acceso a un bloque de información donde guardó el puntero de la pila del núcleo, porque rsp todavía tiene su valor desde el espacio de usuario.

Por lo tanto, el diseño de syscall requiere una llamada de sistema ABI que registra los registros, y es por eso que los valores son lo que son.