linux mremap(2) MAP_ANONYMOUS|MAP_SHARED can't access new area

Viewed 435

I guess I must goof somewhere but I can't see where, so multiple eyes may help.

I intended to use linux mremap() to grow an area in my VAS. The mremap() call seems to do the job, i.e new mapping, but odly enough the extended area is not accessible.

Here is my test program

#define _GNU_SOURCE  
#include <stdlib.h>
#include <stdio.h>

#include <sys/types.h>
#include <unistd.h>
#include <sys/mman.h>

int main(int c, char **v)
{ char *p;
  int i;

  setbuf(stdout, NULL);
  printf("pid=%d\n",getpid());

  p=mmap(0, 4096,PROT_READ|PROT_WRITE,MAP_ANONYMOUS|MAP_SHARED, -1, 0);
  if((void*)p == MAP_FAILED)
  { printf("mmap failed\n");
  }
  p[0]='a';
  printf("p=%#lx p[0]=%c\n",(long)p,p[0]);
  printf("Paused [Ret]:"); read(0,&i,4);

  p=mremap(p,4096,8192,MREMAP_MAYMOVE);
  p[4095]='b';
  printf("p=%#lx p[0]=%c p[4095]=%c\n",(long)p,p[0],p[4095]);
  printf("Paused [Ret]:"); read(0,&i,4);
  p[4096]='c';
  printf("p=%#lx p[0]=%c p[4096]=%c\n",(long)p,p[0],p[4096]);
  exit(0);
}

Running it I get

PW$ cc -o e e.c

PW$ ./e
pid=7178
p=0x7ffa912b9000 p[0]=a
Paused [Ret]:

At this point I can check the maps

PW$ grep zero /proc/7178/maps
7ffa912b9000-7ffa912ba000 rw-s 00000000 00:01 209101 /dev/zero (deleted)

We can see the mapping match p=0x7ffa912b9000, continuing the program we got after mremap()

p=0x7ffa9128b000 p[0]=a p[4095]=b
Paused [Ret]:

PW$ grep zero /proc/7178/maps
7ffa9128b000-7ffa9128d000 rw-s 00000000 00:01 209101 /dev/zero (deleted)

Here we can see the re-mapping was done, we obtained a new address, and the old data 'a' is still there. We can see also that this new mapping is 8192 in size 7ffa9128d000-7ffa9128b000-0x2000=8192. But then trying to write in there in the extended area bring chaos.

Paused [Ret]:
Bus error (core dumped)

I do this on Ubuntu 20.04 kernel is 5.4.0-29-generic

For a minute I thought that may be the extended area had no PROT_WRITE, though the original has it, so I plugged an mprotect() PROT_WRITE|PROT_READ on the new area (new addr, new size), yet no joy.

If someone can spot the goof, and provide a new pointer, it would be greatly appreciated :)

Cheers, Phi

2 Answers

FWIW, I propose here a workaround, it is a bit sloppy, but seems to be the cheapest one I got. One work around could have been to keep an FD open on an unlinked file, and use that as backing store, then ftruncate to grow, and then remap work because it is a named mmap(), this one is to heavy for me as each grow would require an ftruncate() with all the machinery in the FS, then a mremap(), and worst the keep of an FD, as I have tons of mmap regions, would mean tons of FD, and that's not good in my case, yet it could be a valid woraround for the one with few 'grow' needs.

So my second workaround is still based on MAP_ANONYMOUS|MAP_SHARED (the killing combo), and is based on the idea to let the OS choose another mapping address(MREMAP_MAYMOVE), then with the addr, unmap() the excess pages (the grow area), and mmap() it again at fixed adjacent addr.

The end result is a grow that require 3 syscall mremap(), munmap(), mmap(), all relativly lightweight, while the other workaround eat an permanent FD and 2 syscall ftruncate(), mremap(), with ftruncate() being 'heavy'.

So here is my new code and the acceptable result for my case.

Again this is a demonstrator, then no thorough error checking.

#define _GNU_SOURCE  
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>

#include <sys/types.h>
#include <unistd.h>
#include <sys/mman.h>

#define checkaddr(p) access(p,0)
#define strchecka(p) (checkaddr(p),strerror(errno*(errno==EFAULT)))

int main(int c, char **v)
{ int   i;
  char  b[128], *p;
  union { char *p; long  l;}u;

  sprintf(b,"pmap %d | grep zero",getpid());

  p=mmap(0, 4096,PROT_READ|PROT_WRITE,MAP_ANONYMOUS|MAP_SHARED, -1, 0);
  if((void*)p == MAP_FAILED)
  { printf("mmap failed\n");
  }
  u.p=p;
  system((printf("After mmap\n"),b));
  printf("p=%#lx p[0]=%c\n",u.l,u.p[0]='a');

  p=mremap(p,4096,8192,MREMAP_MAYMOVE);
  system((printf("After mremap\n"),b));

  u.p=p+4094; *u.p='b';
  printf("p=%#lx p[0]=%c p[4094]=%c\n",u.l,p[0],*u.p);  
  printf("%#lx addr check => %s\n",u.l,strchecka(u.p));

  u.p=p+4096;
  printf("%#lx addr check => %s\n",u.l,strchecka(u.p));


  munmap(p+4096,4096);
  system((printf("After unmap p+4096\n"),b));
  u.p=mmap(p+4096, 4096,PROT_READ|PROT_WRITE,MAP_ANONYMOUS|MAP_SHARED, -1, 0);
  system((printf("After mmap p+4096\n"),b));
  printf("%#lx addr check => %s\n",u.l,strchecka(u.p));

  u.p=p; p[4096]='c';
  printf("p=%#lx p[0]=%c p[4094]=%c p[4096]=%c\n",u.l,u.p[0],p[4094],p[4096]);
  exit(0);
}

And the compile run

PW$ cc -o f2 f2.c
PW$ ./f2
After mmap
00007f4446d29000      4K rw-s- zero (deleted)
p=0x7f4446d29000 p[0]=a
After mremap
00007f4446cfb000      8K rw-s- zero (deleted)
p=0x7f4446cfbffe p[0]=a p[4094]=b
0x7f4446cfbffe addr check => Success
0x7f4446cfc000 addr check => Bad address
After unmap p+4096
00007f4446cfb000      4K rw-s- zero (deleted)
After mmap p+4096
00007f4446cfb000      4K rw-s- zero (deleted)
00007f4446cfc000      4K rw-s- zero (deleted)
0x7f4446cfc000 addr check => Success
p=0x7f4446cfb000 p[0]=a p[4094]=b p[4096]=c

Note that now we end up with 2 4K adjacent region instead of 1 8K but in my case this is good enough.

Related