¿Por qué no puedo mmap (MAP_FIXED) la página virtual más alta en un proceso Linux de 32 bits en un núcleo de 64 bits?

Mientras intentaba probar¿Está permitido acceder a la memoria que abarca el límite cero en x86? En el espacio de usuario en Linux, escribí un programa de prueba de 32 bits que intenta mapear las páginas altas y bajas del espacio de direcciones virtuales de 32 bits.

Despuésecho 0 | sudo tee /proc/sys/vm/mmap_min_addr, Puedo asignar la página cero, pero no sé por qué no puedo asignar-4096es decir(void*)0xfffff000, la página más alta.Por quemmap2((void*)-4096) regreso-ENOMEM?

strace ./a.out 
execve("./a.out", ["./a.out"], 0x7ffe08827c10 /* 65 vars */) = 0
strace: [ Process PID=1407 runs in 32 bit mode. ]
....
mmap2(0xfffff000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = -1 ENOMEM (Cannot allocate memory)
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0

Además, qué cheque lo está rechazando enlinux/mm/mmap.c¿Y por qué está diseñado de esa manera? ¿Es esto parte de asegurarse de que crear un puntero a one-past-an-object noajustar y romper las comparaciones de punteros, porque ISO C y C ++ permiten crear un puntero al final, pero no fuera de los objetos.

Estoy corriendo bajo un kernel de 64 bits (4.12.8-2-ARCH en Arch Linux), por lo que el espacio de usuario de 32 bits tiene disponibles 4GiB completos. (A diferencia del código de 64 bits en un núcleo de 64 bits, o con un núcleo de 32 bits en el que la división usuario / núcleo 2: 2 o 3: 1 convertiría la página alta en una dirección de núcleo).

No he intentado desde un ejecutable estático mínimo (sin inicio CRT o libc, solo asm) porque no creo que eso haga la diferencia. Ninguna de las llamadas al sistema de inicio CRT parece sospechosa.

Mientras estaba parado en un punto de quiebre, revisé/proc/PID/maps. La página superior ya no está en uso. La pila incluye la segunda página más alta, pero la página superior no está asignada.

00000000-00001000 rw-p 00000000 00:00 0             ### the mmap(0) result
08048000-08049000 r-xp 00000000 00:15 3120510                 /home/peter/src/SO/a.out
08049000-0804a000 r--p 00000000 00:15 3120510                 /home/peter/src/SO/a.out
0804a000-0804b000 rw-p 00001000 00:15 3120510                 /home/peter/src/SO/a.out
f7d81000-f7f3a000 r-xp 00000000 00:15 1511498                 /usr/lib32/libc-2.25.so
f7f3a000-f7f3c000 r--p 001b8000 00:15 1511498                 /usr/lib32/libc-2.25.so
f7f3c000-f7f3d000 rw-p 001ba000 00:15 1511498                 /usr/lib32/libc-2.25.so
f7f3d000-f7f40000 rw-p 00000000 00:00 0 
f7f7c000-f7f7e000 rw-p 00000000 00:00 0 
f7f7e000-f7f81000 r--p 00000000 00:00 0                       [vvar]
f7f81000-f7f83000 r-xp 00000000 00:00 0                       [vdso]
f7f83000-f7fa6000 r-xp 00000000 00:15 1511499                 /usr/lib32/ld-2.25.so
f7fa6000-f7fa7000 r--p 00022000 00:15 1511499                 /usr/lib32/ld-2.25.so
f7fa7000-f7fa8000 rw-p 00023000 00:15 1511499                 /usr/lib32/ld-2.25.so
fffdd000-ffffe000 rw-p 00000000 00:00 0                       [stack]

¿Hay regiones de VMA que no aparecen enmaps que todavía convence al kernel de rechazar la dirección? Miré las ocurrencias deENOMEM enlinux/mm/mmapc., pero es mucho código para leer, así que tal vez me perdí algo. ¿Algo que se reserva un rango de direcciones altas, o porque está al lado de la pila?

Hacer las llamadas del sistema en el otro orden no ayuda (pero PAGE_ALIGN y macros similares se escriben cuidadosamente para evitar que se enreden antes de enmascarar, por lo que no era probable de todos modos).

Fuente completa, compilada congcc -O3 -fno-pie -no-pie -m32 address-wrap.c:

#include <sys/mman.h>

//void *mmap(void *addr, size_t len, int prot, int flags,
//           int fildes, off_t off);

int main(void) {
    volatile unsigned *high =
        mmap((void*)-4096L, 4096, PROT_READ | PROT_WRITE,
             MAP_FIXED|MAP_PRIVATE|MAP_ANONYMOUS,
             -1, 0);
    volatile unsigned *zeropage =
        mmap((void*)0, 4096, PROT_READ | PROT_WRITE,
             MAP_FIXED|MAP_PRIVATE|MAP_ANONYMOUS,
             -1, 0);


    return (high == MAP_FAILED) ? 2 : *high;
}

(Dejé fuera la parte que trató de descuidar(int*)-2 porque se segfaults cuando mmap falla.)

Respuestas a la pregunta(1)

Su respuesta a la pregunta