Mac OS X Essentials • 1:04:32
Learn code fuzzing techniques and other ways to simulate an attack on your own code to find security vulnerabilities. Find out how to respond to security incidents and about new security features in Mac OS X Leopard.
Speakers: Drew Yao, Jacques Vidrine
Unlisted on Apple Developer site
Transcript
This transcript has potential transcription errors. We are working on an improved version.
Hi, and welcome to Dode Hardening Techniques. I'm Drew Yao, and I'm a Security Engineer with the Product Security team. My co-presenter is Jacques Vidrine. He's a Security Ninja with the OS Security team. In this talk, I'm going to give an introduction to product security and to some of our processes. I'm also going to talk about securing code and that means both finding and fixing security bugs.
And then Jacques is going to describe some of the new security features in Leopard. Starting off with Apple product security, we're responsible for the security of all of Apple's products. And as part of our mission, we do both proactive and reactive efforts in order to ensure this security. Now I'm going to just describe the life cycle of an average security bug. The new bugs are reported by a wide variety of people, including Apple internal users and developers and security researchers and security organizations.
We do...are responsible for the communication with all these different kinds of people, and we try to maintain a good relationship with them. If you find a security bug, you can let us know about it either via Radar or via [email protected]. And you may know Radar better as bugreport.apple.com.
We see all new security Radars, and we review them. So if you file a Radar with the classification security, you can be certain that we will see it and we will handle it appropriately. Once a security bug has been reported, we work to determine the root cause and to search for any related issues, both in the same project and in other projects. We coordinate with the engineering team for the fix.
We don't actually implement the fix, but we kind of try to verify that the fix is complete and it really addresses the root cause. As this is going on, we're constantly in contact with the reporter of the issue. We're giving them status reports upon request, and we notify them right before the fix goes out so that we can coordinate an advisory with them. Once the fix is ready, we test it. It gets released, and we post the advisory.
The things that I've talked about so far were all response measures, things that we do once...when someone else reports an issue to us. But we also do a lot of proactive measures, things that either prevent the creation of new bugs or also trying to find existing security bugs. One of the things that we do is training. We do a lot of presentations just like this but to Apple employees. We also do a lot of one-on-one consultation with various engineering teams to do things like code audits, design reviews and so on.
We work to analyze new features, doing a threat analysis; and we do...we work to harden existing software. Another thing we do is what we call red team activities. This just means trying to find security bugs through either testing or code auditing. And as part of our red team activities, we develop and maintain and promote the use of tools; and these include fuzzing and static code analysis tools. And I'll describe these more later. Now moving on to securing code.
I'm going to give an overview of the most common security vulnerabilities and give some tips on finding them, both by code inspection and by fuzzing. Before I go any further, I'd like to just mention the Apple Secure Coding Guide. You can...you can find this just be Googling Apple Secure Coding, and it covers a lot of stuff that I'm not going to cover in this talk. Some of the bullets are...are below.
And so I recommend it. One of the leading causes of security issues is buffer overflows. This, in case you don't know, it just means writing past the end of the memory where you're supposed to be writing and then smashing into other memory. This can often lead to arbitrary code execution.
And I won't talk right now about how exactly that works because it's been covered in many other places; but just in order to find these issues, one of the common things that you can look for is any use of an unbounded copy API such as strcpy. In this...in this call, if bad source is longer than the...the amount of memory allocated for dest, an overflow will result.
And the fix for it is pretty simple. You just switch to using a bounded copy API like strlcpy, and the bound or the amount to copy should be somehow related to the size of the destination. Another common cause of buffer overflows is when you do use a bounded copy, but the bound or the amount of copy is not really validated. It might be coming directly from the user or just being influenced by the user or the attacker.
So in this case, what you can do is just do some kind of bounds check that makes sure that it's not bigger than the size of the destination. Another common cause of security issues is integer overflows. In this first block, we have a call to malloc to try and allocate a certain size; and if the attacker controls this bad_ num, it could be a very large number.
If we have a 32-bit program, then the size T or the amount to malloc would be at most 2 to the 32 minus 1. 2 So if the product or the sum is greater than 2 to the 32 minus 1, then the high bits will be...will be just discarded and only the low 32 bits will be left. So it could be a very small number that actually gets allocated, and that could often lead to a heat buffer overflow later. A related issue is integer underflows. And this is when instead of something being unexpectedly small, it's something becomes unexpectedly large.
This call to strncat is actually correct; but if the dest_size is able to be influenced by the attacker at all, maybe he could legitimately set it to be zero. And so if we look at a third argument, zero minus zero minus 1 would be negative 1. And since strncat takes a size T as the third argument, that's unsigned, the negative 1 will be interpreted as a very large unsigned number leading to a buffer overflow. And there are many ways to detect and handle these, but we introduced one way that's new in Leopard.
And that's the checkint API. You can read about this in checkint.h and it also has man pages, and there are a lot of different flavors of this checkint API for all the different kinds of integer arithmetic that one can do. So, for example, when you...in this sample code, we want to check if bad_num plus padding could overflow.
And we're assuming that both of them are unsigned 32-bit integers. We can just call check_uint32_add on bad_num and padding and pass the point there to result variable. And if after the call, the result variable has the overflow bit set, we know an overflow happened and we can handle it however it's appropriate.
And if that didn't happen, then we know that the overflow didn't happen in this size which was returned is a safe number to allocate. It didn't overflow. A related cause of security issues is signedness issues. And so in this example, we have this bad_len which is a signed integer.
And we also have a check here that's trying to do a bounds check to prevent the copy if the bad_len is bigger than the size of the destination. But if bad_len is under the control of the attacker or can be influenced by the attacker, maybe it can be negative. If it's negative, this check will always return false because a negative number is always less than 100.
So when we get to the memcpy, it's going to be...it's going to be interpreted as a very large unsigned number leading to a buffer overflow. And the fix for this is pretty simple. Just whenever you have something that's being used as, like, a length or a size or something like that, that would really never legitimately be negative. It's a good practice just to make it unsigned by default.
And in general, integers should be unsigned by default unless you have a reason that it would ever be negative. The last common cause of security issues that I'm going to talk about is format string bugs. In this first block, we have three calls to varying APIs where the parameter in red is like a string that's being passed directly as the format string parameter.
And when this happens, if an attacker controls the string at all, he can set it to be like %s or %n or to have these kind of format tokens inside it. And this can lead to code execution or other security problems So the fix is actually very simple.
It's to make sure that the format parameter is a static or fixed constant string; and then if you want to, say, print out a string, you just make sure that you have a %s token and then follow it with a string. So most people are probably not familiar with how exactly format strings can be exploited. So I have a sample program here. What it does is it has a stacked buffer called buff and it copies the first argument to the program into buff and then it calls printf on the buff.
And that's an incorrect call to printf. So what can we do with that? One thing we can do is try and put in a bunch of %x characters in the format string. And how these format string functions work is they look for each of these format tokens like %x and for each one, it'll go on the stack looking for an argument and convert it to a hexadecimal number and print out that number.
With this call to printf, I didn't pass any arguments. So it's just going to get whatever happened to be on the stack and print that out. And especially, you can notice here at the end, this 41414141. This, in case you don't know, hex 41 is the ASCII code for capital A. So actually, this comes from the user supplied format string because it's stored on the stack. So at this point, we've just...we've just determined that an attacker can cause some arbitrary stuff to be printed out which is no big deal. But there is another thing we can do which is instead of for the last %x, I can put in a %n. How %n works is that it takes the number of characters that would have been printed so far and it writes that number to a pointer.
So when I ran this program, I saw that it crashed trying to write to the pointer 41414141. Since this is under my control, I can write to any pointer I want. But in order to get code execution, it would also be very useful if I could control the value that gets written to this arbitrary address.
And that's what this %100000x is for. What this says is at a minimum print 100,000 characters. If you remember, the %n token, what it does is it takes the number of characters that have been printed so far and writes them. So this is exactly what I need in order to control the value that gets written. And, in fact, we see that when I disassemble the instruction that caused the crash, this is a store instruction which is writing the value that's in register 24 to some memor$y.
And when I inspect register 24, we see it's approximately 100,000. So I can basically control the value that gets written and the address that it gets written to. And you can do multiple writes using multiple %n tokens. So it should be clear that code execution can be possible.
You can write whatever you want to wherever you want. And these are a very dangerous issue but also very simple to fix, as I described earlier. To review all these things that I've talked about, some things that you can look for when doing a code audit include looking for unbounded copy APIs being used like strcpy, sprintf or a loop where the loop termination case doesn't...doesn't involve the size of the destination at all.
And the solution to these is just to switch over to using the bounded version of these APIs, like strlcpy. Another thing to look for is integer arithmetic being done on the amount to be allocated or copied. And this can often lead to integer overflows and integer underflows. And you can use the checkint API which is new in Leopard to try to detect these. Oops, going back.
Another thing to look for is anything that's...that represents a length or a size that is signed. It probably should be unsigned just to avoid this problem where when it's negative it can bypass some bounds checks. Format string issues which I just went over, it's very simple to look for them.
You just look for when the format string parameter is not a fixed constant string. Now all these things that I've described so far are bad things that can happen if some malicious input is passed to them. So, for example, strcpy is usually only if it's possible for the attacker to pass a bad...or to control the string that gets passed as the source buffer.
So another way to look at it, to look for code bugs is instead of looking at these bad things that can happen is to use a more top down approach instead which is to look at where data comes into the program and try and follow it through the program, watch as it gets propagated and see where it gets used in dangerous ways. But both the top down and the bottom up approach can sometimes be difficult or not always entirely easy to do.
Let me give an example. Let's say we have a function called foo and foo reads into a header structure from some file or from some socket which is probably untrusted and it also mallocs a fixed size buffer. And at this point, we don't know that any security issues has happened.
And it passes these two parameters to bar, bar passes them to baz and baz passes then to xyzzy. At this point, xyzzy reads into the buffer using hdr.len as the amount to copy. Now you'll remember that header came from the untrusted file or socket or whatever, so hdr.len could be arbitrary. It could be very large. And we also know that buff is a fixed size buffer.
So it's pretty much certain that a buffer overflow can result here. So you'll remember that when we looked at foo, we didn't know that any security issue had happened. You'll also remember that when we looked at xyzzy...if we look at xyzzy alone, we also don't know that any security issue's happened. This is because we don't know the size of the buffer and we don't know that header came from an untrusted source.
So the only way that we could really understand that this is a security issue is if we understand the entire call graph. Now in real-life programs, often the call graph is much more complicated than this. So it can be quite complex to try and do this, and it can also be error prone. One potential solution to this problem is to use static code analysis tools.
There are a number of commerical tools that are available. And basically, what they do is they try to do the same kind of things that a manual auditor would do which is to look at where data comes into the program, to follow it as it gets propagated through the program and to see where it gets used in any dangerous ways.
So these are very useful tools, but they do have some drawbacks including false positives. This just means that when you...when it returns some kind of bug that it thinks is an error, sometimes it's really a bug, sometimes it's not. And if it's not, then it's sort of wasted a little bit of your time trying to figure out if it was real or not. And also because static code analysis tools look at source code, if you don't have source code, then it doesn't apply. So there's a complementary method of finding security bugs using tools, and that is fuzzing.
Fuzzing is automated robustness testing, and this means sending malformed data to a running application. And by malformed, I mean good enough so that it appears valid but also bad enough that it might break stuff. And I'll talk more later about how to generate malformed data. But first let me talk a little bit more about the motivation for fuzzing. Fuzzing is a primary tool for hackers, and this is in large part because it doesn't require access to source code. People outside of the company usually wouldn't have access to source code. So this is the kind of thing that they would have to try.
They can't do code audit or static code analysis tools. It tends to find exploitable flaws, meaning when you run...when you just feed that input to a program, it either crashes or it doesn't. There's no false positives. If it crashes, that's at least a denial of service and potentially something worse. They're relatively easy to create and run, and they test assumptions in the code. And they determine or demonstrate the importance of defensive coding.
A lot of programmers code something so...just so it's functional. For example, if they're coding an image processing program, they'll have suite of valid images and they'll say okay, once my program can display all these images, I'm done. But if they don't take into account that deliberately malformed image can cause security issues, then they're in for some...in for some headaches. And attackers are actively fuzzing commercial products. We know this is true for Apple products, and it may be true for yours as well.
To drive this point home a little more thoroughly, you may remember the month of Apple bugs which was this January. In this month, once every day some security researchers released a security bug publicly in some Mac products. Of the 23 issues that were reported against Apple products, 15 of them were most likely found by fuzzing. And I say most likely because I don't know for sure how they found them.
But given what we know, it's seems very likely The remaining seven issues were reported against third-party Mac products, and of these six were most likely found by fuzzing. Now that we have the motivation out of the way, what can we fuzz? We can fuzz any program that takes untrusted input, and this includes especially programs that speak any network protocol. For example, Safari and Apache both speak HTTP. Anything that parses untrusted files, for example, QuickTime parses a lot of different kinds of media files. And setuid programs because they take a lot of untrusted input from the user.
It's untrusted because the program runs with the privileges of a different user. So once you are ready to do your fuzzing, one of the common ways of doing fuzzing is to take a valid template and try and mutate it. For example, if you're attacking a file format, you would take a valid file and mutate it. If you're attacking a network protocol, you would take a valid session transcript and try to mutate it.
So a very good target within these templates to go for is any kind of size or length or count field, and this is because these values are often used as the amount to either allocate or to copy. As we saw in the earlier section, the number that's being used as the amount to allocate or copy can often cause integer overflows, underflows and buffer overflows when it's used as the amount to copy. So what are some good values that we can try to fill in in these fields? One good thing to try is anywhere near signed or unsigned boundaries, and this is because it can trigger the signedness issues that I mentioned earlier.
Another good thing to try is powers of 2 plus or minus a small offset. This is because programmers often allocate around powers of 2, like 256 or 512, things like that. So if you use this kind of value, plus or minus a small offset, sometimes you can trigger off by one or off by a few errors.
Also with certain values of powers of 2, you can trigger integer overflows or underflows by addition or subtraction. Finally, good things to try are...include using, like, the biggest possible number divided by some other number and then add one. And this is try and trigger integer overflows by a multiplication. For example, SIZE_T_MAX divided by 2 plus 1 times 2 is going to be something that's slightly larger than 2 to the 32. So mod 2 to the 32 is going to2 be some small number.
And if that's used as the amount to allocate, it's going to cause potentially a buffer overflow later. With string fields, one of the common things that you can try is just to take a character and repeat it over and over. The point of this is to try and expose the use of unbounded copy APIs like strcpy.
And the reason you use the same character over and over is because you might want to...like, when you get a crash log, it's pretty common if there was buffer overflow that you'll see this character repeated over and over in the crash log, either in the registers or in the stack or otherwise.
And the reason you try different lengths is because obviously, if it was too short, then sometimes it won't cause the overflow because the buffer won't be big enough. If you try it too long, sometimes there are partial filters in place or incorrect bounds checks in place. So maybe, for example, the 65535 As would not 5trigger the overflow because they would be caught by the partial filter. But the 10,000 would trigger the overflow.
Another good thing to try would be these format tokens. They...if there's a format string bug, they'll cause a crash. And binary data, when you're just working with a text application. This might be because the application does some kind of arithmetic on the character values or it might just be because it's unexpected. And fuzzing is about really just trying to put in unexpected things and see what happens.
And especially,null bytes are useful to try because CStrings are null terminated. So terminating a string unexpectedly early can sometimes cause issues. And if you know a little bit about the format that you're fuzzing, any kind of delimiter is a really good thing to try. And this is because delimiters are used as control information.
So, for example, with HTML and XML, we have these angle brackets. You can try to either add them or removed them from the valid stream. So, for example, this valid HTML becomes this slightly invalid HTML. And if the parser is not robust, it might get confused and bad things might happen. With binary streams, like binary files or binary network protocols, often it's kind of too much work to be very format aware. It's too much work to figure out that this part is a length field, this part is a string, this part might be a time stamp or something like that.
So there's a very surprisingly effective alternative which is just to randomly flip some bits in the stream in a window. And let me give a more concrete example of that. Let's say you want to find a security bug in a program that parses MP3 files like iTunes. What you could do is take a valid MP3 file and make a copy of it and just mutuate the copy in a certain field in the file. And by mutate, I just mean flip some of the bits randomly.
You can then save out this mutated copy, go back to the original, make another copy and again mutate the copy but this time the window or the field of mutation has shifted and then so on. You keep sliding the window across the file, saving out these mutated files. The reason we do this sliding window is that we want to get as much code coverage as possible.
Theoretically, at least, different parts of the code parse different parts of the file. So with each of these files, most of it is valid; but only a small part is malformed. Therefore, just the part of the code that parses that part of the file is going get exercised more. Now once you have created all these mutated test cases, you would then run them against your application.
And there are a list of things that you would probably want to look out for and try to log. The main one, I would say, is crashes. You probably want to, say, try to detect a crash after running each case; and if the crash gets caused, then you log that this test case caused this crash.
Another common thing that gets caused is hangs Depending on if you care or not about hangs, you can do different things. If you don't care, you can just kill the application every time after every test case. If you do care, then you can try to detect it and then kill it. Rarely, very infrequently but sometimes, you can cause a kernel panic. I just wanted to note if you do cause a kernel panic, we'd really like to know about it. So please report it. But if you cause a kernel panic, you can't really detect it afterwards because your system is in a halted state.
So what you can do is if you think a kernel panic is likely, you can...before you run each test case, you can write to a log saying I'm about to run case number N. And if you do get a panic, you can just reboot, look at the file, and you're pretty sure that case N is the one that caused the panic. But fuzzing is not a panacea. It's not perfect. There's some challenges when doing it.
One of them is finding or creating good seeds to mutate. For example, if you're fuzzing MP4 files, MP4 is a container format. It can contain all different kinds of audio and video codecs and bit rates and all this stuff. If you want to get good code coverage, you would have to generate or find a large suite of valid files to mutate. And that can be quite a hassle. Another challenge is determining if a crash exposes a code execution bug because not all crashes are exploitable. And the reason you would do this is for triage. Obviously, you would want to fix these code execution bugs first.
And this is...this can be quite a challenge because if you don't have security expertise, trying to figure out to really declare that I'm 100% sure that this test case could never be exploited can be a lot of work. So for many people who don't necessarily have the security expertise, it may just be easier to fix the crashes, to just treat all of them like serious security bugs because it's more effort to really diagnose them than it is to just fix them. Another common challenge is duplicates. It's quite common that if you run, say, 5,000 cases, maybe 50 of them all caused the exact same crash exposing the exact same bug in the code. So figuring out which ones of these are duplicates of each other can be a lot of effort.
One approach you can take to this is to...after you're done you have all these crash logs, you can just do some kind of text processing on them to figure out which ones are duplicates and you can do this with an automated script. Another common challenge is nondeterministic crashes. This means that for a given test case maybe sometimes it crashes, sometimes it doesn't. And this is often caused by heap buffer overflows or heap corruption in general.
So, for example, depending on whatever happens to be adjacent to your heap buffer, overriding it may or may not cause a crash because maybe it is used again later, maybe it's not. Maybe there's a pointer there; maybe there isn't. So one way to handle this is to use Guard Malloc which you can read about in man libgmalloc.
What it does is it places malloc blocks directly adjacent to an unmapped page. So the moment you write past the end of your malloc block, you'll immediately start writing into this unmapped page and cause a crash right away thus removing the randomness. Another common cause of nondeterministic crashes is race conditions.
Sometimes all the stars just have to be aligned just right in order to cause a crash. So for these, there's not really a great way to handle it. Sometimes you just have to get lucky that it caused the crash, and then afterwards you can try to run the case over and over and see if you can reproduce it.
A related issue is getting good coverage across different environments. It's not uncommon that, say, if you have a computer over here that has certain software installed or certain configuration, that the test case will cause a crash on this one but doesn't cause a crash on that computer. So...and especially in this vein, one thing to watch out for is architecture.
It's not uncommon again that, say, PBC versus Intel or 64-bit versus 32-bit, it'll crash on one but not on another. So in order to get code coverage or just good coverage, you have to try running your testing across different environments. In summary, fuzzing is an automated robustness testing technique. It's a primary tool for hackers.
It's relatively cheap and easy to implement and to use, and it's good to incorporate into the QA process. And by doing this, you're using the same kind of techniques that outside attackers use. So you're likely to find the same bugs; and then when you fix them ahead of time, you can just save yourself a lot of headaches. I'd also like to mention that fuzzing is not entirely just for security.
By increasing the robustness of your code, you also increase or improve your user experience because these malformed or unexpected inputs don't always come from an attacker. Sometimes they just come from people doing crazy stuff. So when that happens, you'd much rather have, like, just some warning message pop up than to have a crash. So that about wraps it up for me, and now I'd like to pass it off to my co-presenter Jacques.
Thanks, Drew. Now Drew covered a lot of techniques that could be used to avoid introducing flaws in your software applications or to find them and eliminate them. But I think as we all know, for any non-trivial piece of software, if...even if you have the most disciplined and best educated team, every now and then a bug does get through. So a strategy based on, just doing it all right the first time or eliminating all the bugs before somebody else finds them is kind of incomplete.
And we recognize that, and we want to help out. So Leopard provides several security improvements that can mitigate a vulnerabiity should one slip by. I'm going to talk about half a dozen different technologies that can mitigate a vulnerability. That is, even if that bug is present in a piece of code, it can either make it very difficult to exploit or it can reduce the impact of that if it is successfully supported...exploited. The first thing I'm going to talk about has to do with a subtle issue in modern user interfaces. And that's that the gesture to open a document is the same as the gesture to launch an application.
The problem with that is that malicious software or malware can be disguised as a document. For example, you may have seen in the news last year that on the Mac rumors site, somebody posted a Trojan called...that was later called Leap A. But they said oh, here's some Leopard screen shots for you. And as it turns out, after downloading it, it wasn't screenshots at all. It was actually a malicious piece of software.
Now casual inspection of that might not be enough. If you just look at it, it looks good. It looks like a JPEG. It has a JPEG icon. I might as well click it and see what it has in there. If you look very carefully, you can see that it is instead an executable file.
And in this case, turns out to be a piece of malicious software. So it's kind of unreasonable, I think, to expect every user every time they download every item to do this kind of close inspection. In fact, sometimes I've heard some security people say to others you know, you should never open e-mail attachments. That's dangerous. Well, it can be dangerous. But to most users, they're thinking well, what do we have e-mail attachments for if I can't even open them.
This is a problem we're trying to solve. So with file quarantine, a new feature in Leopard, it's pretty straightforward. When content is downloaded, the downloading application marks it with some quarantine properties. And after that point, we say that item is quarantined, much like the Department of Agriculture might quarantine a cow before they know if it has mad cow disease or something.
Later when you go to click on that, you want to open it, when Leopard sees that it's quarantined, it performs some additional inspection and that might involve checking the type of the document or checking if there's user overrides or whatever. But if it turns out to be a safe document, an actual document not an application, the quarantine state's silently removed and everything just keeps on flowing. The user's work flow hasn't been interrupted. But if it instead turns out to be an application, the user's presented with the context of his download. He can see when it was downloaded and where it was downloaded from.
Notice here in this dialogue, we even have a show web page button. So the user, if he doesn't remember because he downloaded it two weeks ago, what this was about can actually press this button and be brought back to the web page where the download originated. Something happens similar for Mail. If this had been from a Mail message, it would say show message; and that would bring up the piece of e-mail that the item was previously attached to.
Now this wouldn't be very effective if after having something in quarantine moving that item around or copying it or archiving it would cause the quarantine properties to disappear. So locked pieces of Leopard actually propagate these properties. Let me show you an example. An initial download of a disk image, say, the downloading application puts the quarantine properties on the disk image.
Later we mount that disk image with the disk image's framework, is it doing the work behind the scenes? Maybe we look inside. We see there's an archive, a zip archive. Of course, you know, we're going to extract that. Maybe copy it out to finder and open it up.
Finally, eventually, hopefully, we're going to stop running into archives and run into some actual files we care about and think great, we finally got to the screenshots, let's go. Well, even after all that manipulation, we still have that download context available for the user. So we don't lose that.
I think that's really important in order to make this work. As I said, we've modified lots of Leopard in order to make this possible like the disk images framework and our archive utilities and things of that nature. And, of course, we've modified our network applications like Safari and Mail to add the quarantine properties in the first place.
So we'd like you guys applications to do the same kinds of things. And, of course, to do that you'll need an API. The file quarantine API is straightforward. I'm going to talk about it in two pieces. The first is directly manipulating quarantine properties. This is handled through a new launch services named attribute.
What one does is create a dictionary with the pieces of quarantined information that needs to be stored with the item. What type of download application it was, the origin URL, things of that nature. And then you can us LSSetItem attribute to store that with that item. So the information of what type of keys there are and what their meaning are can be found in the launch services headers, LSInfo and LSQuarantine. The second part of the API is something called automatic quarantine mode.
With this, you can start your application in automatic quarantine mode and every file that it creates will be quarantined. It will have quarantine properties. Now these properties won't be as rich as the ones that you can get by sending them directly because, you know, Leopard doesn't know whether an arbitrary process is doing e-mail or chatting or what not.
And it doesn't know the origin for your file. It doesn't know why you created that file. But at least you have the minimum information such as the time the file was created and what application created it so the same mechanisms can work. I'll give a quick example of how to use these two APIs.
The first is directly manipulating into properties. There's two steps. First you create a dictionary with the pieces of quarantined information. In this example, I started out putting the origin URL and the type of application. I've deleted the rest. These slides get long quick. The second step is to actually take that dictionary and assign it to an item.
And, again, as I said, we used LSSetItemAttribute. If you later wanted to observe what the quarantine information is or even see if a file has some quarantine information, say, because your file management thing that copies files or archives files, you can do that by using the LS copy item attribute to get that same dictionary right back out. Using automatic quarantine mode's even simpler.
You simply edit your info.plist and add this one key, the LSFileQuarantineEnabled. When that's in place, when the application's launched, it will cause all files created to be quarantined. You can optionally specify some path matching patterns for files that you don't want to have quarantined. Now that's not as useful as you might think.
It turns out that quarantining things that aren't really downloads doesn't have any ill effects. If you have an application that starts up and creates a preferences file and it was running in quarantine mode, that preferences file is going to get quarantined. It sounds not right. But it's actually quite rare for the user to navigate through his library preferences directory and double click on that thing to launch the property editor. But it does happen.
In that case, it'll be examined. It'll be seen that it's a regular document. The quarantine will be cleared. And, again, things just continue without any interruption to the work flow. So I'm not sure how much utility this has, but it is there. Why if you have the ability to directly set the quarantine properties and especially supply more information would you want to use the automatic quarantine mode? I can think of a couple of reasons. One is because you want to start using quarantine right now.
That's a fair reason. That's a good reason. But another can be illustrated by how Safari is using quarantine today. Safari actually runs in automatic quarantine mode. So all the files that Safari creates are quarantined. But it also uses the LSSetItemAttribute API in order to specify more details for those items that it knows are downloaded.
The automatic quarantine mode picks up the slack, though, for something like plug-ins. Safari doesn't have a knowledge of what plug-ins might be doing. Some of them might be doing downloads. But it sure wants them to have quarantine information associated with it. So that picks up those other pieces. Well, let's say those plug-ins evolve and start setting quarantine information themselves. That's still going to work out because they're just going to override that default automatically added quarantine information.
So this work is working pretty well in practice. The next thing I'd like to talk about is the Leopard Sandbox. In the security community, the Sandbox is a overloaded term. For example, if the Java security policy is often called the Java Sandbox. In that case, the Sandbox is actually the mechanism that's implementing and enforcing the security policy. That's in contrast to the Leopard Sandbox which is created to harden your application. It's not a replacement or extension of the usual operating system access controls. Rather, it's a safety mechanism. It's like a seat belt. It's there for when things go wrong.
It hardens your application by letting you specify what kinds of things your application is expected to do, only the things that it needs to do. And if it attempts to do other things, it simply won't allow it. And this works even with root processes with processes that have full system privileges. So the result of this is that for those applications, the impact of the vulnerability is greatly reduced. If an attacker manages to compromise an application in a Sandbox, that's not great, you don't like that; but it still limits what that attacker can do with that compromise.
Because of this, many system services in Leopard are now running in a Sandbox. That includes things like BIND, the Internet name server, little demons like portmap, the Xgrid infrastructure. These are actually all running with quite conversative Sandboxes where everything's denied by default and we've attempted to give it only the privileges that it needs to do its job. We also have Spotlight importers and QuickLooks plug-ins running in Sandbox. That Sandbox is a little more liberal.
It does provide a speed bump, though, for an attacker by keeping importers and plug-ins from being able to talk to the network. So this means it can't accidentally or maliciously phone home or upload data to a remote server. So if you're writing an import or a plug-in, you should keep that in mind. I wanted to give an example of the Sandbox profile language, the configuration language that's used to create Sandboxes. Now for this release, this language is private interface. It's a scheme-based configuration language.
And in this example...thanks, we're happy with the flexibility from that...in this example, this profile denies everything by default and adds the ability to read system configuration variables to do any kind of networking in this case. But it also allows some files system access but quite restricted, some reading, some writing, some creating; but only to paths that match the regular expressions that have been specified.
That gives you just a little taste of the kind of power that's behind the scenes there. But you may still want to use the Sandbox. Even though this is private interface, we do have a public interface which is really simple and provides a few pre-defined Sandboxes. Those are things like a pure computation profile which basically restricts almost any operating system access.
At first it might seem weird. How can you use that? Well, one example that we've put to good use is to take a multi-media codec and to run it in its own address space. And then we have that codec also running in a Sandbox. So its parent process with a user interface and everything else can feed it data from whatever source, the network, files, which might be malicious.
And well, codecs, image decoders, multi-media decoders are...have...are notorious for having things like integer overflows and whatnot. But if one of these should be triggered, then the attacker may have compromised that codec but he doesn't really have any ability to do anything else except maybe suck some of your CPU time for a while. There's a second saving grace, too. When you're doing streaming video, sometimes there is corruption of the data stream.
Sometimes that unfortunately results in the codecs crashing. If you're running it in a separate address space, the codec crashes and goes away. The user doesn't necessarily have to see that, especially if you just restart the codec after you resync the data stream. You get some pretty cool stability that way. We also provide some read-only profiles which are meant for things that are pure viewers like a PDF viewer or a Word viewer, something like that. And I mentioned earlier about the Spotlight plug-in...Spotlight importers and the QuickLooks plug-ins. They use a prohibit networking built-in profile.
As an example of how to use these, we have this snippet. There's just one function call to make, sandbox_init and you specify a constant that details the Sandbox that you want to be put in. When this function returns successfully, you're in the Sandbox. The process can't escape that Sandbox. It can't enter another one.
That's it. Moving on, we have a new automatic firewall in Leopard. This has a new inbound filtering engine; and unlike most of personal firewalls, instead of doing packet-based filtering, it filters based on application. So no longer would one have to figure out and select the port and protocol in order to make an application work while still keeping a firewall. In this case, I've selected iTunes and iChat, decided I want those guys to be able to talk on the network. In particular I want them to be able to listen for incoming connections.
But I've chosen some different options for them. For iChat, since I want to get video chats from anywhere, I've just allowed the incoming connections from anywhere. But for iTunes, I'm doing music sharing; but I don't necessarily want the whole world to grab my music so I've restricted it to local connections only. That means connections from my locally connected subnets. But you don't even have to go to system preferences.
We've come out with, I think, a great way to have applications almost configure themselves. So if your user wants to download and run SubEthaEdit, you know, I decided I'm going to do some collaborative editing today. And I haven't run it before on this machine, we'll get a dialogue that says hey, this thing needs to listen. Do you want to let it or not? If you choose...whatever you choose, that choice is going to be recorded. You're going to be asked the one time.
If I deny, it'll be stored as a deny for that application. If I allow, it'll be stored as an allow. That's a much easier configuration for most users. They don't have to figure out why didn't SubEthaEdit not work just now. It told me I was going to be able to edit documents across my network and stuff, but it didn't work. I'm not sure why. This way, they're totally in control of it. They don't even have to visit the sys configuration panel unless they want to, unless they want to change their decision later. Now IPFW is still present.
Got ahead of myself. IPFW is still present for advanced users. This is the IPFW packet filtering firewall that we've had for several releases. You can still configure it from the command line or using the Mac OS X server user interface, or maybe some of you guys are writing some great applications for configuring that firewall. Package signing has been added to our PackageMaker and to our installer.
In PackageMaker, you can select the field labeled certificate, pick a certificate out of your Keychain that's suitable for key signing. And the package that will then be produced will be signed with that certficate. The installer, when using that, can detect any modification that may have happened since it was signed. If it does that, it'll fail to install and warn the user what's happened.
This is a good complement to code signing which is a slightly different beast. Code signing provides several benefits. One is that it detects malicious modification to your code or unintentional ones. But more importantly, it allows us to keep an application's identity across versions and updates. So that's pretty cool. It knows that wonder app version 1 and wonder app version 2 are just two different instances of the same program.
Well, that's useful because other pieces of the system, like the Keychain, can use that information. So we'll expect you'll see far fewer of the Keychain dialogues that tend to happen when Safari or Mail or your application is updated and they need to grab passwords from the Keychain. We shouldn't have to prompt the user so much saying hey, is that really Mail? Do you really want me to give it your password? Because we would have maintained a stable identity across the updates. There's also several other pieces of Leopard that use that identity when the code's signed. I mentioned the Keychain already. The automatic firewall that we just discussed also uses that in order to configure itself.
Parental controls now uses the code signing so that when you select that I'm going to let my kid use Safari or Mail, it has that as a cryptographically signed identity so it can allow that and only that. It's much harder to spoof a, say, World of Warcraft and call that Safari.
I stole that from Perry's talk yesterday. It was a In-Depth Code Signing thing. So authorization services and several other pieces of the infrastructure also use this. All of the gory details are in the Code Signing Guide. This will tell you how to sign your code and what the ramifications are going to be of that. All I know I should tell you is please go sign your code. There's no ill effects to that. It's still going to run on Tiger. Of course, it's going to run on Leopard. Tiger will ignore the signature.
But you'll be able to leverage several benefits in Leopard as you have it today, and we believe that we'll increasingly add more benefits to that over time. Now some of you may have gone to the compiler advances talk yesterday and have seen a little bit of this material already, but we're going to go in a little more depth about what's going on. The first piece is non-executable data. It's also called...I think Intel calls it execute/disable.
Since Intel Tiger shipped, Mac OS X stack has been marked non-executable. The benefit of this is that it makes certain buffer overflow attacks impossible. When a buffer overflow occurs, a typical attack would include delivering instructions that the attacker wants your computer to execute, some kind of a payload; and that would go into a stacked buffer.
It would also overwrite the return address that's on the stack, and that's how the attacker controls the instruction pointer. So we point that at his payload in the stack. That's the classic buffer overflow. But with a non-executable stack, that doesn't work. When the attacker attemps to jump into the stack, they'll get a memory protection fault.
Now there's still other places that the attacker can put his payload. There's the heap and static data segments. But in Leopard, all 64-bit apps extend this protection. So it covers the stack and the heap and other locations. In fact, by default, the entire set of memory regions in a 64-bit process on a Leopard is either writeable or executable.
This is known as WXRX in some circles. So first a 64-bit app that's running on a Intel Core 2 Duo or later or on a G5, the memory layout might be something like this diagram which simply illustrates several memory regions. The green ones are the ones that are writeable but can't be executed, and the blue one is the one in this diagram that can be executed. But it's read only. You can't write to it. By the way, 32-bit apps will continue to remain compatible as they are on Tiger by only providing the non-executable stack. They won't get the extra protection.
This is just another reason to use 64-bit apps and deliver them whenever possible. Now there are some applications that actually do need to generate code, do need to write some instructions to the data segment and then execute it. Just in time filers might be an example. So in order to do that you can just use the unprotect system call in order change the protection on a particular memory region. So all this non-executable data stuff really makes it harder to pull off an exploit. It's going to mitigate a lot of buffer overflows, format string bugs and the other types of bugs we talked about.
But it's not perfect. There's still ways to do an exploit. For example, an attacker, even though he can't write the payload he wants, may find that there's a library already loaded in memory that does just what he wants. Let's use the standard C system function as an example. The standard C system function takes one argument, a string, and passes it to the shell and executes whatever it says. That's pretty powerful.
So if an attacker can arrange that the return address points to the system and provides on the stack a string of the command he wants to run, then he's successfully gained control of your machine.This technique is commonly known as "return-to-libc." Now a way to thwart this is to introduce something called address space layout randomization or ASLR.
A system without ASLR might look something like this. Here we have libSystem. That's our main kitchen sink...I mean, main standard library with the AppKit, Core Services and some other frameworks I've added here for illustration. An attacker might be targeting that system function we talked about in libSystem. He can do that because he knows for all Macs with the same operating system release and same architecture the location, the memory location of that function is the same. So he can jump into that memory location and start executing. As we see here, we start off with the prologue of the function, saving the base pointer. So this is going to work for him. But the reason this works is because the whole address space is predictable.
With ASLR, we load common operating system libraries in a random order, starting from a random page and put random gaps in between the libraries. This makes it much harder. The attacker now may still be aiming for the libSystem system function, but he's maybe using the same address as before.
He can't predict what the address is any longer. So his exploit is going to wind up jumping maybe into unmapped page or maybe into the middle of some data segment in Quartz Core. That's here. He's not going to get the results that he thinks. Now I...and I'm just kidding.
That crash...that crash is what's likely to result, but that crash is a heck of a lot better than getting owned, than letting that attacker have control of your machine. So there's still some areas that are not randomized by default, and those might have something that the attacker can jump into in certain situations. But if you compile your applications with something called pie or position independent executable, then that will cause your application's main executable to be loaded at a random address as well and all the dependent libraries that aren't already in the shared cache.
So you should really do that. Take advantage of that, again. Now this is really only effective for remote attackers, for someone trying to break into your web browser or into your network service. I just felt I should mention that. If you have a local attacker, they can pretty much look for themselves and see where the functions are.
So if you have a malicious user on your system, this doesn't protect you much. But it's really great for those network facing apps. We've got a couple of more options. We have now stack overflow checking as an option in our developer tool chain. This uses a technique called Canaries that's been implemented before by things like stack guard.
The word...the term Canary comes from, of course, the miner's Canary. The miner would go into a shaft; and the danger there is methane gas, carbon monoxide. Sometimes that leaks out and kills you. So bring a Canary along which are much more sensitive to the gases. If the Canary dies, you know you better get out now because the gas is coming.
So we do something similar here. I'll show you how we use a Canary in stack protection. On the right, we have a function, appropriately named bad, that's not very well written. There's a buffer overflow there. There's a fixed size stack buffer, and we're using sprintf on that stack buffer. But we're supplying a couple of parameters that could be unbounded. So, of course, after the path buffer's been filled out, the data will continue to overwrite the return address and you knows what else there. Of course, the key thing is that return address.
If the attacker can control that return address, he can control what the application does to a great degree. If you use the stack protector, it inserts the Canary, a random value, one that the attacker can't predict. Now when the buffer overflow occurs, it'll overwrite the Canary before it gets to the return address and may continue and successfully overwrite the return address.
But the stack protector has added code. When the function is about to return, it checks the Canary. If the Canary is not the same value it started with, then it's all over. The application aborts. Again, not the best thing; but better than being owned. One last thing is object size checking.
This can change some unsafe API usage into some checked behavior. There's a list of them, mostly memory movement and string manipulation functions. You turn it on very...in a very straightforward fashion. You just define four to five source to two. No one asks about the two. Just use the two.
To illustrate what this does, we have the same bad function that we had on the previous slide. I've called it before here, and then what the compiler does to it. So first, of course, we had that sprintf on this fixed size buffer. The compiler changes this into a call to a new function. It's built-in sprintf check and includes something called a builtin_object_size which tells...instructs the compiler if you can at compile time how big this thing is, pass that value here.
So...and not in all cases can it tell. If you're doing dynamic memory allocation off the heap, it's not going to be able to figure that out. But if it's a buffer on...a fixed size buffer on the stack or if it's a fixed size buffer in your data segment or if it's part of a structure where you have the full type definition for that, it can tell. And in those cases, it will do the sprintf as usual. But if it detects an overflow, that it's written more than was in the buffer, then, again, we abort the process. So in summary, today we noted that security really requires both proactive and reactive measures.
Educate yourself on the different types of security vulnerabilities that they are. Certainly do as much testing as possible. Hunt around for your own bugs. But also be prepared. It's important to have a good process in place for that day that someone else outside your organization does report a vulnerability to you. You need to smoothly handle that and roll out an update in a manner appropriate for that kind of a bug.
Fuzzing is one of the, I think, the easiest techniques to use for testing. It's cheap to implement, and it produces lots of results. And it's kind of fun. So if you can do that, add that to your...when you're testing for security issues, I think you'll get a real return on that. And then finally, these new security options, definitely use them. We're using them. We're going to make more, and we're going to use more.
So I hope you enjoyed that. Craig Keithley is going to help us out with the Q&A. And we have some references here. Again, if you or a friend or an enemy knows about or believes they know about a potential vulnerability or security issue in a Apple product, please report that to Apple Product Security. Use a bug report, or e-mail [email protected]. The Secure Coding Guide and the Code Signing Guides are available for you for your reference.