In most systems, this will work if it's done correctly.
It's called file locking.
Conceptually, when a process puts
a lock on a file no one else can access it.
So, if the second process tries to
switch files, it blocks.
When the first process releases the lock,
then the second one can do the switch
but by then the first process is done with the file.
Now, the whole purpose of locking is
to assure indivisibility between
the "Check " and the "Use".
The idea is you lock the file
before the check and then you unlock it after the use.
Now, when you do this you have to be careful,
because the semantics of
different systems differ even now.
So, I'm going to talk about two system lockings,
which are archetypal Linux locks and FreeBSD locks.
With Linux, you have two types of locks,
advisory locking and mandatory locking.
Advisory locking in this condition is worthless.
The reason is advisory
locking assumes cooperating processes.
So, for example if you have
a database file and two processes are running,
that will want to update it.
The first one grabs an advisory lock.
The second one when it wants to
update the database, checks to the lock.
Gets back a mess.
It gets back a code indicating,
"Hey, that file is locked."
It says, "Okay, I'll wait till the lock's released."
The problem with advisory locks when you're dealing with
race conditions that are
malicious like we're talking about,
is that the attacker is not going to honor that lock.
The attacker will just go ahead and make the change,
and the advisory lock doesn't stop them.
But mandatory locking does.
When a process grabs a mandatory lock on a file,
no other process can access
that file until the lock is released
and the operating system forces the second and
all other processes to honor the lock.
So, you don't have an option.
That's why it's mandatory.
The file is locked, that's it.
When the lock is released,
then others can access it.
The next slide shows the way you do
this sum with system calls.
F-lock or fcntl, either of them will do it.
In fact, they believe fcntl calls f-lock.
These are for advisory locks.
For mandatory lock, you've got to use fcntl.
Also, the file system on Linux has to be mounted
with a special option to allow
mandatorian locks to be enabled them.
Mand is the actual option given to mount.
What this does is it turns on
the set gid bit and turns off the group execute bit to
indicate that the files
are available for mandatory locking.
The process does the appropriate fcntl to lock it.
No one else can access it because of the way
the kernel enforces the locking.
When it's done, tells fcntl to release the lock.
On the most of the Linux systems,
at least the ones I looked on,
root was forced to honor this as well.
So, when you do the mandatory locking
you are also locking out root.
This may be what you want,
on the other hand it may be
a very very rude surprise to root.
But it turns out
the mandatory locks have problems as well.
The problem again comes around through synchronicity.
Let's say process one reads a file.
Process two blocks it.
It alters it and then unlocks it.
Meanwhile, process one which isn't edited,
the contents have been changed.
Process one writes out what was originally read.
You've overwritten the changes to process two.
This is the classic readers writers problem.
What would actually have to happen here,
is that process one would have to grab
the lock first in order to prevent this from happening,
or process two would have to grab the lock
before process one try to read the file.
Now, if root wants access and the file is locked,
root can't get access.
However, if it kills the process holding the lock,
that releases the lock.
The kernel smart about this,
when the process terminates the lock is gone.
Also, you have issues with read and
write if the system calls are
implemented in a divisible way which is typical of Linux.
If someone does a write and starts to write,
and then someone else grabs the mandatory lock,
the write will complete.
So, even though the file seems locked,
it's actually going to change.
The same thing with read.
These are artifacts of the Linux kernel,
the way Linux implement system calls and
locks and there's not really
anything you can do about it.
The last note which is implicit throughout this but I
wanted to make it explicit, I locked the file.
Great. The grandparent directory,
in other not the directory containing the file but
the one containing the directory
containing the file, is world writeable.
So, I move the directory
containing the locked file to another name.
Then I simply create another directory with its name in
another file with the name of
the file that's locked, and I'm done.
So, locking the file does not prevent the race condition.
It just makes it harder to exploit.
If the locking is done under favorable circumstances,
that is you grab it before anyone
else is able to open the file,
then you in pretty good shape.
FreeBSD does things a little differently.
It provides something called openat.
The idea here is the directory is opened,
and given when you open the directory
you get a file descriptor.
So, you give that file descriptor
to openat and then you give it to path.
What that says is okay.
Starting with the directory whose file descriptor
you're given, resolve the path.
If it happens that path is just the filename,
then you're giving it the directory which is open.
Then the only thing the attacker can do is
switch the binding of the file,
can't do anything with any answers to
directories because those are
controlled by a file descriptor.
Just be sure the one that you open is
the one that's right that's
far down the path as you can trust.
That makes life a lot easier