In this video, I'm
going to show you a Classic Race Condition.
If you recall from video one,
we have an access control check being done on a
file.The file is referred to by
name and so the check is
done on the object bound to the name using that name.
But when we do the open,
the open again finds the name,
is given the name, finds
the object that name refers to and opens it.
What happens if the object that the name refers to in
the access control check is not the same as
the object as the object the name refers to in the open?
The name and object references,
the way they're tied together is called the binding.
The assumption here is that, the binding
is not changed on those two lines,
it refers to the same object.
But if I could change that, it doesn't work.
So there's a way around this, use file descriptors.
The reason is that,
every time I refer to a file by name,
the kernel has to take that name and
resolve it to find the actual object on disk,
to find the inode to which that name refers.
But when I get the file descriptor after an open,
I go in and I look at the file descriptor.
The file descriptor simply points to the inode directly.
There's no need for the kernel to resolve
anything because the file descriptor is
actually an index into an array that contains
the inode which is
loaded into core when I opened the file.
So what this means is,
the file descriptor if you like is a direct pointer.
When I refer to the file descriptor,
I immediately get to the object.
But when I refer to the name,
I go through a series of pointers as
the kernel resolves the name to get to the object.
During that resolution,
during all those intermediate resolutions,
the binding can change.
Here's another instance or example of how this works.
It's very common to say, "Okay.
Now that we've opened the file using file descriptors,
now we can go ahead and check
whether or not you can access
it and if you can't access it then we just close it."
This is exceptionally appealing.
It's also completely wrong as the slide shows.
The problem here is,
you're still resolving the name in both cases,
in both the access check and the open.
I create XYZ to point to
the sensitive file that I want to read.
As soon as it's open,
I changed that XYZ to refer to something I can see.
If I already opened the file,
you would had access to all that privileged information,
the check says, "Well,
it's not really protected,
this is a new object that I own and can acts,
can read, so I'll go ahead and let it through."
So it's still a race condition.
The descriptor is bound to the object
not to the name and you're using the name here twice.
By the way, if you notice,
here I'm using this standard libraries,
so technically fp is a file pointer.
However, standard IO libraries
file pointers simply point to
a structure internal to
the library and in
that structure the file descriptor is located.
So even if you
use the standard IO library and get file pointers,
everything I say about file descriptors
applies to file pointers as well.
Now, how do you avoid this?
Well, there are a couple of ways.
Some systems have a system call called f-access.
If you have that, then you open
the file and give f-access the file descriptor.
The f-access system call
does not go out and resolve anything.
It simply looks at the file descriptor,
goes into the array containing
the inode and then checks your permissions.
So it never goes out on disk,
never does any kind of resolution,
just uses the file pointer
immediately, file descriptor immediately.
Unfortunately, most systems I've seen
don't have this wonderful system call,
but they do have another called f-stat.
This returns status information for the file.
So what you do is
essentially the same thing as you did before.
Open the file, do
f-stat on the file descriptor and now you've got
the information you need specially the UID of the file,
the owner of the file, the group IDs of the file,
and the permission modes of the file and so now
you can do your own test.
The slide access usage has
some information about that, the way you do that.
Now, when is access safety used?
The system call access,
because it doesn't sound very safe, actually it is.
It's very safe if the path you give
it is a regular file
or directory or device or something like that.
That help contains the file,
resolves to ultimately file,
and the file and all of
its ancestor directories are in
writable by any untrusted user.
So in other another words,
what you want here is a set of
trustworthy directories and by trustworthy
I mean only trusted people can write to it.
If you can't guarantee that,
the safest way to check the file is simply, open a pipe,
spawn a sub-process using fork,
reset the effect of UID and then
try to open the file using the subprocess.
If you succeed,
then send the file descriptor
or send a message back to the parents saying,
"Yes it works, it's safe."
Otherwise, don't, send back a no.
On some systems, you can send back
file descriptors over the pipe,
if you can do that I very strongly recommend it.
You can't if the path is
untrusted and the only way
to do this correctly is to open a pipe,
a spawn a subprocess with fork,
reset the effect of UID of the process to the owners UID,
to the real UID and then open the file in the subprocess.
If that works, then the subprocess simply
copies the contents of the file into
the pipe or reads from the pipe
and writes it out to the file
depending on what you're doing.
That way, the pound process which is
privileged never opens the file and never touches it.
So that way you don't have to worry about
is the file open because it's
privileged or root or is it
open because I can legitimately write to it.
If you can't legitimately write to it,
the open will fail in the subprocess and you're done.
When you do check the file path,
there are a couple of things to check for.
The first thing is a symbolic link.
A symbolic link really is a file containing
a path that the kernel treats the kernel
will then take that path and resolve it to get to
the file or the directory that
the symbolic link points to.
The problem with symbolic links is that,
they hide the first part
of the path up until where the link comes in.
So really, if you think of a path as a graph with
each directory in the path in the file at the end
being a node and the lines
being showing the way they are linked together.
The symbolic link has the second path coming in and
joining that first path somewhere in
the middle and you have
to check all directories on
that first path not on the second.
So that's what you need to check.
Also, when you're checking for the ability to write,
be very sure that you check those writing ability,
the ability to write in the ancestor directories,
and also check for real UIDs and GIDs,
not the effect of ones.
When you have to do this,
the effect of one will probably allow anything,
it's probably going to be root.
But, if you want to check for real UIDs,
then that's the UID of the person running
the process and it's probably not root.
This access system call brings up
a very common misunderstanding or air insecurity.
The rule of security to follow as just
because you can do it doesn't mean you should.
That's actually a very good rule in life in most cases.
In this case, just because you can use access,
that doesn't mean you should.
Check to be sure that the path you're giving
the access that its first argument is trustworthy.
Use subprocesses and have them drop from privileges,
subthreads are fine in this context
as long as you can get the thread drop privilege,
that handles the problem.