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/14/2003 20:07:06
[ On Monday, July 14, 2003 at 18:00:31 (-0400), der Mouse wrote: ]
> Subject: Re: funlink() for fun!
>
> You are trying to add a way to go from a file descriptor to one
> particular member of that set of pathnames. (And then do something
> with it, but the hard part is that mapping. Depending on which message
> I read, either you're willing to error out unless the set has only one
> element, or you want a rather ill-defined member of the set, one
> somehow related to how the file descriptor was obtained.)
I think I've always been willing to have the funlink() call fail with an
error if it finds the link count of the file to be greater than one.
In any case that has always been my intent. The ultimate goal is to
find a safer way for a process to unlink a temporary file. The ability
to do this to a file passed via stdin, etc., is only a secondary feature.
(and one for which I still can't think of any good application :-)
The caching of the opened filenames obviously optimizes the
implementation of funlink(2) significantly, but of course at some
expense that must be shared with all open() calls (though I suppose
another O_CACHENAME flag could be added to help tune it :-).
The caching of the opened filename also helps deal with the case where
the file has gained additional links, but the original pathname still
points to the opened file.
In fact I'd be just as happy to have funlink() fail if the original
pathname no longer existed -- i.e. do away with the internal ftw() idea
entirely and rely only on the cached opened filename, especially if the
need for funlink(2) was anticipated (as it normally would be) such that
the filename caching would only be done when explicitly requested.
Indeed one would expect no better chance of success if some third part
process had renamed the file to be unlinked behind one's back, so to
speak. The thing we're trying to avoid is some symlink replacing a
directory in the pathname of file being renamed such that the wrong file
is unlinked. Obviously several other things have to be broken for such
a situation to result in a true vulnerability, but still the
availability of funlink(2) would eliminate the need to always carefully
fchdir() into a safe directory, and indeed may eliminate some of the
need for such safe directories in the first place.
> This is possible to do. It is also a major philosophical shift, with
> the corresponding design shift that implies.
I see no philosophical shift implied by my funlink(2) proposal, least of
all any that one might call "major".
The "trivial" implementation of funlink() is (logically at least) no
different than using "find -inum", but of course it can be made safe
from TOCTOU race conditions, which is its whole purpose in the first
place (i.e. to avoid having to safely fchdir() to the directory the file
was originally created in and thus avoid having to find one's way back
to where one started from).
> It may be an interesting
> thing to consider when designing a new OS; it is of no particular value
> for an existing one with an existing commitment to the present design
> (and the philosophy behind it).
On the contrary -- you yourself have pointed out the limitations of
using fchdir() to get to a safe temporary directory and remove temporary
files from it.
This problem of making it easier to safely unlink temporary files is, or
at least should be, still very much in the forefront of concerns for
modern unix systems. We can't rely on the administrator to have
configured temporary directories to always be located on filesystems
separate from other files the caller may have permission to unlink since
this is not even the case for NetBSD out of the box.
BTW, I've also been thinking of another flag for open(2) that could be
used in conjunction with O_CREAT|O_EXCL in order to optimize the
checking of parent directories for "vulnerabilities" (in the same way as
the safe_dir() function I've described before does, but in a far safer
and faster in-kernel implementation). It could be called O_SAFE. In
conjunction with funlink(2), it would make it possble to safely create a
new temporary file directly within a world-writable directory and later
safely unlink it. Caching the opened filename would help make this
operation efficient enough to use in place of a safe sub-directory and
would thus eliminate the need for O_NOACCESS (and O_MKDIR). Strictly
speaking what I've called O_CACHENAME abvoe could be implied by O_SAFE
too, thus making it possible to optimize the whole system even further.
In this scenario I'd be quite happy if funlink(2) always failed with
ESRCH or EINVAL or similar unless the file descriptor was opened with
O_SAFE.
Hmmm.... maybe chdir() should return a file descriptor which read() and
write() would always return EBADF, but fchdir() would be happy to use --
then chdir(".") could be made to always magically succeed even if the
directory was in a totally inaccessible path, thus giving a valid file
descriptor for fchdir() without having to implement O_NOACCESS. Should
not a process always be able to get back to where it was started, even
if it has dropped its privileges (or was started without privileges) and
no longer has the rights necessary to chdir() even if it knew where it
was?
--
Greg A. Woods
+1 416 218-0098 VE3TCP RoboHack <woods@robohack.ca>
Planix, Inc. <woods@planix.com> Secrets of the Weird <woods@weird.com>