Anonymous mmap zero-filled?
Asked Answered
H

2

18

In Linux, the mmap(2) man page explains that an anonymous mapping

. . . is not backed by any file; its contents are initialized to zero.

The FreeBSD mmap(2) man page does not make a similar guarantee about zero-filling, though it does promise that bytes after the end of a file in a non-anonymous mapping are zero-filled.

Which flavors of Unix promise to return zero-initialized memory from anonymous mmaps? Which ones return zero-initialized memory in practice, but make no such promise on their man pages?

It is my impression that zero-filling is partially for security reasons. I wonder if any mmap implementations skip the zero-filling for a page that was mmapped, munmapped, then mmapped again by a single process, or if any implementations fill a newly mapped page with pseudorandom bits, or some non-zero constant.

P.S. Apparently, even brk and sbrk used to guarantee zero-filled pages. My experiments on Linux seem to indicate that, even if full pages are zero-filled upon page fault after a sbrk call allocates them, partial pages are not:

#include <unistd.h>
#include <stdio.h>

int main() {
  const intptr_t many = 100;
  char * start = sbrk(0);
  sbrk(many);
  for (intptr_t i = 0; i < many; ++i) {
    start[i] = 0xff;
  }
  printf("%d\n",(int)start[many/2]);
  sbrk(many/-2);
  sbrk(many/2);
  printf("%d\n",(int)start[many/2]);
  sbrk(-1 * many);
  sbrk(many/2);
  printf("%d\n",(int)start[0]);
}
Hay answered 9/7, 2013 at 3:42 Comment(0)
B
15

Which flavors of Unix promise to return zero-initialized memory from anonymous mmaps?

GNU/Linux

As you said in your question, the Linux version of mmap promises to zero-fill anonymous mappings:

MAP_ANONYMOUS

The mapping is not backed by any file; its contents are initialized to zero.

NetBSD

The NetBSD version of mmap promises to zero-fill anonymous mappings:

MAP_ANON

Map anonymous memory not associated with any specific file. The file descriptor is not used for creating MAP_ANON regions, and must be specified as -1. The mapped memory will be zero filled.

OpenBSD

The OpenBSD manpage of mmap does not promise to zero-fill anonymous mappings. However, Theo de Raadt (prominent OpenBSD developer), declared in November 2019 on the OpenBSD mailing list:

Of course it is zero filled. What else would it be? There are no plausible alternatives.

I think it detracts from the rest of the message to say something so obvious.

And the other OpenBSD developers did not contradict him.

IBM AIX

The AIX version of mmap promises to zero-fill anonymous mappings:

MAP_ANONYMOUS

Specifies the creation of a new, anonymous memory region that is initialized to all zeros.

HP-UX

According to nixdoc.net, the HP-UX version of mmap promises to zero-fill anonymous mappings:

If MAP_ANONYMOUS is set in flags, a new memory region is created and initialized to all zeros.

Solaris

The Solaris version of mmap promises to zero-fill anonymous mappings:

When MAP_ANON is set in flags, and fildes is set to -1, mmap() provides a direct path to return anonymous pages to the caller. This operation is equivalent to passing mmap() an open file descriptor on /dev/zero with MAP_ANON elided from the flags argument.

This Solaris man page gives us a way to get zero-filled memory pages without relying on the behavior of mmap used with the MAP_ANONYMOUS flag: do not use the MAP_ANONYMOUS flag, and create a mapping backed by the /dev/zero file. It would be useful to know the list of Unix-like operating systems providing the /dev/zero file, to see if this approach is more portable than using the MAP_ANONYMOUS flag (neither /dev/zero nor MAP_ANONYMOUS are POSIX).

Interestingly, the Wikipedia article about /dev/zero claims that MAP_ANONYMOUS was introduced to remove the need of opening /dev/zero when creating an anonymous mapping.

Beghtol answered 1/12, 2020 at 4:30 Comment(0)
C
8

It's hard to say which ones promise what without simply exhaustively enumerating all man pages or other release documentation, but the underlying code that handles MAP_ANON is (usually? always?) also used to map in bss space for executables, and bss space needs to be zero-filled. So it's pretty darn likely.

As for "giving you back your old values" (or some non-zero values but most likely, your old ones) if you unmap and re-map, it certainly seems possible, if some system were to be "lazy" about deallocation. I have only used a few systems that support mmap in the first place (BSD and Linux derivatives) and neither one is lazy that way, at least, not in the kernel code handling mmap.

The reason sbrk might or might not zero-fill a "regrown" page is probably tied to history, or lack thereof. The current FreeBSD code matches with what I recall from the old, pre-mmap days: there are two semi-secret variables, minbrk and curbrk, and both brk and sbrk will only invoke SYS_break (the real system call) if they are moving curbrk to a value that is at least minbrk. (Actually, this looks slightly broken: brk has the at-least behavior but sbrk just adds its argument to curbrk and invokes SYS_break. Seems harmless since the kernel checks, in sys_obreak() in /sys/vm/vm_unix.c, so a too-negative sbrk() will fail with EINVAL.)

I'd have to look at the Linux C library (and then perhaps kernel code too) but it may simply ignore attempts to "lower the break", and merely record a "logical break" value in libc. If you have mmap() and no backwards compatibility requirements, you can implement brk() and sbrk() entirely in libc, using anonymous mappings, and it would be trivial to implement both of them as "grow-only", as it were.

Constructivism answered 27/7, 2013 at 8:59 Comment(2)
I didn't know this until I looked it up but MAP_ANON isn't in POSIX.Mineraloid
@Olsonist: that's kind of surprising, but it adds to the "no hard-and-fast promises about MAP_ANON", to the extent that there's not even a promise that MAP_ANON will exist. :-)Constructivism

© 2022 - 2024 — McMap. All rights reserved.