> I am unable to understand why access() system call is a security hole
> .Can anyone explain how it can be a security hole .
here's a very informative post on the subject of access(), written
by Chris Torek a while back:
---------------------------------------------------------------------
The access() system call does the wrong thing. It really should
not even exist! (The "system call", that is. If you think access()
*should* exist, read on. :-) )
Unix has the concept of "set user ID", where a program can run "as
user X" but "on behalf of user R". That is, user R starts the
program, but the program runs with the permissions of user X.
Now, when "user X" is the super-user, i.e., the program is setuid
0 or "root", the program will automatically have all possible
permissions. (This is more or less the definition of "the super-user":
uid 0 is all-powerful; anything anyone else can do, the super-user
can also do, and even a few more things on top of that.)
So, suppose you have a setuid-root program so that when run it can
always do anything. Suppose further that this program wants to
make sure that User R -- the "real" user -- is allowed access to
some particular file. The original (broken) solution, stuck into
Unix and forever after perpetuated, was to add the access() system
call. It is defined as: "tell me whether the real user -- user R
-- has permission to read, write, and/or execute this here file
name."
The problem with this approach is quite simple. By the time you get
the answer, it is out of date!
Suppose that setuid-root program "writefile" gets the name of a file
from "31337 h4x0r", who is trying to break security on the machine.
The writefile program attempts to keep the cracker from breaking
security by calling access(). The cracker says:
please write on /tmp/zorch
so writefile() does:
char *filename;
...
if (access(filename, W_OK)) {
fprintf(stderr, "%s: permission denied.\n", filename);
exit(1);
}
... do a little more work, or maybe even no more work, then ...
... open or fopen the file and write on it.
Now all the cracker has to do is run:
% touch /tmp/zorch
% ls -l /tmp/zorch
-rw-r--r-- ... cracker ...
% writefile /tmp/zorch & (rm /tmp/zorch; ln /etc/passwd /tmp/zorch)
Now there is a race, which the cracker hopes will go like this:
First, "writefile" access()es /tmp/zorch while "cracker" still owns
it, so the access() call will see that /tmp/zorch is indeed writable
by user "cracker". Next, the "rm" runs and removes /tmp/zorch.
Then the "ln" runs and makes /tmp/zorch a link (or, with "ln -s",
a symlink) to /etc/passwd. Last, "writefile" opens /tmp/zorch --
which is really /etc/passwd -- and writes on it. The open succeeds
because "writefile" is running as the super-user.
It may take many runs to "win" the race and cause writefile to
stomp on /etc/passwd, but eventually it *will* happen. There is
no fix for this: the problem is one of atomicity, and separating
the access() test and the actual opening results in a "non-atomic"
sequence, so no matter what else you do, if you use access() first,
and later do the open(), there will still be a race.
The solution is not to use access() at all. Modern Unix systems
offer some method -- usually seteuid() or setreuid() -- by which
a setuid program can temporarily "give up" its specialness, actually
do the open (or whatever), and then "resume" its specialness. This
way the open call is atomic: it tests to make sure that the cracker
has permission to work on /tmp/zorch, and that succeeds or fails
based on whether /tmp/zorch is a link or not. If the cracker
removes and replaces /tmp/zorch after the open() has returned, it
does not matter; the program still has the original file, not the
link.
Thus, the access() call completely fails at its job of providing
security for setuid programs. What about for "non-secure" programs,
that do not run setuid?
Here the access() call does not create security holes, so this
objection to its very existence evaporates. "Non-secure" programs
can indeed use access(). But it still has the very same flaw that
created the security hole in the original program! It still gives
an "out of date" answer. The out-of-date answer no longer poses
a security risk since there is no security -- but it is just as
out of date. Your program is going to have to make sure that the
ultimate operation (open, or whatever) succeeds, in case access()'s
answer was right at the time but became wrong.
Thus (I argue) you might as well just do the operation, and see if
it worked. The answer from access() cannot be trusted, so why
bother?
Now, there is a valid argument for having some function that predicts
whether some future operation is likely to succeed. In particular,
this is good for interactive programs that give users a chance to
change their minds.
This function can, of course, be implemented directly via the stat()
call. The stat() operation will tell you whether the file exists,
and if so, what its permissions are. You can then calculate whether
you have sufficient permissions to have done the operation on the
file whose information stat() gave you, back at the time you did
the stat().
There is no particular reason not to have a C library "wrapper"
function, perhaps even called access(), that does the stat() for
you. But there is no reason that this has to be a system call --
and it definitely *should not* be used in "secure", setuid programs,
because it just does not work for that.
Note that the stat() method, while more work, is also more general.
You can not only figure out *whether* some future operation is
fairly likely to succeed or fail, but also *why*. If the operation
is likely to fail, you can work backwards through the path, looking
at directories, to see exactly where the problem is. So access(),
if it exists at all, is merely a convenience function for the lazy.
Better programs will end up needing stat() anyway. Thus, the
important system call is stat(), not access(). Also, I claim that
access() is probably better spelled: "could_likely_access()". :-)