Asignación de pila mínima

Asignación de pila mínima

Estoy estudiando la especificación ELF (http://www.skyfree.org/linux/references/ELF_Format.pdf), y un punto que no me queda claro sobre el proceso de carga del programa es cómo se inicializa la pila y cuál es el tamaño de página inicial. Aquí está la prueba (en Ubuntu x86-64):

$ cat test.s
.text
  .global _start
_start:
  mov $0x3c,%eax
  mov $0,%edi
  syscall
$ as test.s -o test.o && ld test.o
$ gdb a.out -q
Reading symbols from a.out...(no debugging symbols found)...done.
(gdb) b _start
Breakpoint 1 at 0x400078
(gdb) run
Starting program: ~/a.out 

Breakpoint 1, 0x0000000000400078 in _start ()
(gdb) print $sp
$1 = (void *) 0x7fffffffdf00
(gdb) info proc map
process 20062
Mapped address spaces:

          Start Addr           End Addr       Size     Offset objfile
            0x400000           0x401000     0x1000        0x0 ~/a.out
      0x7ffff7ffa000     0x7ffff7ffd000     0x3000        0x0 [vvar]
      0x7ffff7ffd000     0x7ffff7fff000     0x2000        0x0 [vdso]
      0x7ffffffde000     0x7ffffffff000    0x21000        0x0 [stack]
  0xffffffffff600000 0xffffffffff601000     0x1000        0x0 [vsyscall]

La especificación ELF tiene muy poco que decir sobre cómo o por qué existe esta página de pila en primer lugar, pero puedo encontrar referencias que dicen que la pila debe inicializarse con SP apuntando a argc, con argv, envp y el vector auxiliar justo encima. eso, y lo he confirmado. ¿Pero cuánto espacio hay disponible debajo de SP? En mi sistema hay 0x1FF00bytes asignados debajo de SP, pero presumiblemente esto es una cuenta regresiva desde la parte superior de la pila en 0x7ffffffff000y hay 0x21000bytes en el mapeo completo. ¿Qué influye en este número?

Soy consciente de que la página justo debajo de la pila es una "página de protección" que automáticamente se puede escribir y "crece en la pila" si escribo en ella (presumiblemente para que el manejo ingenuo de la pila "simplemente funcione"), pero si asigno un marco de pila enorme, entonces podría sobrepasar la página de protección y la falla de segmentación, por lo que quiero determinar cuánto espacio ya se me ha asignado correctamente justo al inicio del proceso.

EDITAR: Algunos datos más me hacen sentir aún más inseguro de lo que está pasando. La prueba es la siguiente:

.text
  .global _start
_start:
  subq $0x7fe000,%rsp
  movq $1,(%rsp)
  mov $0x3c,%eax
  mov $0,%edi
  syscall

Jugué con diferentes valores de la constante 0x7fe000aquí para ver qué sucede, y para este valor no es determinista si obtengo un error de segmento o no. Según GDB, la subqinstrucción por sí sola expandirá el tamaño del mmap, lo cual es un misterio para mí (¿cómo sabe Linux lo que hay en mi registro?), pero este programa generalmente bloqueará GDB al salir por alguna razón. No puede ser ASLR la causa del no determinismo porque no estoy usando GOT ni ninguna sección PLT; el ejecutable siempre se carga en las mismas ubicaciones de la memoria virtual cada vez. Entonces, ¿se trata de alguna aleatoriedad del PID o de la memoria física? En general, estoy muy confundido en cuanto a cuánta pila está realmente disponible legalmente para acceso aleatorio y cuánta se solicita al cambiar RSP o al escribir en áreas "justo fuera del alcance" de la memoria legal.

Respuesta1

No creo que esta pregunta tenga realmente que ver con ELF. Hasta donde yo sé, ELF define una forma de "paquete plano"una imagen de programa en archivos y luego volver a ensamblarla lista para su primera ejecución. La definición de qué es la pila y cómo se implementa se encuentra en algún lugar entre la CPU específica y el sistema operativo específico si el comportamiento del sistema operativo no se ha elevado a POSIX. Aunque no hay duda de que la especificación ELF plantea algunas exigencias sobre lo que necesita en la pila.

Asignación de pila mínima

De tu pregunta:

Soy consciente de que la página justo debajo de la pila es una "página de protección" que automáticamente se puede escribir y "crece en la pila" si escribo en ella (presumiblemente para que el manejo ingenuo de la pila "simplemente funcione"), pero si asigno un marco de pila enorme, entonces podría sobrepasar la página de protección y la falla de segmentación, por lo que quiero determinar cuánto espacio ya se me ha asignado correctamente justo al inicio del proceso.

Estoy luchando por encontrar una referencia autorizada para esto. Pero he encontrado una cantidad suficiente de referencias no autorizadas para sugerir que esto es incorrecto.

Por lo que he leído, la página de protección se utiliza para capturar el acceso fuera de la asignación máxima de pila y no para el crecimiento "normal" de la pila. La asignación de memoria real (asignación de páginas a direcciones de memoria) se realiza según demanda. Es decir: cuando se accede a direcciones no asignadas en la memoria que se encuentran entre la base de pila y la base de pila - tamaño máximo de pila + 1, la CPU puede desencadenar una excepción, pero el kernel manejará la excepción asignando una página. de memoria, sin generar en cascada una falla de segmentación.

Por lo tanto, acceder a la pila dentro de la asignación máxima no debería causar un error de segmentación. Como has descubierto

Asignación máxima de pila

La documentación de investigación debe seguir las líneas de la documentación de Linux sobre la creación de subprocesos y la carga de imágenes (tenedor(2),clon(2),ejecutivo(2)). La documentación del ejecutivo.menciona algo interesante:

Límites en el tamaño de los argumentos y el entorno.

...recorte...

En el kernel 2.6.23 y posteriores, la mayoría de las arquitecturas admiten un límite de tamaño derivado del softwareRLIMIT_STACKlímite de recursos (verlímite de obtención(2))

...recorte...

Esto confirma que el límite requiere que la arquitectura lo admita y también hace referencia a dónde está limitado (límite de obtención(2)).

RLIMIT_STACK

Este es el tamaño máximo de la pila de procesos, en bytes. Al alcanzar este límite, se genera una señal SIGSEGV. Para manejar esta señal, un proceso debe emplear una pila de señales alternativa (sigaltstack(2)).

Desde Linux 2.6.23, este límite también determina la cantidad de espacio utilizado para los argumentos de la línea de comandos y las variables de entorno del proceso; para más detalles, consulte execve(2).

Hacer crecer la pila cambiando el registro RSP

No conozco el ensamblador x86. Pero llamaré su atención sobre la "Excepción de error de pila" que pueden activar las CPU x86 cuando se cambia el registro SS. Por favor corríjanme si me equivoco, pero creo que en x86-64 SS:SP acaba de convertirse en "RSP". Entonces, si entiendo correctamente, se puede activar una excepción de falla de pila mediante un RSP decrementado ( subq $0x7fe000,%rsp).

Consulte la página 222 aquí:https://xem.github.io/minix86/manual/intel-x86-and-64-manual-vol3/o_fe12b1e2a880e0ce.html

Respuesta2

Cada región de memoria de proceso (por ejemplo, código, datos estáticos, montón, pila, etc.) tiene límites, y un acceso a la memoria fuera de cualquier región, o un acceso de escritura a una región de solo lectura genera una excepción de CPU. El núcleo mantiene estas regiones de memoria. Un acceso fuera de una región se propaga hasta el espacio del usuario en forma de señal de error de segmentación.

No todas las excepciones se generan al acceder a la memoria fuera de las regiones. Un acceso dentro de la región también puede generar una excepción. Por ejemplo, si la página no está asignada a la memoria física, el controlador de errores de página lo maneja de forma transparente para el proceso en ejecución.

La región de la pila principal del proceso inicialmente tiene solo una pequeña cantidad de marcos de página asignados, pero crece automáticamente cuando se le envían más datos a través del puntero de la pila. El controlador de excepciones verifica que el acceso todavía esté dentro de la región reservada para la pila y, si lo está, asigna un nuevo marco de página. Esto sucede automáticamente desde el punto de vista del código de nivel de usuario.

Se coloca una página de protección justo después del final de la región de la pila, para detectar un desbordamiento de la región de la pila. Recientemente (en 2017), algunas personas se dieron cuenta de que una sola página de protección no es suficiente, porque se puede engañar a un programa para que disminuya el puntero de la pila en una cantidad grande, lo que puede hacer que el puntero de la pila apunte a alguna otra región que permita escrituras. La "solución" a este problema fue reemplazar la página de protección de 4 kB por una región de protección de 1 MB. Mira estoartículo de LWN.

Cabe señalar que esta vulnerabilidad no es del todo trivial de explotar; requiere, por ejemplo, que el usuario pueda controlar la cantidad de memoria que asigna un programa mediante una llamada a alloca. Los programas robustos deben verificar el parámetro pasado a alloca, especialmente si se deriva de la entrada del usuario.

información relacionada