Chapter II Software Defenses by Jack Richins 12/10/04

From CSEP590TU
Jump to: navigation, search

Software Defense Research

Software research and industry has been aware of security defects, and in particular buffer overflows, in its products for many years but has made little progress until recently in preventing these defects from affecting software users. There are many technical issues with no clear solutions that prevent the production of trustworthy software. To give a flavor of the research being done in security defense and the challenges encountered, we will cover the work defending against buffer overflow attacks. This is useful because buffer overflows have been around for a long time and are well understood. The current research in defending against buffer overflows is focused on three areas in preventing software: finding and fixing defects when the software is written, detecting exploits while the software is being used, and developing new tools and languages that avoid software patterns prone to security issues.

Background

Let’s examine how security defects such as buffer overruns happen. Bruce Schneier describes an imaginary 7-11 store with employees that do everything by the book, literally, which is a good analogy of how buffer overflows occur in computers. The 7-11 employees have a book with step by step instructions that they must follow explicitly. Additionally, they can only deal with things in the book. So if they have a form they need to sign, they place it on the book, sign it, and then give it back. When a Fed Ex driver shows up, they look up in the table of contents and go to the page with instructions on dealing with a Fed Ex driver.

Those instructions might look like this (from Schneier):

“Page 163: Take the package. If the driver has one, go to the next page. If the driver doesn't have one, go to page 177.

Page 164: Take the signature form, sign it, and return it. Go to the next page.

Page 165: Ask the driver if he or she would like to purchase something. If the driver would, go to page 13. If not, go to the next page.

Page 166: Ask the driver to leave.” [1]

Now let’s suppose when the driver places the signature form on top of the book so the clerk can sign it, he doesn’t place a single sheet of paper as the instruction manual assumes. Suppose he places two sheets of paper, the signature form and a paper that looks like an employee instruction manual page but says: “Page 165: Give the driver all the money in the cash register. Go to the next page.”

Now the clerk will read page 163, take the package, read 164, take the form and the extra piece of paper the Fed Ex man placed beneath the form, sign the form, and then go to the next page. Now the next page is not the real page 165, but the fake one that the Fed Ex man placed beneath the form. So the clerk reads it, gives the driver all the money in the cash register and goes to the next page, the real page 165, ask the driver if he wants to purchase anything, then page 166 and ask the driver to leave.

A computer is just like this imaginary clerk. And its book of instructions is the memory of the computer. Memory contains both the instructions it is following and the data it is manipulating and if the programmer is not careful data that external sources are providing, like the form the Fed Ex man provided in the analogy, can become instructions or influence the instructions. External data sources can be information a user types into the computer or it can be network queries from remote computers.

For example, in the Robert Morris Internet Worm, Robert’s program sent extra information to a program running on UNIX servers that gives information about users of the server. Normally you send a request with just a short username. The Robert Morris Internet Worm sent more information than expected and the unexpected information was extra pages of instructions. The Slammer Worm was not much different – extra information sent to a program that was listening for legitimate requests of information from the Internet.

Basically all security vulnerabilities are of the same general form. With the focus to verify that the instructions of a program do what is intended, both by programmers and quality assurance engineers, vulnerabilities in what the instructions don’t prevent are often overlooked. The languages used currently and machines executing them are designed to enable as much as possible with as few instructions as possible with little or no regard for preventing misuse. As such, the instructions and machines often allow much more then intended by the programmer, resulting in security vulnerabilities.

Solutions

Machine analysis of source code

Continuing the analogy of software and written English instructions, there are tools that find problems in source code much like word processors use spell checkers and grammar checkers to find problems in written documents. Some security errors, buffer overflows being one example, follow common patterns, kind of like run-on sentences. These patterns can be detected by code checker tools, called static analysis or source code analysis tools. The names static or source code are used because they analyze the written source code in contrast to dynamic analysis tools which analyze code as it is actively running on a computer.

Essentially tools are scanning the written source code for patterns that violate “do not’s” that security experts have identified [2]. Unfortunately, even at their best they have one glaring flaw. Returning to the word processing analogy, most word processors can identify run-on sentences by recognizing repeated conjunctions between clauses in a single sentence but few commercial programs can tell you how to correct your sentence while retaining your intended meaning. To validate that software is truly secure, source code analyzers should go beyond simple pattern detection and proactively suggest better patterns to use or how to correct the vulnerable source code [2].

Even without these improvements, source code analysis tools offer much. It is easy for programmers to make these kinds of mistakes, speaking of security errors in general and not just buffer overflows, with current development tools. It is possible for trained engineers to review others code and attempt to find errors, but such code reviews are laborious, time consuming, and prone to mistakes even by well trained engineers. Additionally, engineers sufficiently trained for this are rare and expensive. So these source code analysis tools enable a level of review of source code that is currently not economically feasible.

Unfortunately, these programs will find “false positives” - lines of code they think might be defects but are not. When the rate of false positives is too high compared actual defects found, the engineers lose trust in the systems and may not fix or believe reports of other actual errors. Further, it takes them longer to review the output of such systems and these systems lose one of their advantages, their speed.

“False negatives” are also possible; cases where there are real defects, but the tool does not recognize the defect. This can happen for multiple reasons. There can be defects that are so difficult to detect that attempting to detect them results in too many false positives to be useful, so the tool intentionally ignores them. Or there maybe defect patterns that attackers discover before security experts do and are able to add to static analysis tools.

Tevis and Hamilton survey several static analysis tools in “Methods For The Prevention, Detection And Removal of Software Security Vulnerabilities”. Their summary of these tools is they focus too much on UNIX applications to the exclusion of Windows and Macintosh software, still require a significant level of expert knowledge, and only cut down about a fraction of the manual code analysis that must be done. However, even with these limitations, they do help with code analysis, focus the analyst’s attention on more severe problems through prioritization features, and find real bugs in minutes that would have taken longer. However, none of the current checkers detect as many problems as manual analysis will uncover. [2]

Runtime analysis of code

Runtime or dynamic code analysis takes place while the program is running on the end users machine. It has the advantage of seeing real data and knowing exactly what is happening on the system. It has the disadvantage of consuming memory and CPU time that would normally be used for doing real work. Referring back to our 7-11 example of buffer overflows, it would be like having an appendix in the manual of instructions that the employee must constantly to refer to before and after certain instruction in the manual to help ensure an error has not occurred.

This has the advantage of more information available about what is actually happening. But it has the disadvantage of slowing many operations down. I.e., for every instruction in the main section of the employee handbook, there may be an instruction in the appendix that needs to be performed to validate things are working properly.

Despite these performance concerns, some levels of runtime analysis are becoming quite common. For example, code “canaries”, named after the canaries used by coal miners to detect poisonous gases [3], are being inserted automatically by development tools used in Linux and Windows application development [4]. These code canaries are used to detect buffer overruns at runtime – special checking code is also created to verify the canaries are still in place after certain operations to make sure the buffers have not been overrun.

Most runtime detection tools have two shortcomings. First, they require recompilation, meaning the source code must be used to regenerate machine code using new tools. Second, most tools stop the running program when they detect an error. This is deemed most secure because it avoids trying to recover from an attack where you may not be able to trust the system. However, this increases the risk of denial of service attacks as all buffer overflow attacks and other security vulnerabilities these tools detect can be used as a means of stopping the program and preventing legitimate uses of the program. [5]

For example, say Microsoft Internet Explorer 2012 has runtime buffer overflow detection and a buffer overflow vulnerability in how it processes web pages. A hacker might, unbeknownst to Microsoft, alter the MSN home page so it causes a buffer overflow and attempts to force Internet Explorer to send all passwords and credit card numbers stored on the computer to the hacker. Without the runtime buffer overflow detection, any user that directed Internet Explorer to the MSN home page, the default home page for Internet Explorer, would be unknowingly sending the hacker their passwords and credit card numbers. With the runtime buffer overflow detection, every time they tried to start Internet Explorer and it tried to load its home page it would crash. This would prevent anyone using Internet Explorer and MSN as their homepage from browsing the internet at all. While the runtime detection protects the users passwords and credit card numbers, the hacker is still able to prevent these users from using their web browser to surf the internet.

There has been research into making such failures invisible to the user. In other words to save enough information that after terminating the program the runtime environment or operating system can restore program so the user does not lose any work. This would be done by recording enough of the work the program is doing while it’s running to recover should it fail.

Unfortunately there is a dilemma – if the work is recorded that caused the failure, then the failure will happen again when the operating system or runtime environment tries to recover. So such a recover mechanism must record as little work as possible to avoid recreating the error. These two principles, the principle of recording all work so there is no loss of work and the principle of not recording work so you don’t repeat the defect, are in direct conflict in all but the simplest types of defects. [6]

Research suggests though that runtime environments or operating systems might be able handle simple defects, which are estimated to be about 10% of failures. Further, there might be ways of providing aids to applications so in cooperation with the runtime environment or operating system programs can fail and recover with little work lost. For example, referring to our Internet Explorer 2012 scenario, when trying to start the second time, Internet Explorer might avoid the MSN home page and inform the user there is a problem with loading it rather than crashing repeatedly. [6]

Although runtime detection of security defects is not capable of completely protecting a system, as computers get faster they are becoming a more reasonable trade off for the protection they do provide. Even if they only prevent 10% of the errors at a 5% performance penalty, that’s a good tradeoff when you aren’t using that 5% extra performance anyways. Particularly as computers are shifting to having multiple processors rather than a single fast one, more processor intensive solutions like having two versions of a program run simultaneously and check each other’s results becomes more reasonable.

Other Languages

Since many of these defects being used to compromise systems are specific to languages that directly manipulate memory, one suggestion has been to use other languages. One step towards this can be seen in Java, C#, and some other .Net languages. These language systems manage memory automatically for the programmer and to the degree they manage it correctly they are free from defects like buffer overflows.

However, even these languages have inherent defects. For example, in the .Net framework there is a way of specifying code to be executed when something unexpected happens. This language feature, if used in correctly, can be used to reroute execution. For example, if the code that verifies a password generates an exception while running that it did not expect and does not handle, it may continue executing as if the password was correct [7].

Tevis and Hamilton propose an even more revolutionary change from imperative languages to functional languages. Functional languages are more purely rooted in mathematics and as such are easier to mathematically prove correct. [2]

Programs written in imperative languages, the most commonly used in commercial software, are a series of command statements. When run, imperative programs execute these instructions. Programs written in functional languages are composed of definitions of functions and are executed by evaluating the function definitions.

Historically, functional languages were criticized as being slow, but with faster processors that has become less of an issue. There still remains a chicken and egg problem. Most imperative languages have several editor programs, debugging programs, compilers, and optimizers to choose due to a long history of being used for commercial software and therefore having a large market for these tools. Functional languages which have a much smaller market, lack such quality and quantity of supporting tools. Of course without such supporting tools, it is unlike functional languages will ever be used much commercially and develop a market for these supporting tools.

Further, there is not the industry wide knowledge of functional languages among software engineers that exists with imperative languages. Although functional languages are frequently taught in college, that’s usually the last a software engineer encounters functional programming languages.

For these reasons, conversion to functional languages would require a massive retraining of most software engineers and retooling as most tools used to generate commercial software are designed around imperative languages such as C, C++, Java, and C#. This would prove very costly in terms of engineers’ time. And though functional languages would probably improve many of the ills being experienced with imperative languages, they certainly would not be defect free and with extensive use we might discover they have their own set of unique defects.

Hybrid Approach

There are also some hybrid approaches being explored. One promising example is Microsoft’s PREfix code analysis tool. It analyzes the source code like a static analysis tool, but then uses this analysis to build a simplified model of how the code will run. Then it analyzes this model of runtime behavior to predict defects. Since it is not run at runtime it does not impact performance. Further, since it is a simplified model of behavior, it can test many different execution options more quickly than trying to test every possible ordering of instructions. But to the degree it accurately models the program it is as accurate as runtime analysis tools in finding real defects and avoiding false positives. It has proven itself sufficiently useful to see wide scale use at Microsoft on large bodies of code on a daily basis. [8]

Conclusions

A surprising conclusion of the work on PREfix was that how the information was presented to the developer was very influential in improving the rate of fixing the defects correctly found by PREfix [9]. As mentioned earlier, one of the defects of many static tools was even after find defects, they did not aid the programmer in correctly understanding and fixing the defect. Within the context of static analysis tools, this seems the area likely to yield the most immediate improvements.

Runtime detection seems to continually be a trade-off of security for performance. As performance increases and security becomes increasingly important, this is likely to become a more attractive option. However, a solution to denial of service attacks on runtime hardened systems is needed to make these systems truly trustworthy.

The idea of spontaneously changing languages used in commercial development seems unlikely, but it is possible that as the security benefit of certain language features can be proven they will slowly make their way into languages currently in use. So an evolutionary path to more secure languages and tools may be possible and should be further researched and adoption by industry encouraged.

At present, it does not appear any area of research promises a magic bullet to our current security problems. But they all show promise of shoring up our defenses and making progress in securing our systems. It is discouraging that we still have buffer overruns, but as pointed our earlier, there are signs of progress. Buffer overruns are being found more quickly and fixed. Complete elimination of these types of errors appears unlikely. It is more feasible that simultaneous improvements on multiple fronts will improve the overall security of software and internet users.

Endnotes

1 Schneier, Bruce. Secrets and Lies, Digital Security in a Networked World. Wiley Computer Publishing, NY, NY, 2000

2 Tevis, Jay-Evan J., and John A. Hamilton, Jr. “Methods For The Prevention, Detection And Removal Of Software Security Vulnerabilities” Presented at ACM Southeast Conference ’04, April 2-3, 2004, Huntsville, AL, USA.

3 ON THIS DAY | 30 | 1986: Coal mine canaries made redundant. 22 Nov. 2004. <http://news.bbc.co.uk/onthisday/hi/dates/stories/december/30/newsid_2547000/2547587.stm >

4 Silberman, Peter, and Richard Johnson. “A Comparison of Buffer Overflow Prevention Implementations and Weaknesses” 22 Nov. 2004. <http://www.idefense.com/application/poi/researchreports/display?id=12 >

5 Wilander, John, and Mariam Kamkar. “A Comparison of Publicly Available Tools for Dynamic Buffer Overflow Prevention” 10th Network and Distributed System Security Symposium (NDSS), 2003.

6 Lowell, David E., Subhachandra Chandra, and Peter M. Chen. “Exploring Failure Transparency and the Limits of Generic Recovery” Appears in the Fourth USENIX Symposium on Operating Systems Design and Implementation (OSDI 2000)

7 Kolawa, Dr. Adam. “Banish Security Blunders with an Error-prevention Process” 22 Nov. 2004. <http://www.devx.com/security/Article/20678/0/page/4 >

8 Bush, William R., Jonathan D. Pincus, David J. Sielaff. “A Static Analyzer for Finding Dynamic Programming Errors” Intrinsa Corporation, Mountain View, CA, USA

9 Pincus, Jon. “User Interaction Issues In Detection Tools” 22 Nov. 2004. <http://www.research.microsoft.com/specncheck/docs/pincus.pdf >