13

In the given example below I try to set the stacksize to 1kb.

Why is it now possible to allocate an array of ints on the stack with size 8kb in foo() ?

#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);
}
tchrist
  • 78,834
  • 30
  • 123
  • 180
tur1ng
  • 3,139
  • 5
  • 24
  • 31
  • Thank you, I am now addicted to finding out why this doesn't work as advertised in man(2) setrlimit. Fortunately, gcc lets you specify the stack size :) – Tim Post Nov 07 '10 at 16:26
  • A question favorited more often than it was upvoted—at this time. Interesting. – Pascal Cuoq Nov 07 '10 at 17:39

2 Answers2

9

The limit is set immediately but only checked when trying to allocate a new stack or trying to grow the existing stack. A grep for RLIMIT_STACK (or a LXR identifier search) on the kernel sources should tell.

Apparently, the initial size of the stack is whatever is needed to the filename + env strings + arg strings plus some extra pages allocated on setup_arg_pages (20 pages in 2.6.33 1,2, 128 Kb on 2.6.34 3).

In summary:

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

where

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

Additionally, kernels with Ingo Molnar's exec-shield patch (Fedora, Ubuntu, ...) have an additional EXEC_STACK_BIAS "(2MB more to cover randomization effects.)", see the call to the new function over_stack_limit() from acct_stack_growth() ([Ubuntu1], [Ubuntu2], [Ubuntu3]).

I've edited the original program to show this:

#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);
}

Which results in:

$./rl
foo: 42
$./rl -l
limiting stack size
Segmentation fault
$  
ninjalj
  • 42,493
  • 9
  • 106
  • 148
  • 1
    No, actually, I was able to grow an existing stack. I am now like a dog that won't let go of a bone with this problem. – Tim Post Nov 07 '10 at 16:25
  • @Tim Post: are you sure the stack did grow? See my edited answer, there is some extra space on the initial stack. – ninjalj Nov 07 '10 at 18:21
  • Yes, I expanded both cases to 16k, same result. – Tim Post Nov 07 '10 at 18:48
  • @Tim Post: expand it to > 80 Kb on 2.6.33- x86 and > 128Kb on 2.6.34+ – ninjalj Nov 07 '10 at 18:55
  • And again, same behaviour, and we're not reproducing the issue of setrlimit() returning success when setting a 2k stack ceiling :) – Tim Post Nov 07 '10 at 19:15
  • @Tim Post: can you try my modified test? – ninjalj Nov 07 '10 at 19:53
  • I have to use a value `unsigned ints[522100];` to generate a "segmentation fault" on my system. BUT: it occur not on every run - looks randomly. – tur1ng Nov 07 '10 at 21:53
  • @tur1ng: what is your kernel version? arch? pagesize? contents of setup_arg_pages() on fs/exec.c on your kernel sources? – ninjalj Nov 07 '10 at 22:14
  • @tur1ng: About the randomness, see my comment about `arch_align_stack()` (called from setup_arch_pages and also later when creating argv+envp+auxv arrays on the binfmt handler, e.g: fs/binfmt_elf.c for ELF) to pmg's answer. – ninjalj Nov 07 '10 at 22:17
  • @ninjalj: Standard Ubuntu 10.04 64bit with 2.6.35-22-generic kernel and a pagesize of 4mb. – tur1ng Nov 08 '10 at 20:20
  • @tur1ng: exec-shield adds EXEC_STACK_BIAS (2 MB) to the stack, I'll update the answer. – ninjalj Nov 08 '10 at 20:48
  • 1
    Attempting to set `rlimit_stack` after [Stack Clash](http://www.openwall.com/lists/oss-security/2017/06/19/1) remediations may result in failure or related problems. Also see Red Hat [Issue 1463241](https://bugzilla.redhat.com/show_bug.cgi?id=1463241) – jww Jun 21 '17 at 16:18
4

I think setrlimit moves the "resource pointers" but doesn't apply the new limits until you exec a new copy of the program.

#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;
}

And a test run:

$ ./a.out 
lim: 8388608 / -1
foo 1: 42
lim: 65536 / 65536
foo 2: 42
Killed

Why the process gets killed before printing the limits (and before calling foo), I don't know.

pmg
  • 106,608
  • 13
  • 126
  • 198
  • I suspected similar and just tried with `fork()`, which made no difference. I can't understand why setrlimit() only effects processes spawned via `exec` and not the parent, but that does appear to be the case. – Tim Post Nov 07 '10 at 15:31
  • With GDB I get 'Program exited normally' after the "foo 2: 42" line - no killed, no segfault – tur1ng Nov 07 '10 at 15:46
  • @tur1ng: try adding `newarg[0] = argv[0];` at the beginning of main. I suspect your binary is not called "a.out" – pmg Nov 07 '10 at 15:50
  • It's the correct a.out. Even a `ulimit -s 1` will not result in an error. – tur1ng Nov 07 '10 at 16:21
  • @pmg, what kernel version / OS special sauce are you using? I think we might be talking about a moving target here :) I also can't reproduce your results with 2.6.31 (Ubuntu) – Tim Post Nov 07 '10 at 16:24
  • I'm using Debian 64-bit 2.6.32. I just rerun the program twice and got the stack limit once (the program printed `lim: 1024 / 1024` before the "Segmentation fault") and the same behavior as above (the "Killed" output) the other time. Apparently the "killed" output is from `ld` or `libc`: my kern.log file has lines pointing to those utilities – pmg Nov 07 '10 at 16:57
  • So we draw straws to talk to Ulrich? Yeah, umm, nah. This is indeed a bug. – Tim Post Nov 07 '10 at 18:47
  • @Tim Post: What is a bug? See the explanation of initial stack size on my answer, even on 2.6.31 you should have plenty of initial stack space even after using space for auxp, env pointers, argv pointers, argc, and misc libc+ld.so initialization. OTOH the initial stack size may change a little from run to run due to stack ASLR. – ninjalj Nov 07 '10 at 19:04
  • @pmg: I'll explain what happens on your test. Have a look at http://lxr.free-electrons.com/source/fs/exec.c?v=2.6.32#L633 , lines up to the `expand_stack()` invocation. First run of the program is created with a stack size sufficient for args + env strings + 20 extra pages. Second run is created with a stack size of MAX(size for args + env strings, 65536) (args, env and filename are put onto stack before `setup_arg_pages()`). Third run is created with a stack size enough for args + env strings, but not much more, and crashes really soon. – ninjalj Nov 07 '10 at 21:04
  • @pmg: Actually, the third run probably crashes while creating the auxv array, env pointers, argv pointers and argc. – ninjalj Nov 07 '10 at 21:27
  • @ninjalj: the usual outcome is for the program to crash on the third run (before the printf in main) with a "Killed" message; but I've had a few runs crashing with "Segmentation fault" before and after the printf in main. – pmg Nov 07 '10 at 21:36
  • @pmg: When the crash happens exactly probably depends on `arch_align_stack()` (see `/arch/x86/kernel/process_32.c`). – ninjalj Nov 07 '10 at 22:01
  • On CentOS 5.8 with kernel 2.6.18 I use `RLIMIT_AS` since `RLIMIT_DATA` appears to have no effect. – Daniel Apr 30 '13 at 10:28