Beginning of stack on Linux
Asked Answered
V

1

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
Vortex answered 5/2, 2019 at 21:51 Comment(12)
Do you have ASLR on?Edith
Have you looked at what's on the stack with a debugger?Liquid
@ASLR All addresses change with each run so I suppose yes.Vortex
(By the way, you can use /proc/self/maps.)Warring
(Online demo: wandbox.org/permlink/CLW9SlPOy5rTg7mp)Warring
You should probably take a look at your platform's startup code (crt*.o) to see what your actual entry point function is doing.Warring
Oh, here, if you pick a different local variable, you get something more expected: wandbox.org/permlink/tO5WuOAb3SdCed3vWarring
@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.Vortex
Did you see that I changed it from C++ to C? That way I avoided some dynamic initialization preambling.Warring
@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.Vortex
@PSkocik Ah, ok. I think, it does. Did you try to disable ASLR and see, if there is still a variable offset?Weka
@Weka Nice idea! Turning it off constantizes (is that a word?) the offset to 1 page.Vortex
W
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.

Weka answered 5/2, 2019 at 23:58 Comment(1)
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 calls from CRT startup code that use a few slots of stack space.Davit

© 2022 - 2024 — McMap. All rights reserved.