OpenBSD Journal

Is Your Stack Protector Working?

Contributed by tbert on from the no-slack-stack dept.

OpenBSD developer Ted Unangst (tedu@) recently wrote a blog post titled, "Is Your Stack Protector Working?" and with permission it's reposted below:

Veracode has a new blog post "A Tale of Two Compilers" about differing behavior when two compilers are faced with a subtle buffer overflow. It's somewhat tangential to the main point, but I noticed that even though the compilers Veracode tested had stack overflow protection enabled, neither detected the bug or prevented the exploit. Detection and prevention of precisely this bug was a headline feature of the original ProPolice implementation. The version of gcc(1) used in OpenBSD has changed several times since then, so I tested it to make sure it still works.

code

#include <stdio.h>

int main(int argc, char *argv[]) {
  int x = 0xabad1dea; // the best number.
  char buf[32];

  scanf("%32s", buf);
  printf("%s 0x%x\n", buf, x);
  return 0;
}

The bug is that scanf(3) will overflow buf and write a single nul byte into x, changing the value to 0xabad1d00. As Veracode explains, whether this actually happens depends on padding and alignment. Whether the stack protector can detect the bug also depends on whether the compiler reorders local variables so that overflows are more likely to be detected.

results

> gcc -o scanf scanf.c
> ./scanf
1234567890123456789012345678901
1234567890123456789012345678901 0xabad1dea
> ./scanf 
12345678901234567890123456789012
12345678901234567890123456789012 0xabad1dea
Abort trap

As soon as we overflow the buffer by a single byte, the bug is detected. Also notice that the value of x has not been corrupted. On OpenBSD the stack protector does a little more work. Larger variables and arrays like buf are moved to the top of the stack frame, above variables like x. This prevents the typical overflow from hitting them. The stack protector cookie is also placed directly after the end of the last variable, with no padding. Even if you change the size of buf to 33, it will be aligned such that a one byte overflow can be detected.

caveats
The stack protector can only work if the function returns. Had we called exit instead of returning from main, the bug is not detected. The stack protector can only do so much to arrange buffers. If there are two char arrays in the function, only one of them will be next to the overflow detecting cookie.

fallout
Whenever we add a new program correctness security measure (stack protector, malloc(3) assertion, mmap(2) randomization) to OpenBSD, one of the things we check for is that existing programs stop working. We know that existing software has lots of bugs. If nothing breaks, it means the feature isn't working.

(Comments are closed)


Comments
  1. By Peter J. Philipp (pjp) pjp@solarscale.de on http://centroid.eu

    Hi!

    Why does tedu@ get an Abort trap and I don't? I'm using his program and thankfully the bad idea is not overwritten... however my program continues...and doesn't trap...

    jupiter$ ./stack
    12345678901234567890123456789012213234234234
    12345678901234567890123456789012 0xabad1dea
    jupiter$

    jupiter$ sysctl kern.version
    kern.version=OpenBSD 5.4-stable (GENERIC.MP) #0: Fri Nov 15 09:28:01 CET 2013
    root@jupiter.centroid.eu:/usr/src/sys/arch/amd64/compile/GENERIC.MP

    does having gone to -stable cause that?

    High Regards,

    -peter

    Comments
    1. By Otto Moerbeek (otto) on http://www.drijf.net

      Indeed, amd64 (current) does not abort here. Possibly it has some different stack alignment rules. loongson does abort here.

      Comments
      1. By tedu (76.99.34.87) on

        > Indeed, amd64 (current) does not abort here.

        A bug!

        Comments
        1. By Anonymous Coward (64.20.30.66) on

          > > Indeed, amd64 (current) does not abort here.
          >
          > A bug!

          Not that I'm calling it a bug, but wouldn't the expected behavior be to abort? I'm very interested in how this is handled between architectures.

          Comments
          1. By Otto Moerbeek (otto) on http://www.drijf.net

            > > > Indeed, amd64 (current) does not abort here.
            > >
            > > A bug!
            >
            > Not that I'm calling it a bug, but wouldn't the expected behavior be to abort? I'm very interested in how this is handled between architectures.

            Stack protection only protects the return address and frame pointer. There's no way it will catch any stack based overflow. Due to alignment rules on amd64, the buffer will live further in the stack frame, hence the one byte overflow just falls into the alignment gap.

            i386 allows it's stack frame to be packed tight.

            Comments
            1. By Anonymous Coward (71.226.166.233) on

              > Stack protection only protects the return address and frame pointer. There's no way it will catch any stack based overflow. Due to alignment rules on amd64, the buffer will live further in the stack frame, hence the one byte overflow just falls into the alignment gap.
              >
              > i386 allows it's stack frame to be packed tight.

              Thanks, Otto!

    2. By Anonymous Coward (213.136.42.60) on

      > Why does tedu@ get an Abort trap and I don't? I'm using his program and thankfully the bad idea is not overwritten... however my program continues...and doesn't trap...

      i386 (tested 5.4 in kvm) does Abort trap too.

Latest Articles

Credits

Copyright © - Daniel Hartmeier. All rights reserved. Articles and comments are copyright their respective authors, submission implies license to publish on this web site. Contents of the archive prior to as well as images and HTML templates were copied from the fabulous original deadly.org with Jose's and Jim's kind permission. This journal runs as CGI with httpd(8) on OpenBSD, the source code is BSD licensed. undeadly \Un*dead"ly\, a. Not subject to death; immortal. [Obs.]