* [PATCH] man/man2/mmap.2: Document when MAP_GROWSDOWN does/doesn't trigger growth
@ 2026-04-23 16:44 Ben Kallus
2026-05-04 13:00 ` Alejandro Colomar
0 siblings, 1 reply; 2+ messages in thread
From: Ben Kallus @ 2026-04-23 16:44 UTC (permalink / raw)
To: alx; +Cc: linux-man, Ben Kallus
The man page states that MAP_GROWSDOWN can only cause a mapping to
grow by a single page. This is incorrect; mappings can grow by many
pages at a time, until reaching either the stack size limit or growing
too close to another mapping.
To observe that mappings can grow by more than one page, and that they
are limited by the stack size limit, run the following C program with a
stack size limit of 0x800000 bytes, and then again with a stack size limit
of 0x801000 bytes, and observe that it segfaults as the comments describe.
> struct page {
> char data[4096];
> };
> static_assert(sizeof(struct page) == 4096);
>
> void *const BASE_ADDRESS = (void *)0xabcdef000;
>
> int main(int const argc, char const * const * const argv) {
> volatile struct page *p = mmap(
> BASE_ADDRESS,
> 1,
> PROT_READ | PROT_WRITE,
> MAP_ANONYMOUS | MAP_PRIVATE | MAP_GROWSDOWN | MAP_FIXED_NOREPLACE,
> -1,
> 0
> );
> // stack_limit=0x800000 // stack_limit=0x801000
> (p - 2047)->data[0] = 0; // no segfault // no segfault
> (p - 2048)->data[0] = 0; // segfault // no segfault
> (p - 2049)->data[0] = 0; // segfault // segfault
> }
To observe that mappings stop growing when they get within 256 pages of
the next lower mapping (instead of a single page, as the man page
currently states), run the following program, and observe that it
segfaults as the comments describe.
> struct page {
> char data[4096];
> };
> static_assert(sizeof(struct page) == 4096);
>
> struct page *const BASE_ADDRESS = (void *)0xabcdef000;
>
> int main(int const argc, char const * const * const argv) {
> volatile struct page *p = mmap(
> BASE_ADDRESS,
> 1,
> PROT_READ | PROT_WRITE,
> MAP_ANONYMOUS | MAP_PRIVATE | MAP_GROWSDOWN | MAP_FIXED_NOREPLACE,
> -1,
> 0
> );
>
> struct page *p2 = mmap(
> BASE_ADDRESS - 258,
> 1,
> PROT_READ | PROT_WRITE,
> MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED_NOREPLACE,
> -1,
> 0
> );
>
> // no segfault (causes p to grow by a page)
> (p - 1)->data[0] = 0;
>
> // unmap the test page
> munmap(p2, 1);
>
> // unmap the new page from p growing
> munmap((struct page *)p - 1, 1);
>
> struct page *p3 = mmap(
> BASE_ADDRESS - 257,
> 1,
> PROT_READ | PROT_WRITE,
> MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED_NOREPLACE,
> -1,
> 0
> );
>
> // segfault because p can't grow due to proximity to p3
> (p - 1)->data[0] = 0;
> }
Fixes: 176b1a76 (2016-11-21; "mmap.2: Add (much) more detail on MAP_GROWSDOWN")
Signed-off-by: Ben Kallus <benjamin.p.kallus.gr@dartmouth.edu>
---
man/man2/mmap.2 | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/man/man2/mmap.2 b/man/man2/mmap.2
index 20b94c243..925b18ffc 100644
--- a/man/man2/mmap.2
+++ b/man/man2/mmap.2
@@ -276,11 +276,11 @@ should check the returned address against the requested address.
This flag is used for stacks.
It indicates to the kernel virtual memory system that the mapping
should extend downward in memory.
-Touching an address in the "guard" page below the mapping will cause
-the mapping to grow by a page.
-This growth can be repeated until the mapping grows to within a
-page of the high end of the next lower mapping,
-at which point touching the "guard" page will result in a
+Touching an address below the mapping will cause the mapping to grow to
+accommodate the access.
+This growth can be repeated until the mapping crosses the stack size limit,
+or grows to within 256 pages of the high end of the next lower mapping,
+at which point accessing below the mapping will result in a
.B SIGSEGV
signal.
.TP
--
2.54.0
^ permalink raw reply related [flat|nested] 2+ messages in thread
* Re: [PATCH] man/man2/mmap.2: Document when MAP_GROWSDOWN does/doesn't trigger growth
2026-04-23 16:44 [PATCH] man/man2/mmap.2: Document when MAP_GROWSDOWN does/doesn't trigger growth Ben Kallus
@ 2026-05-04 13:00 ` Alejandro Colomar
0 siblings, 0 replies; 2+ messages in thread
From: Alejandro Colomar @ 2026-05-04 13:00 UTC (permalink / raw)
To: Ben Kallus; +Cc: linux-man
[-- Attachment #1: Type: text/plain, Size: 5688 bytes --]
Hi Ben,
On 2026-04-23T12:44:14-0400, Ben Kallus wrote:
> The man page states that MAP_GROWSDOWN can only cause a mapping to
> grow by a single page. This is incorrect; mappings can grow by many
> pages at a time, until reaching either the stack size limit or growing
> too close to another mapping.
>
> To observe that mappings can grow by more than one page, and that they
> are limited by the stack size limit, run the following C program with a
> stack size limit of 0x800000 bytes, and then again with a stack size limit
> of 0x801000 bytes, and observe that it segfaults as the comments describe.
Would you mind pasting a shell session that runs it with both stack
limits?
>
> > struct page {
> > char data[4096];
> > };
> > static_assert(sizeof(struct page) == 4096);
> >
> > void *const BASE_ADDRESS = (void *)0xabcdef000;
I'd prefer upper-casing the hex value, to differentiate it visually
from the 'x':
0xABCDEF000;
If this seems too packed, C23 now allows using a digit separator:
alx@devuan:~/tmp$ cat s.c
void *const BASE_ADDRESS = (void *)0xA'BCDE'F000;
alx@devuan:~/tmp$ gcc -Wall -Wextra -S s.c
alx@devuan:~/tmp$
> >
> > int main(int const argc, char const * const * const argv) {
Unrelated, but you can define int main(void) without parameters. That's
allowed by ISO C. Here's a quote of C23, but this is allowed by any
version of ISO C:
5.1.2.3.2 Program startup
1 The function called at program startup is named main.
The implementation declares no prototype for this function.
It shall be defined with a return type of int
and with no parameters:
int main(void) { /* ... */ }
or with two parameters
(referred to here as argc and argv,
though any names may be used,
as they are local to the function in which they are declared):
int main(int argc, char *argv[]) { /* ... */ }
or equivalent;6)
or in some other implementation-defined manner.
<https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3220.pdf#paragraph.5.1.2.3.2>
> > volatile struct page *p = mmap(
> > BASE_ADDRESS,
> > 1,
> > PROT_READ | PROT_WRITE,
> > MAP_ANONYMOUS | MAP_PRIVATE | MAP_GROWSDOWN | MAP_FIXED_NOREPLACE,
> > -1,
> > 0
> > );
> > // stack_limit=0x800000 // stack_limit=0x801000
> > (p - 2047)->data[0] = 0; // no segfault // no segfault
> > (p - 2048)->data[0] = 0; // segfault // no segfault
> > (p - 2049)->data[0] = 0; // segfault // segfault
> > }
>
> To observe that mappings stop growing when they get within 256 pages of
> the next lower mapping (instead of a single page, as the man page
> currently states), run the following program, and observe that it
> segfaults as the comments describe.
Would you mind running the program and pasting that here too?
>
> > struct page {
> > char data[4096];
> > };
> > static_assert(sizeof(struct page) == 4096);
> >
> > struct page *const BASE_ADDRESS = (void *)0xabcdef000;
> >
> > int main(int const argc, char const * const * const argv) {
> > volatile struct page *p = mmap(
> > BASE_ADDRESS,
> > 1,
> > PROT_READ | PROT_WRITE,
> > MAP_ANONYMOUS | MAP_PRIVATE | MAP_GROWSDOWN | MAP_FIXED_NOREPLACE,
> > -1,
> > 0
> > );
> >
> > struct page *p2 = mmap(
> > BASE_ADDRESS - 258,
> > 1,
> > PROT_READ | PROT_WRITE,
> > MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED_NOREPLACE,
> > -1,
> > 0
> > );
> >
> > // no segfault (causes p to grow by a page)
> > (p - 1)->data[0] = 0;
> >
> > // unmap the test page
> > munmap(p2, 1);
> >
> > // unmap the new page from p growing
> > munmap((struct page *)p - 1, 1);
> >
> > struct page *p3 = mmap(
> > BASE_ADDRESS - 257,
> > 1,
> > PROT_READ | PROT_WRITE,
> > MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED_NOREPLACE,
> > -1,
> > 0
> > );
> >
> > // segfault because p can't grow due to proximity to p3
> > (p - 1)->data[0] = 0;
> > }
>
> Fixes: 176b1a76 (2016-11-21; "mmap.2: Add (much) more detail on MAP_GROWSDOWN")
> Signed-off-by: Ben Kallus <benjamin.p.kallus.gr@dartmouth.edu>
> ---
> man/man2/mmap.2 | 10 +++++-----
> 1 file changed, 5 insertions(+), 5 deletions(-)
>
> diff --git a/man/man2/mmap.2 b/man/man2/mmap.2
> index 20b94c243..925b18ffc 100644
> --- a/man/man2/mmap.2
> +++ b/man/man2/mmap.2
> @@ -276,11 +276,11 @@ should check the returned address against the requested address.
> This flag is used for stacks.
> It indicates to the kernel virtual memory system that the mapping
> should extend downward in memory.
> -Touching an address in the "guard" page below the mapping will cause
> -the mapping to grow by a page.
> -This growth can be repeated until the mapping grows to within a
> -page of the high end of the next lower mapping,
> -at which point touching the "guard" page will result in a
> +Touching an address below the mapping will cause the mapping to grow to
> +accommodate the access.
> +This growth can be repeated until the mapping crosses the stack size limit,
> +or grows to within 256 pages of the high end of the next lower mapping,
> +at which point accessing below the mapping will result in a
Thanks! The diff seems reasonable. Please adjust the commit message
and resend.
Have a lovely day!
Alex
> .B SIGSEGV
> signal.
> .TP
> --
> 2.54.0
>
--
<https://www.alejandro-colomar.es>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-05-04 13:01 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-23 16:44 [PATCH] man/man2/mmap.2: Document when MAP_GROWSDOWN does/doesn't trigger growth Ben Kallus
2026-05-04 13:00 ` Alejandro Colomar
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox