22//
33// Regression test for the bug fixed in PR #1075:
44// munmap_syscall rounded `len` up to a page multiple and issued a single
5- // mmap(MAP_FIXED|PROT_NONE) over the whole rounded range. When that
6- // range crossed into a SharedMemory-backed vmmap entry, the host-level
7- // PROT_NONE silently clobbered the shm page.
5+ // mmap(MAP_FIXED|PROT_NONE) over the whole rounded range. When that range
6+ // crossed into a SharedMemory-backed vmmap entry, the host-level PROT_NONE
7+ // silently clobbered the shm page.
88//
9- // Layout strategy (no MAP_FIXED, no shmat hint):
10- // lind's allocator places NULL-addr shmat / mmap at the top of a free
11- // gap, and empirically leaves a 1-page stride between consecutive
12- // NULL-addr allocations. So:
13- //
14- // shmat(NULL) -> shm at top page T
15- // mmap (NULL) -> anon at T - 2*PAGE
16- // page at T - PAGE -> unmapped gap
9+ // Layout strategy:
10+ // 1. shmat(NULL) -> shm lands at some address T (allocator decides)
11+ // 2. mmap(T - 2*PAGE, ..., MAP_FIXED) -> anon forced to exactly T - 2*PAGE
12+ // 3. Gap page at T - PAGE is unmapped
1713//
1814// [ anon @ T-2P ][ gap @ T-P ][ shm @ T ]
1915//
20- // munmap(anon, 2*PAGE+1) rounds up to 3*PAGE and targets [T-2P .. T+P),
21- // covering {anon, gap, shm}. The shm entry's backing is SharedMemory, so:
16+ // munmap(anon, 2*PAGE+1) rounds up to 3*PAGE and targets [T-2P .. T+P),
17+ // covering {anon, gap, shm}.
2218//
2319// Buggy path: one mmap(PROT_NONE, MAP_FIXED) over the full 3*PAGE range
2420// clobbers shm's host memory -> reading shm[0] faults.
2521// Fixed path: the overlap loop filters out the SharedMemory entry;
26- // only the anon page gets PROT_NONE'd. shm stays readable.
27- //
28- // The stride is asserted explicitly — if allocator behavior ever changes
29- // and anon doesn't land at T - 2*PAGE, the test fails loudly instead of
30- // silently passing.
22+ // only the anon/gap pages get PROT_NONE'd. shm stays readable.
3123#include <sys/ipc.h>
3224#include <sys/shm.h>
3325#include <sys/mman.h>
@@ -48,35 +40,59 @@ int main(void) {
4840 assert (((uintptr_t )shm % PAGE_SIZE ) == 0 && "shm not page-aligned" );
4941 memset (shm , 0xAB , PAGE_SIZE );
5042
51- char * anon = (char * )mmap (NULL , PAGE_SIZE , PROT_READ | PROT_WRITE ,
52- MAP_PRIVATE | MAP_ANONYMOUS , -1 , 0 );
53- assert (anon != MAP_FAILED && "anon mmap failed" );
54- memset (anon , 0xCD , PAGE_SIZE );
43+ // Force anon exactly 2 pages below shm using MAP_FIXED.
44+ // We control anon's address; we don't need the allocator to cooperate.
45+ char * anon_target = shm - 2 * PAGE_SIZE ;
46+ char * anon = (char * )mmap (anon_target , PAGE_SIZE , PROT_READ | PROT_WRITE ,
47+ MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED , -1 , 0 );
5548
56- // Precondition on allocator layout: anon must land exactly 2 pages
57- // below shm (one page of unmapped gap between them). If this ever
58- // changes, fail loudly instead of passing by accident.
59- assert (shm == anon + 2 * PAGE_SIZE &&
60- "allocator layout changed: anon is not at shm - 2*PAGE" );
49+ fprintf (stderr , " DIAG: shm @ %p\n" , (void * )shm );
50+ fprintf (stderr , " DIAG: anon_target= %p\n" , (void * )anon_target );
51+ fprintf (stderr , " DIAG: anon actual= %p\n" , (void * )anon );
52+ fprintf (stderr , " DIAG: gap @ %p\n" , (void * )(shm - PAGE_SIZE ));
53+ fprintf (stderr , " DIAG: munmap range [%p, %p) — rounded len = 3 pages\n" ,
54+ (void * )anon , (void * )(anon + 3 * PAGE_SIZE ));
6155
62- // Unaligned munmap starting at anon, ending one byte past the gap:
63- // len = 2*PAGE+1 -> rounded length = 3*PAGE
64- // rounded range = [anon, anon + 3*PAGE) = [anon, shm + PAGE)
65- // Buggy runtime PROT_NONEs the whole range (clobbering shm).
66- // Fixed runtime skips the SharedMemory-backed page.
56+ if (anon == MAP_FAILED ) {
57+ fprintf (stderr , "FAIL: MAP_FIXED mmap for anon failed\n" );
58+ shmdt (shm );
59+ shmctl (shmid , IPC_RMID , NULL );
60+ return 1 ;
61+ }
62+ if (anon != anon_target ) {
63+ fprintf (stderr ,
64+ "FAIL: MAP_FIXED returned %p instead of %p — "
65+ "kernel rejected the fixed placement\n" ,
66+ (void * )anon , (void * )anon_target );
67+ munmap (anon , PAGE_SIZE );
68+ shmdt (shm );
69+ shmctl (shmid , IPC_RMID , NULL );
70+ return 1 ;
71+ }
72+
73+ memset (anon , 0xCD , PAGE_SIZE );
74+
75+ // munmap(anon, 2*PAGE+1):
76+ // rounded length = 3*PAGE
77+ // rounded range = [anon, anon+3*PAGE) = [T-2P, T+P)
78+ // Buggy runtime: PROT_NONEs the full range including shm at T.
79+ // Fixed runtime: skips the SharedMemory-backed page at T.
6780 int rc = munmap (anon , 2 * PAGE_SIZE + 1 );
6881 assert (rc == 0 && "trigger munmap failed" );
6982
83+ fprintf (stderr , " DIAG: munmap returned 0, checking shm integrity\n" );
84+
7085 for (int i = 0 ; i < PAGE_SIZE ; i ++ ) {
7186 if ((unsigned char )shm [i ] != 0xAB ) {
72- printf ( "FAIL: shm[%d] = 0x%02x, expected 0xAB\n" ,
73- i , (unsigned char )shm [i ]);
87+ fprintf ( stderr , "FAIL: shm[%d] = 0x%02x, expected 0xAB\n" ,
88+ i , (unsigned char )shm [i ]);
7489 shmdt (shm );
7590 shmctl (shmid , IPC_RMID , NULL );
7691 return 1 ;
7792 }
7893 }
7994
95+ fprintf (stderr , " DIAG: all %d shm bytes intact\n" , PAGE_SIZE );
8096 printf ("PASS: shm page intact after unaligned munmap of nearby anon\n" );
8197 shmdt (shm );
8298 shmctl (shmid , IPC_RMID , NULL );
0 commit comments