This less this video from lesson seven of module three, talks in a little more detail about Time of Check to Time of Use race conditions. So now, I've warned you about access, you're now going to see why access is bad in considerable detail. So let's go onto this slide labeled TOCTTOU Flaw. This stands for Time of Check to Time of Use flaw, and sometimes there's only when 'T' in the middle, TOCTOU flaw same thing. What this refers to as the time of check when you do the access check, to time of use when you use that to open something. There's a window in the middle. You check a condition, and then you act on the condition. What happens if the conditions truth or false it changes when you act on it. So in other words, initially, the condition is true, but before you can act on it it becomes false. That's the issue. The example given here that the file name is rebound to file object that each file system access. What that means is, whenever you use a file name the Kernel always go through this resolution process walking down the set of directories, until it finds the entry in the final directory with the file name, then associated with that is the inode, and then it can go out and get the information about who can access the file, and so forth. So the next slide goes into a little more detail on this. First, we check a condition, and this lists the communist system calls you'll see access status there for the same reason I talked about fstat that you can check permissions there. Then you check the use, and here notice all the common system calls listed require a file name, not a file descriptor, chown, chgrp, and chmod all have versions that use the file descriptor, they begin with fchown, fchgrp, fchmod. Use those, those are safe provided you've got the file descriptor. Otherwise, you have the name and you're acting on a check. So if I can switch the binding after the check, but before the use of got you. In order to be able to do this, two things have to hold. First of all, the program has to be written in such a way that there is this window, this gap between the program check and the use. Now, if you've got that gap that does not mean a race condition is assured. It just means when could occur. If you don't have that gap, there is no way can occur. That's why when we talked about the subprocesses dropping privilege and then opening the file, there's no gap there, the checking is done within the Kernel, and as far as the processes are concerned that is indivisible, and so you'll be able to read it. If you're Kernel can be interrupted at that point or run a separate process, then you've got real problems, but in general that doesn't happen. In order to be able to exploit this, you need to be able to change the binding of objects and names. So that means there has to be an environmental condition, where an untrusted user can do that swapping. As with the programming condition, if the user can do that swapping, it doesn't mean a race condition is occurring, it may be that the program doesn't have that window. The programming condition doesn't hold in which case who cares. If you don't have that environmental condition if only trusted users can do the swapping, then there's no race condition even if the programming condition holds because the only people who would do the exploit are people you trust, and by assumption if you trusted you won't do this. Of course, if you're rude, you don't need to do this. So let's go into the programming condition in little more detail. On the next slide, there's an outline of it, you do a check to check assumptions and validate the assumptions. Yes, this person running this program can open the file. Then the use acts on the assumption. Yes, the person running the program will open the file for them because it said the checks that we could. The attack is you pass the check, and then you do something to invalidate the check before the open occurs. So I switched the file name so it now points to a very sensitive file. That's invalidated the assumption that the check validated. So now, when you do the open I'm simply assuming that the user who's running the process can actually open the file. That is now an assumption because the binding has changed, therefore, the check is no longer valid. So the procedure here is to locate these intervals in the program. Where do you have these intervals between check and use? Now, referencing files is what ties into this whole thing. There are two ways to reference files on Linux systems and most systems. The first one is what's called late binding, and that's the one that's normally used when you give it a file name. Because the object that's returned is bounded the name, but it's rebound that every instance of the name, and the system has to walk down the directory to find it. So that's late binding, and it's essentially as I said earlier an indirect pointer with multiple levels. The second way is by a descriptor, sometimes called the file handle, that's the same thing. When you create that descriptor with an open, for example, or something else, some other call, the Kernel does the resolution and pulls the information of the INO directly into the Kernel memory, and the descriptor or index or handle will get you right to that in core information, which includes the location of things on disks. So this is if you like early binding. It's never rebound because the binding is there. If you want it rebound you have to delete or you have to close the file descriptor or somehow explicitly dissociate, do the dissociation. This brings us to the intervals. There are four types of intervals. The first is where you do the check and the use both with file names. In that case, the programming condition holds because they can rebind between the check in the use. The second is where I check with the file name, and then use the descriptor. Well, again, I can do the rebind me of the file name to something other than what the descriptor identifies. So the programming condition occurs. Same is true if you do the check with the descriptor, and then using the name you act. Because again I can change what the name is bound to, so it's no longer bound to this thing the descriptor points to. The last one is where I do both the check and the use with the same descriptor. In that case, there's no way to switch the binding of the descriptors, because you've to close file and open it, and the program has the file opens, so unless it's badly programmed, it's not going to be closed. So in that case, the programming condition does not hold, that's the safest one. So how do you detect this? Well, you basically build a call graph, which shows which functions call which other functions, and then you look for the checks and the uses in the functions, and mark the function calls as either check, use, or both, and then you just scan the graph looking for checks followed by users. Be sure in this case that the objects you're passing in or rather that what you're passing in refers to the same thing. First, it refers to the same thing, but in general that's the approach. In particular, look for pairs of these checks and uses that refer to the same file. They refer to different files typically it's not a problem, but if they refer to the same file, it is. Incidentally, be careful, because there may be one file with two different names. Either symbolic links or hard links, and in that case, even though your scan of the program may show them as different, they're really the same. So this is something you have to do very carefully. The next slide shows a quick and dirty way to do it. I'll talk about an example where this was done rather successfully. Essentially, go into the function, look for two successive system calls that use the same name, one being the check and one being the use. You'd have all sorts of problems with this, for example, this quick and dirty check doesn't handle macros, doesn't handle a whole bunch of things. The names of the arguments are checked. These are usually variables, but the values we're already talking about now are not checked. So the same variable may mean two different things. So I can say it's very, very quick and dirty. But I'll give you an example of how successful this was when we did it. Now, in addition to the programming condition you need the environmental condition to hold, and the environmental condition basically is very simple. Can the user or any untrusted user alter or replace the thing to which the file name refers or the file, in this case, file name refers? If the answer is yes, and there's a programming condition true, it's exploitable. Now, the policy that you deal with that your system has dictates whether or not changing the file is all you need to do. If you're reading or writing, the question is, can you change something further up in the path? That is you may not be able to change the object, but you may be able to change the path, what the path means so it goes off to a different object, and this brings up the whole notion of a trustworthy file. These are files which an untrusted user can't alter or replace, and the idea is if the user is trustworthy, they're not going to exploit a race condition. Therefore, don't worry about them. If they are untrusted, then they will, and so you have to worry about them. To trustworthy file is one that only trusted users can play games with. The next slide goes into this in a little bit more detail. The function returns true if the argument can't be altered by an untrusted user, and again this refers to the binding of the name to the object. Now, you can prove in fact that as you give a path name to trust, and it's a directory slash file, that's true if and only if both the directory and the file are a trustworthy. If the directory is actually a symbolic link, so this is symbolic link to a directory containing the file f, the symbolic link has to be trustworthy, and also the directory containing the file is to be trustworthy, essentially that goes back to the first case. So you can write this function recursively, and we'll get into that in a moment. The next slide simply shows in a little more detail what it means, but the bottom line is, if the trust function returns true, then there's no environmental condition, and therefore, the race condition doesn't hold or rather there's no way to exploit a race condition, assuming of course you program it correctly.