Subject: Re: funlink() for fun!
To: der Mouse <mouse@Rodents.Montreal.QC.CA>
From: Greg A. Woods <woods@weird.com>
List: tech-kern
Date: 07/11/2003 13:47:11
[ On Friday, July 11, 2003 at 04:12:57 (-0400), der Mouse wrote: ]
> Subject: Re: funlink() for fun!
>
> > I guess the choice [...] depends on what possible reason one might
> > have for implementing funlink() in the first place, and thus how one
> > defines it.
>
> > If all one is trying to do is flesh out the set of f*() system calls
> > which have file descriptor parameters to make it orthogonal to the
> > set which have pathname parameters then
>
> ...you shouldn't do funlink() in this form in the first place, as
> unlink() is not an operation on a file but an operation on a link to a
> file.
But that's the whole point of funlink() (and perhaps even some of the
other f*() calls, such as fchdir()) -- turn an operation on a file into
an operation on a filename (i.e. a link to a file). funlink() would of
course act upon the file (i.e. the inode and the storage it points to)
as well as the link recorded in the parent directory, since of course it
will also have to mark the file (inode) (and its storage) as free.
I suppose by stretching one's imagination a wee bit it's possible to see
how funlink() could help eliminate a TOCTOU race condition for a process
that must unlink a temporary file in some unsafe place like /tmp.
That's quite a bit of a stretch though and it's an especially long and
unlikely stretch if you consider that "safe" sub-directories can and
should _always_ be used in unsafe directories (since rmdir() is always
safe to use in a world-writable directory). Perhaps funlink() could
have secure programming uses if a process has to remove files created in
a "safe" sub-directory of a world-writable directory and for some reason
it cannot chdir to that sub-directory any longer, but I can't at the
moment imagine any such situation.
> Now, an funlink() that takes an fd on a directory and a (slashless)
> component name, that would be a sensible way to add an fd-based variant
> of unlink().
If the process has an FD open on the parent directory then it should be
able to much more easily just fchdir() there first, obtaining and
keeping an open FD for its PWD for those cases where it has to go right
back to where it started.
For other uses (such as unlinking stdin) such a form would be unusable,
and after all the goal is to define a system call that accepts a file
descriptor and acts upon the file that was opened in the same way as the
s/^f// system call would act when passed a filename referring to the
same file that was opened. As I say above funlink() would actually act
on the the parent directory of the file as well as the file itself.
> But there are more serious things to fix first, like the inability to
> use open()/fchdir() with directories that are execute-only. (To fix
> this right, we need something like O_NOACCESS.)
Yes, I suppose that such an ability could be somewhat helpful at times.
I've always though the only extremely serious omission in the f*()
function call set has been faccess() (and of course there should not
have ever been an access() call since it is inherently insecure); though
I suppose flink(), fsymlink(), and futime() would all be useful when
their operations must be done using already open files, especially when
that file is stdin or similar, but also of course when it is in some
"unsafe" place.
It could also be very useful for secure programming if we had an O_MKDIR
flag for open() that would allow one to safely create a new directory in
some "unsafe" place (in combination with O_CREAT|O_EXCL of course) and
then if the creation succeeds the process can safely just fchdir() right
into it with no further checks necessary (assuming the new directory is
created directly in the only unsafe directory along the path). O_MKDIR
may even eliminate enough of the cases where you'd use your O_NOACCESS
to make O_NOACCESS strictly unnecessary.
I also find open_as_user() useful enough for secure programming that
I've written, and I regularly use, several different implementations,
including the latest one which uses file descriptor passing. I should
turn it into a faster and much simpler system call someday though.
Similarly it would probably be helpful if libc provided implmentations
of safe_chdir() and safe_create_or_truncate().
Which reminds me... we need to make the following rule a lot more
prominently understood, especially among the crowd working on tools like
"sysinst" that help non-security experts configure systems: Never ever
make a world writable directory unless that directory is the root
directory of some mounted filesystem (other than of course the root
filesystem itself). This means /tmp and /var/tmp "MUST" _always_ be
separate filesystems. We can't eliminate insecure code in one fell
blow, but we can eliminate many of the vulnerabilities such code can be
susceptible to by following this simple rule.
> > (I suspect always caching the file's parent directory vnode for all
> > open files as well would also add too many more complications, and it
> > still wouldn't help if the rename changed the file's parent
> > directory.)
>
> It could, because then rename() could notice the change and change the
> cache to refer to the new location.
Yes, that's what I meant by "too many more complications." :-)
--
Greg A. Woods
+1 416 218-0098; <g.a.woods@ieee.org>; <woods@robohack.ca>
Planix, Inc. <woods@planix.com>; VE3TCP; Secrets of the Weird <woods@weird.com>