为什么在每次运行而不是固定数量的堆栈使用量不同时发生堆栈溢出?

时间:2021-07-06 20:57:57

I am running a program with a recursive call on a Debian OS. My stack size is

我在Debian OS上运行一个带递归调用的程序。我的堆栈大小是

-s: stack size (kbytes)             8192

As far as I've learned, the stack size must be fixed, and should be the same that must be allocated to a program at every run unless it is explicitly changed with ulimit.

据我所知,堆栈大小必须是固定的,并且应该与每次运行时必须分配给程序的相同,除非使用ulimit显式更改。

The recursive function is a decrements a given number until it reaches 0. This is written in Rust.

递归函数是给定数字的递减,直到达到0.这是用Rust编写的。

fn print_till_zero(x: &mut i32) {
    *x -= 1;
    println!("Variable is {}", *x);
    while *x != 0 {
        print_till_zero(x);
    }
}

and the value is passed as

并且值传递为

static mut Y: i32 = 999999999;
unsafe {
    print_till_zero(&mut Y);
}

Since the stack allocated to the program is fixed, and theoretically must not change, I was expecting a stack overflow at the same value each time, but it is not, which means the stack allocation is variadic.

由于分配给程序的堆栈是固定的,理论上不能改变,我每次都希望堆栈溢出的值相同,但不是,这意味着堆栈分配是可变的。

Run 1:

运行1:

====snip====
Variable is 999895412
Variable is 999895411

thread 'main' has overflowed its stack
fatal runtime error: stack overflow

Run 2:

运行2:

====snip====
Variable is 999895352
Variable is 999895351

thread 'main' has overflowed its stack
fatal runtime error: stack overflow

Although the difference is subtle, shouldn't it be ideally causing the stack overflow at the same variable? Why is it happening at different times, implying different stack size for each run? This is not specific to Rust; a similar behavior is observed in C:

虽然差别很小但是不应该理想地导致堆栈在同一个变量上溢出?为什么它会在不同的时间发生,这意味着每次运行的堆栈大小不同?这不是Rust特有的;在C中观察到类似的行为:

#pragma GCC push_options
#pragma GCC optimize ("O0")
#include<stdio.h>
void rec(int i){
    printf("%d,",i);
    rec(i-1);
    fflush(stdout);
}
int main(){
setbuf(stdout,NULL);
rec(1000000);
}
#pragma GCC pop_options

Output:

输出:

Run 1:

运行1:

738551,738550,[1]    7052 segmentation fault

Run 2:

运行2:

738438,738437,[1]    7125 segmentation fault

1 个解决方案

#1


16  

Most probably this is due to ASLR.

最有可能的原因是ASLR。

The base address of the stack is randomized at each run to make certain types of exploits more difficult; on Linux this has a granularity of 16 bytes (which is the biggest alignment requirement on x86 and almost any other platform I know).

堆栈的基址在每次运行时随机化,以使某些类型的漏洞更加困难;在Linux上,它的粒度为16个字节(这是x86和我所知道的几乎任何其他平台上最大的对齐要求)。

On the other hand, the page size is (normally) 4 KB on x86, and the system detects the stack overflow when you touch the first forbidden page; this means that you'll always have available a partial page first (with the offset depending from ASLR), and then two full pages before the system detects a stack overflow. Hence the total usable stack size is at least the 8192 bytes you requested, plus the first partial page, whose available size is different at each run.1

另一方面,x86上的页面大小(通常)为4 KB,当您触摸第一个禁止页面时,系统会检测到堆栈溢出;这意味着您将始终首先获得部分页面(偏移量取决于ASLR),然后在系统检测到堆栈溢出之前有两个完整页面。因此,总可用堆栈大小至少是您请求的8192个字节,加上第一个部分页面,其可用大小在每次运行时都不同。


  1. All this in the "regular" case where the offset is nonzero; if you are very lucky and the random offset is zero you probably get exactly two pages.
  2. 所有这些都在“常规”情况下,其中偏移是非零的;如果你很幸运并且随机偏移为零,你可能会得到两页。

#1


16  

Most probably this is due to ASLR.

最有可能的原因是ASLR。

The base address of the stack is randomized at each run to make certain types of exploits more difficult; on Linux this has a granularity of 16 bytes (which is the biggest alignment requirement on x86 and almost any other platform I know).

堆栈的基址在每次运行时随机化,以使某些类型的漏洞更加困难;在Linux上,它的粒度为16个字节(这是x86和我所知道的几乎任何其他平台上最大的对齐要求)。

On the other hand, the page size is (normally) 4 KB on x86, and the system detects the stack overflow when you touch the first forbidden page; this means that you'll always have available a partial page first (with the offset depending from ASLR), and then two full pages before the system detects a stack overflow. Hence the total usable stack size is at least the 8192 bytes you requested, plus the first partial page, whose available size is different at each run.1

另一方面,x86上的页面大小(通常)为4 KB,当您触摸第一个禁止页面时,系统会检测到堆栈溢出;这意味着您将始终首先获得部分页面(偏移量取决于ASLR),然后在系统检测到堆栈溢出之前有两个完整页面。因此,总可用堆栈大小至少是您请求的8192个字节,加上第一个部分页面,其可用大小在每次运行时都不同。


  1. All this in the "regular" case where the offset is nonzero; if you are very lucky and the random offset is zero you probably get exactly two pages.
  2. 所有这些都在“常规”情况下,其中偏移是非零的;如果你很幸运并且随机偏移为零,你可能会得到两页。