There are some applications that do want to open a file given by the user with setuid privilege.
One bug that used to be quite common was allowing the caller to specify a
file where logging or debug output should go to; be it through a command
line switch or a command line option. Now how can this be done safely?
The answer is, don't do it. There's really no reason for the user
to redirect log messages. Consider a code snippet that logs a message
saying ``bad command line argument'' and logs the option given by the user.
If the user is able to redirect this log message to any file he likes, why
not make it append this to /etc/passwd? Now assume he invokes
program with a command line option containing a command line option
like this, where \n are newline characters:
-blabla::::::\ngotcha::0:0:::\n
What happens is that the program will append several lines to the passwd file; one containing the complaint from the program, and a second containing a valid entry for an account named gotcha with a user and group ID of 0, and no password. Instant root access!
Therefore, you should not give the caller any control over where something gets written.
There are a (small) number of exceptions to this rule, however. For instance, there's a Linux program called v4lconf that configures a frame buffer device, and it must open /dev/fbX as root in order to do this. To do so safely, it must check that the name provided by the user is indeed legal:
if (strncmp(device, "/dev/", 5) /* is it in /dev ? */
|| strstr(device, "..") != NULL /* is it /dev/../foo ? */
|| stat(device, &stb) < 0
|| major(stb.st_rdev) != FRAMEBUFFER_MAJOR)
fatal("invalid device %s\n", device);
The check for .. path components is necessary because otherwise
the caller could use a path name of /dev/../tmp/fb0. Again, this
would introduce a race condition because between the stat()
call and the opening of the file, the attacker would have ample time to
replace the /tmp/fb0 symlink.