使用 setrlimit() 设置堆栈大小并引发堆栈溢出/段错误

Posted

技术标签:

【中文标题】使用 setrlimit() 设置堆栈大小并引发堆栈溢出/段错误【英文标题】:Set stack size with setrlimit() and provoke a stack overflow/segfault 【发布时间】:2011-05-06 07:18:09 【问题描述】:

在下面给出的示例中,我尝试将堆栈大小设置为 1kb。

为什么现在可以在foo() 中分配大小为8kb 的整数数组?

#include <stdio.h>
#include <sys/resource.h>

void foo(void);

int main() 
 struct rlimit lim = 1024, 1024;

 if (setrlimit(RLIMIT_STACK, &lim) == -1)
  return 1;

 foo();

 return 0;


void foo() 
 unsigned ints[2048];

 printf("foo: %u\n", ints[2047]=42);

【问题讨论】:

谢谢,我现在很想知道为什么这不像 man(2) setrlimit 中所宣传的那样有效。幸运的是,gcc 允许您指定堆栈大小:) 一个问题被收藏的次数多于被点赞的次数——此时。很有趣。 【参考方案1】:

我认为setrlimit 移动了“资源指针”,但在您exec 一个新的程序副本之前不会应用新的限制。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/resource.h>

void foo(int chk) 
  unsigned ints[2048];
  ints[2047] = 42;
  printf("foo %d: %u\n", chk, ints[2047]);


int main(int argc, char **argv) 
  char *newarg[] =  "argv[0]", "one", "two" ;
  char *newenv[] =  NULL ;
  struct rlimit lim;

  newarg[0] = argv[0];
  getrlimit(RLIMIT_STACK, &lim);
  printf("lim: %d / %d\n", (int)lim.rlim_cur, (int)lim.rlim_max);
  switch (argc) 
    case 1: /* first call from command line */
      lim.rlim_cur = 65536;
      lim.rlim_max = 65536;
      if (setrlimit(RLIMIT_STACK, &lim) == -1) return EXIT_FAILURE;
      newarg[2] = NULL;
      foo(1);
      execve(argv[0], newarg, newenv);
      break;
    case 2: /* second call */
      lim.rlim_cur = 1024;
      lim.rlim_max = 1024;
      if (setrlimit(RLIMIT_STACK, &lim) == -1) return EXIT_FAILURE;
      foo(2);
      execve(argv[0], newarg, newenv);
      break;
    default: /* third call */
      foo(3);
      break;
  
  return 0;

还有一个测试运行:

$ ./a.out 林:8388608 / -1 富 1:42 林:65536 / 65536 富 2:42 被杀

我不知道为什么进程在打印限制之前(以及在调用 foo 之前)被终止。

【讨论】:

我怀疑类似,只是尝试了fork(),没有区别。我不明白为什么 setrlimit() 只影响通过 exec 而不是父进程产生的进程,但似乎确实如此。 使用 GDB 我在“foo 2: 42”行之后得到“程序正常退出” - 没有被杀死,没有段错误 @tur1ng:尝试在 main 开头添加 newarg[0] = argv[0];。我怀疑您的二进制文件不称为“a.out” 这是正确的 a.out。即使是ulimit -s 1 也不会导致错误。 @pmg,你用的是什么内核版本/操作系统专用酱?我想我们可能在这里谈论一个移动的目标:) 我也无法用 2.6.31 (Ubuntu) 重现你的结果【参考方案2】:

限制会立即设置,但仅在尝试分配新堆栈或尝试增加现有堆栈时检查。内核源代码上的 RLIMIT_STACK (or a LXR identifier search) 的 grep 应该会告诉您。

显然,堆栈的初始大小是文件名 + env 字符串 + arg 字符串加上在 setup_arg_pages 上分配的一些额外页面(2.6.33 1,2 中的 20 页,128 Kb 2.6.34 3)。

总结:

initial stack size = MIN(size for filename + arg strings + env strings + extra pages, MAX(size for filename + arg strings + env strings, RLIMIT_STACK))

在哪里

size for filename + arg strings + env strings <= MAX(ARG_MAX(32 pages), RLIMIT_STACK/4)

此外,带有 Ingo Molnar 的 exec-shield 补丁的内核(Fedora、Ubuntu 等)还有一个额外的 EXEC_STACK_BIAS “(多出 2MB 以涵盖随机化效果。)”,请参阅对新功能 over_stack_limit() 来自 acct_stack_growth() ([Ubuntu1], [Ubuntu2], [Ubuntu3])。

我已经编辑了原始程序来显示这个:

#include <stdio.h>
#include <sys/resource.h>

void foo(void);

int main(int argc, char *argv[]) 
        struct rlimit lim = 1, 1;


        if (argc > 1 && argv[1][0] == '-' && argv[1][8]=='l') 
                printf("limiting stack size\n");
                if (setrlimit(RLIMIT_STACK, &lim) == -1) 
                        printf("rlimit failed\n");
                        return 1;
                
        

        foo();

        return 0;


void foo() 
        unsigned ints[32768];

        printf("foo: %u\n", ints[2047]=42);

结果:

$./rl
foo: 42
$./rl -l
limiting stack size
Segmentation fault
$  

【讨论】:

不,实际上,我能够扩展现有堆栈。我现在就像一条狗,不会放过这个问题。 @Tim Post:你确定堆栈确实增长了吗?请参阅我编辑的答案,初始堆栈上有一些额外的空间。 是的,我将两种情况都扩大到 16k,结果相同。 @Tim Post:在 2.6.33-x86 上将其扩展为 > 80 Kb,在 2.6.34+ 上扩展至 > 128 Kb 尝试在Stack *** 修正后设置rlimit_stack 可能会导致失败或相关问题。另请参阅红帽 Issue 1463241

以上是关于使用 setrlimit() 设置堆栈大小并引发堆栈溢出/段错误的主要内容,如果未能解决你的问题,请参考以下文章

如何为 sbt 设置堆大小?

线程的堆栈

Windows 上的进程资源限制

何时堆内存优先于堆栈内存

JVM系列第11讲:JVM参数之堆栈空间配置

堆栈和堆内存的大小[重复]