Re: StackGuard: Automatic Protection From Stack-smashing Attacks

From: Tim Newsham (newshamat_private)
Date: Fri Dec 19 1997 - 10:55:31 PST

  • Next message: Steve Bellovin: "Re: StackGuard: Automatic Protection From Stack-smashing Attacks"

    > StackGuard: Automatic Detection and Prevention of Buffer-Overflow Attacks
    
    >From the online paper:
    > StackGuard detects and defeats stack smashing attacks by protecting the
    > return address on the stack from being altered.  StackGuard has two
    > mechanisms to protect the return address:  one provides greater assurance,
    > and the other provides greater performance.
    
    You are only protecting the return address.  This means many programs
    will still be vulnerable to overflow attacks.  In particular you
    don't protect the overflow of locals in a procedure, nor the overflow
    of globals in the data segment or heap.  While it does stop the
    "cookbook" stack overflow attacks,  it does not really put an end
    to the problem.  Consider for example the following (contrived but
    not entirely fictional) examples:
    
          int save_uid;
          char buf[10];
    
          save_uid = getuid();
          setuid(0);
          fp = fopen("input", "r");
          fscanf(fp, "%s", buf);
          setuid(save_uid);
    
    overflowing the buffer will allow the user to increase his priveledge
    for the duration of the program execution, which may be a very bad thing.
    
          int x, *ptr;
          char buf[10];
    
          ptr = get_some_pointer();
          x = compute_some_value();
          strcpy(buf, getenv("SOME_ENV"));
          *ptr = x;
    
    overflowing the buffer allows the user to overwrite any 4 bytes
    in the entire program.  Clearly not good.
    
          char cmdbuf[100], username[10];
    
          cmdbuf[0] = 0;
          strcat(cmdbuf, "/bin/cat ");
          strcat(cmdbuf, filename);
          getusername(username);
          if(strcmp(username, "foobar") == 0)
              return;
          system(cmdbuf);
    
    overflowing the username buffer allows an attacker to overwrite the
    buffer that gets past to system().  Not good.
    
    These are just examples of attacks involving locals.  Attackers can
    also overwrite globals that are in the data and bss segments as
    well as globals allocated dynamically on the heap.  The end result
    is that an attacker can overwrite data in a security critical program
    that was never intended to be user changeable.  This changes the
    entire security dynamics of the program.  The program may still
    be secure after the change, or it may not be.  The attackers job
    is then to find out how to manipulate the program given the
    constraints of the buffer he can overflow.
    
    
    Further in the paper you discuss two techniques that can be used:
    
    > The higher performance StackGuard mechanism is to instrument the stack
    > frame with a "canary" word laid right next to the return address on
    > the stack.  If an attacker attempts to smash the stack and change the
    > return address, the attacker will necessarily overwrite the canary word as
    > well.  The code emitted by the compiler to do function returns is enhanced
    > to check the integrity of the stack by looking for the canary word:  if
    > the canary word has been changed, the program aborts instead of yielding
    > control to the attacker.  The canary word is selected at random when
    > the process starts, to make it difficult for the attacker to guess the
    > canary value.
    
    Obviously this technique is not totally secure (which you note by
    calling the second technique "high assurance").  By adjusting the
    size of the "canary" you can probably reduce the chances of effective
    guessing of the canary pretty low (assuming you have a good source
    of true randomness).  Obviously if the canary is a small value of
    say 16 bits, an attacker can trivially brute force it (setting off
    alarms along the way.. but hey, he can clean up afterwards).
    I assume you picked an integer value that fits nicely on the stack
    or a 64 bit quantity for extra assurance.
    
    A clever attacker may be able to trick the program into disclosing
    the canary value though.  For example if he has more than one
    overflow, or an overflow that is repeated several times with different
    input.  In this case he can overflow the buffer right up to the
    value before the canary,  watch the resulting behavior of the program
    to infer the canary, and then construct a second overflow with the
    proper canary value in it.  As an example:
    
          char filename[100], username[10];
    
          strcpy(filename, queuename);
          strcat(filename, ".dat");
          getusername(username);
          if(!authorized_user(userlist, username))
              return 0;
          fd = open(filename, O_READ);
          if(fd == -1) {
              perror(filename);
              return -1;
          }
    
    An attacker can cause "filename" to extend right up to the
    canary value (and then continue running till the next nul byte on
    the stack) to get the canary value.
    
    > The higher assurance StackGuard mechanism utilizes a tool from the
    > Synthetix  project called  MemGuard.  MemGuard provides fine-grained
    > memory protection, down to the size of a single word.  The MemGuard
    > variant of StackGuard applies MemGuard's protect() to the return
    > address when a function starts, and releases the protection of the return
    > address when the function finishes.  If any part of the program ever tries
    > to alter the return address (which is not normal behavior) then the MemGuard
    > memory protection mechanism detects the write to protected
    > data, and aborts the program.
    
    A much safer technique than the canary technique, but it comes at
    a considerable performance penalty.  I may be willing to let some
    small utilities on my system run with this loss of performance.
    I doubt many people would make all the security critical programs
    on their system run with this degraded performance,  especially
    large performance critical daemons like sendmail and httpd.
    
    
    So in summary:  You reduced, but did not eliminate, the number
    of buffer overflow problems that allow a user to hijack the program.
    The first of your techniques does not eliminate as many of these
    problems as the second technique, but the second technique is
    unattractive due to its performance impact.  Even with the best
    of these techniques implemented, there will still be a number
    of security problems left in the system due to buffer overflows.
    
    Comments:  If you fix the root of the problem, you can eliminate
    all the problems.  If you make a fix that combats one of the
    common attacks against a problem,  you may make quicker progress,
    but you cannot address the entire problem.  In addition you may
    affect performance (memory protection) or functionality (allowing
    execution of code on the stack).
    
    My personal feelings on the recent proposals for fixing
    "the overflow problem" is that I don't like them.  They all
    seem hacky to me, and all claim to be a silver bullet to finally
    put an end to the problem.  I much rather see the original problems
    fixed,  a solution that is much more aesthetically pleasing to
    me.  On the other hand the proposals do reduce the number of
    attacks, and buy time until attackers get more sophisticated
    in their exploits.
    
    > Crispin Cowan, Research Assistant Professor of Computer Science
    > PO Box 91000                   | digital: crispinat_private
    
                                            Tim N.
    



    This archive was generated by hypermail 2b30 : Fri Apr 13 2001 - 13:37:02 PDT