Slashdot is powered by your submissions, so send in your scoop

 



Forgot your password?
typodupeerror
×

How Do You Know Your Code is Secure? 349

bvc writes "Marucs Ranum notes that 'It's really hard to tell the difference between a program that works and one that just appears to work.' He explains that he just recently found a buffer overflow in Firewall Toolkit (FWTK), code that he wrote back in 1994. How do you go about making sure your code is secure? Especially if you have to write in a language like C or C++?"
This discussion has been archived. No new comments can be posted.

How Do You Know Your Code is Secure?

Comments Filter:
  • by forgoil ( 104808 ) on Monday January 08, 2007 @07:24AM (#17506428) Homepage
    In this case it doesn't in one important way. Programming is the same regardless of language, since humans are the same regardless of language. What you need to write good software (again, why just secure? Why focus on a certain aspect, why not just generalize?) is skills/knowledge and good habits. My best advice here is to make sure you give yourself good coding habits. Don't say things like "I'll clean that up later" or "I will add error checks later" or something equally damaging. Give yourself good, sensible, habits and follow them. Any average programmer must know what buffer overflow means, and how to correct it. You can't even be an average programmer unless you know. So why is such insecure code written in C/C++ then? My thinking is plain mistakes and bad habits.
  • by Berkana ( 471619 ) on Monday January 08, 2007 @07:30AM (#17506466) Homepage
    If you program using strictly functional programming, you can not only verify that your code is 100% secure, but you can even automate the process. (Preferably in a functional programming language such as Scheme, caml, Haskel, LISP, or Erlang; imperative languages make it very difficult/slow to do with functions what functional languages do very naturally and easily.) Purely functional code can be subjected to automated code auditing easily, whereas code auditing imperative code cannot be guaranteed to catch every bug and unintentionally available abuse.

    Here's why, and why just about any computational problem can be solved using FP (functional programming):
    Functional languages conform to lambda calculus, which has been shown to be Turing equivalent, which means that any program that can be computed on a Turing machine can be solved using Lambda calculus. So long as you program using strictly functions, your program can be verified according to the rules of lambda calculus, and the verification would be as sure as a mathematical proof. This is the only sure way I know of really knowing with mathematical certainty that your application is secure.

    Pure functional programming has no assignment statements; there are no state changes for you to keep track of in your program, and in many cases abuses resulting unintended changes of state are the root of security problems. This is not to say that there is no state in functional programming; the state is maintained through function call parameters. (For example, in an imperative programming language, iteration loops keep track of a state variable that guides the running of the loop, whereas a functional program never actually keeps track of state with a variable that changes value; a functional program would carry out iteration by recursion, and the state is simply kept as a parameter passed to each call of the function. No variable with changing state is ever coded.)

    Since functional programs lack assignment statements, and assignment statements make up a large fraction of the code in imperative programs, functional programs tend to be a lot shorter for the same problem solved. (I can't give you a hard ratio, but depending on the problem, your code can be up to 90% shorter when described functionally.) Shorter code is easier to debug, which helps in securing code. The reason functional code is so much shorter is that functional programing describes the problem in terms of functions and composition of functions, whereas imperative code describes a step by step solution to the problem. Descriptions of problems in terms of functions tend to be far shorter than algorithmic descriptions of solving them, which is required in imperative code.

    Here's the biggest benefit of managing complexity with functional programming: as a coder, you NEVER have to worry about state being messed with. The outcome of each function is always the same so long as the function is called with the same parameters. In imperative programming as done in OOP, you can't depend on that. Unit testing each part doesn't guarantee that your code is bug free and secure because bugs can arise from the interaction of the parts even if every part is tested and passed. In functional programming, however, you never have to deal with that kind of problem because if you test that the range of each function is correct given the proper domain, and pre-screen the parameters being passed to each function to reject any out-of-domain parameters, you can know with certainty where your bugs come from by unit testing each function.

    If you need to guarantee the order of evaluation (something that critics of FP advocates sometimes use to dismiss FP advocacy), you can still use FP and benefit: in functional programming, order of evaluation can be enforced using monads. Explaining how is beyond the scope of a mere comment though, but in any case, if you need really reliable code, consider using a functional programming style.

    I can't do justice to the matter here; for more information, see th
  • by Berkana ( 471619 ) on Monday January 08, 2007 @07:48AM (#17506574) Homepage
    If you want to learn about Lambda Calculus (which was developed by Alonzo Church, a contemporary of Allan Turing), Wikipedia is a good place to start (http://en.wikipedia.org/wiki/Lambda_calculus [wikipedia.org] ), but mastering Lambda Calculus is not necessary; first master a functional programming language, and a lot of the lambda calculus will be made easier.

    To summarize, here's how you verify with mathematical certainty that a functional program is secure:
    1. You use purely functional code; that guarantees that there are no changes of state involved in the operation of your program.
    2. you unit test each function to make sure that given the correct domain/scope, their return values are always conforming to the desired range (and I don't just mean numbers when I say "range"; I mean correct data formatting, list/tree formatting, data structures, etc.), and you set up input filters that exclude any call parameters that are not part of your desired function domain.
    3. You check to see what functions call which functions, and make sure that they never call a function with parameters that are incorrectly formatted or out of the correct domain
    4. You make sure that every function and every constant is properly scoped.

    That's the gist of it. Anything more on this topic, such as automatic code auditing with the certainty of mathematical proofs (by means of lambda calculus proofs) is beyond my expertise. I just know that it's possible to truly secure functional code with mathematical certainty, whereas with imperative code, you can only be sure that your code has not yet failed or exposed a rare bug or failure condition.
  • by Anonymous Coward on Monday January 08, 2007 @07:57AM (#17506612)
    Don't "roll your own" security unless absolutely necessary. Find someone else's implementations and work with those.

    This is correct for crypto, wrong for everything else. I've often replaced third party authentication code that was above and beyond what was required in terms of complexity.

    Design the code for security, code to that design. I've seen of security bugs creep into code because it was never designed to be secure.

    In principle I agree, in the real world it's about finding a balance. You can't design away security bugs and you can't spend 60% of your runtime stuck in input validation and security procedures.

  • by Anonymous Coward on Monday January 08, 2007 @07:59AM (#17506624)
    max planck, the famous german physicist whoose name is immortalized in the planck, once observed that it is not tthat scientists retrain themselves as theorys become obsolete, but that the new genrations don't learn the errors of hte past
  • 3 Things (Score:1, Interesting)

    by Anonymous Coward on Monday January 08, 2007 @08:00AM (#17506638)
    a) good coding practices
    b) formal peer reviews for pre-design, design, code, test specifications, and test results
    c) Purify!!! http://www-306.ibm.com/software/awdtools/purify/ [ibm.com] A license for every developer AND tester!

    I haven't written any code since 1999, but that was how I setup the development team for that company. The reviews also are a form of cross training and team building. Nobody is perfect and showing our individual errors helps everyone fit in. OTOH, there was 1 guy who clearly didn't understand header files and was labled "Mr. Header" for almost a year. After the first 1 week of taunts by his peers, he quickly learned when and how to avoid putting too many header files into his code.

    Other code issues were discovered and learned by the entire team. We didn't hide errors, we published them within the team and never told management anything about who caused what to happen.
  • by joss ( 1346 ) on Monday January 08, 2007 @08:08AM (#17506692) Homepage
    Helped a lot for this kind of thing. The tool went downhill quite
    a long way but its still useful. Electric fence helps too.
    Then a lot of old fashioned software engineering.. use raw arrays
    as little as possible, add bounds checking to std::vector [] if you
    feel inclined, use gprof to identify any code not being excercised
    by your unit tests [you do have unit tests, right]. Lastly, actually
    read the darn code and make sure anytime you are using raw arrays
    you check the size.
  • by TheRaven64 ( 641858 ) on Monday January 08, 2007 @08:29AM (#17506804) Journal
    Don't trust your own code. The reason OpenBSD is secure is party because the code is security audited constantly, but also partly because much of the system is written on the assumption that the rest of it is buggy. Isolate your code as much as possible. If you can get away with it, fork off separate modules and communicate between them over a well-defined interface. Validate everything that is received. Don't let any of your code run with more privileges than it needs; make good use of chroot and setuid. If you don't need to be able to access anything on the filesystem then the first thing you should do is make an empty directory and chroot there; that way an attacker who compromises your code can't do anything useful.

    The best advice I read was from the Erlang documentation. It suggested that you program defensively on a system level, but not on a module level. If a module receives input it can't understand, or thinks it is in an invalid state, the correct behaviour is for it to crash. A system of monitors should deal with failures of components, because they can determine how the failure will affect other components. There has only been one remote root hole in OpenBSD in the last ten years, and it would have been avoided if the OpenSSH developers had used this principle.

  • by TheRaven64 ( 641858 ) on Monday January 08, 2007 @08:33AM (#17506816) Journal

    Remember, deep down inside the other languages, there often is a compiler, library, interpreter, etc written in C/C++.
    Not in the case of Smalltalk. The Squeak VM is written in a subset of Smalltalk which is compiled by a compiler written in Smalltalk into native code. Most of the Java VM and compiler, I believe are written in the same way.

    There are plenty of libraries for handling strings and memory allocation in C, in C++ there are string and storage classes that do as much or as little checking as you need.
    Once you have added enough to a language that it no longer looks like the original, then it's time to ask yourself if you picked the correct language for the job to start with. I could write a dynamic dispatch mechanism with inheritance for use in C, but I would start to wonder if I shouldn't really have written the entire project in Objective-C instead.
  • by Anonymous Coward on Monday January 08, 2007 @09:42AM (#17507306)
    I don't know that I'd say the STL helps you a lot in this way. It's great, though, I like it a lot. Buffer overflows and stack overflows are side-effects of memory management. If you don't want to or cannot manage memory, or aren't sure, then you really want to do your project in Java or OCaml or something else. That's the issue.


    C and C++ coders have jammed allocated memory on to the stack for years to make it "garbage collected" when the frame pops off the stack. That also opens up the very easy to exploit stack overflows, if you put memory on the stack and then do any unbounded or improperly bounded I/O to it, you're done. The STL provides smart pointers to allow you to manage heap as if it were on the stack but the problem remains that you have to manage it.


    The word on the street, and I discount it, by a lot of the hackery types is that the use of C++ with any overflow is easier to exploit than a normal C application because it has the virtual function look up table which basically provides an easy one-stop shop where you can get a pointer to almost any other subroutine in the program. It makes sense logically but I don't know of any exploits that make use of it

  • by CDarklock ( 869868 ) on Monday January 08, 2007 @09:45AM (#17507336) Homepage Journal
    > Why do people keep this meme that C/C++ is so insecure?

    Because bugs don't belong to programmers, they belong to code.

    Imagine the difference between "I fixed a Linux kernel bug", which earns you much respect from the community, and "I fixed one of Linus Torvalds' bugs" - which is a rather offensive thing to say.

    So while the insecurity is the programmer, not the language, we can't blame the programmer. It's simply not acceptable.
  • Once you go outside of a container, you already have a fatal error and the appropriate response is to crash (albeit gracefully if possible). The problem isn't so much that the program crashes, but rather that the program may consider data outside of bounds as valid memory, thus allowing buffer overflows and undefined behavior to occur.

    The difference between pure C/C++ and the STL is that something like strcmp can create a rather subtle sort of buffer overflow error, whereas buffer overflows involving STL containers are generally easier to avoid and detect. For that matter, if you use the STL algorithms library to its full potential, you may find that you hardly ever need to use explicit indexing or iterators other than begin() and end().
  • Why would I want to? (Score:3, Interesting)

    by jc42 ( 318812 ) on Monday January 08, 2007 @10:55AM (#17508116) Homepage Journal
    I think it's a bad mistake to make your code secure. If you look at sales figures, you see that sales are inversely proportional to security. So customers don't want secure computer software. If they wanted that, they'd buy it. Clearly, what people want is the most insecure software they can get.

    I say go with The Market, and write the most insecure software you can. Securing your software will only waste your time and decrease your sales.

  • by ebuck ( 585470 ) on Monday January 08, 2007 @11:29AM (#17508534)
    For ever string function, there's an equivalent that will only perform the operation on the first n bytes. If you're working with a C library that's old and doesn't have such a convienece, you can always wrap it with a call that does.

    The real problems come into play when you're using a 3rd party library. You can always police your code, but it's hard to police / fix other's code. Open source libraries are great for this in general, but there's not always an open source solution for connecting to proprietary buses, services, etc.

    In the end, solutions that require policing are only as good as policing. Policing is designed to only be effective after some atrocity has been committed, and so policing will likely only be effective after the exploit. A much better solution would prevent use of unbounded string functions by not having them defined. Perhaps there's some compiler magic that could be employed, but I doubt such techniques will gain much traction. It's like asking a guild of master carpenters to switch building materials. Once you know the materials and weaknesses, usually it's better to design around the weaknesses than to change materials.

    As a pratical real-world example near me, our school system just replace over a million bricks in a nearby school. The reason was that the new-fangled iodized metal bolts were used (way back when) to bind the bricks to the sub wall. Iodized was new and "hot" and it didn't rust, so the wall should have lasted forever. However, it corrordes when exposed to salt water, and the school was close enough to the sea to be exposed to salt water vapor. The problem was discovered when a worker leaned against a brick wall and it toppeled over.

    In the end, education will bring the current coders around, but don't expect the problem to go away. There will be many years of people reading antiquated "how to program" books that teach older, less safe, practices. There will be people reentering the marketplace that will have missed the newer techniques. There will be users installing from the copy of winZip (or whatever) that they downloaded in 2000.

    Only with time, and a whole lot of paitence, will this problem die. It won't be fixed, it will decline until it's barely noticable.

     
  • by Berkana ( 471619 ) on Tuesday January 09, 2007 @03:20AM (#17519800) Homepage
    They handle state in a scoped manner that is hard to describe without lots of example code. The best example I can think of is Erlang. If you look at the link I posted above (re-posted for your convenience: http://www.defmacro.org/ramblings/fp.html [defmacro.org] ) they mention Ericsson inventing the functional language Erlang to handle concurrency.

    As for how state functional languages handle state; state is held in the parameters a function is called with. The simplest example is recursion; in an imperative program using a for loop or a while loop or something like that, state is stored in a counter variable that gets incremented or decremented or somehow changed each time the loop is run. In recursion, if the ending condition is not met, the function calls itself with slightly differing parameters; the parameters keep track of the state, but unlike imperative programming, since the parameter is not a variable that can be changed once a call is made, it is impossible to have bugs caused by unexpected or unintentional changes to a variable in the scope of other operations that might change it. FP doesn't permit any declared values to change, so there are no "variables", just constants.

    If this makes no sense at all, you'll just have to program a few loops in an imperative language, and a few in a functional language using recursion, and see the difference. It's a lot easier to show interactively than to explain.

The moon is made of green cheese. -- John Heywood

Working...