8

I thought I could get the beginning of my process stack by taking the address of a variable in main and rounding up to a page boundary (considering that my stack grows down).

I compared this to the boundary reported by /proc/self/maps and it's always off by 1, 2, or 3 pages (4096 bytes per page), never by a different offset. The difference varies with each run and this C program used in the following (messy, not minimalistic) pipeline demonstrates the difference.

stacksz.c:

#include <stdlib.h>
#include <unistd.h>
#include <stdint.h>
#include <stdio.h>
#define CAT "cat /proc/XXXXXXXXXXX/maps"
#define CATP "cat /proc/%ld/maps"
#define MASK ((sizeof(char)<<12)-1)

int main()
{
    uintptr_t  top = (uintptr_t)&top + MASK & ~MASK;

    char cat[sizeof CAT];
    sprintf(cat,CATP,(long)getpid());
    if(system(cat)) return 1;

    printf(" %lx stack\n", top);
    return 0;
}

bash pipeline:

gcc stacksz.c && echo "$(( $(./a.out |grep stack |tr '-' ' ' |cut -d' ' -f2 |sed 's/^/0x/'|tr '\n' -|sed 's/-$//') ))"

I'm curious if anyone can explain this phenomenon. The machine is Linux precision 4.15.0-43-generic #46-Ubuntu SMP x86_64.

I got the following offset distribution in 1000 runs:

4096 195
8192 490
12288 315
Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
Petr Skocik
  • 58,047
  • 6
  • 95
  • 142
  • 1
    Do you have ASLR on? – Eugene Sh. Feb 05 '19 at 21:53
  • Have you looked at what's on the stack with a debugger? – zneak Feb 05 '19 at 21:53
  • @ASLR All addresses change with each run so I suppose yes. – Petr Skocik Feb 05 '19 at 21:54
  • (By the way, you can use `/proc/self/maps`.) – Kerrek SB Feb 05 '19 at 22:00
  • (Online demo: https://wandbox.org/permlink/CLW9SlPOy5rTg7mp) – Kerrek SB Feb 05 '19 at 22:07
  • You should probably take a look at your platform's startup code (crt*.o) to see what your actual entry point function is doing. – Kerrek SB Feb 05 '19 at 22:08
  • 1
    Oh, here, if you pick a different local variable, you get something more expected: https://wandbox.org/permlink/tO5WuOAb3SdCed3v – Kerrek SB Feb 05 '19 at 22:11
  • @KerrekSB Tried your program locally 1000 times. I'm never getting a match like in the link you posted. It's always off by 1, 2, or 3 pages on my machine. Thanks for looking at it, anyways. I don't really care about it deeply, but I think it's interesting. – Petr Skocik Feb 05 '19 at 22:26
  • Did you see that I changed it from C++ to C? That way I avoided some dynamic initialization preambling. – Kerrek SB Feb 05 '19 at 22:42
  • @Ctx All the absolute addresses get randomized and my `cat /proc/sys/kernel/randomize_va_space` is at `2` (full randomization). I don't know what's up with the first variables in main being variably 1-3 pages away from the start of the stack, but I don't suppose it has much to do with ASLR. – Petr Skocik Feb 05 '19 at 23:37
  • @PSkocik Ah, ok. I think, it does. Did you try to disable ASLR and see, if there is still a variable offset? – Ctx Feb 05 '19 at 23:49
  • @Ctx Nice idea! Turning it off constantizes (is that a word?) the offset to 1 page. – Petr Skocik Feb 05 '19 at 23:57

1 Answers1

5

ASLR first completely randomizes the stack location in the virtual memory. But it does more: It also randomizes the stack pointer relative to the top of the stack mapping!

From the linux source code:

unsigned long arch_align_stack(unsigned long sp)
{
        if (!(current->personality & ADDR_NO_RANDOMIZE) && randomize_va_space)
                sp -= get_random_int() % 8192;
        return sp & ~0xf;
}

Here, if ASLR is active, the stack pointer is reduced by 0-8192 bytes and then 16 bytes aligned. This explains the variable offset of 1-3 pages.

Ctx
  • 18,090
  • 24
  • 36
  • 51
  • Other things that move RSP: there has to be space for the strings pointed to by `argv[]` and `envp[]` somewhere, perhaps at the top of the mapping. And the arrays of pointers go on the stack directly above the stack pointer value on entry to user-space. (SP points at `argc`; above that is `argv[0]`, then `argv[1]` etc. then 0, then `envp[0]`, etc.) Also, the OP's program reports RSP in `main`, after which is reached after a couple `call`s from CRT startup code that use a few slots of stack space. – Peter Cordes Sep 29 '22 at 19:18