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.