funlink() functionality for Linux 2.4 Michal Zalewski Why would you need this patch? Unlinking files is generally a tricky business. Traditionally, it is next to impossible to make sure the file you're actually removing is the file you've lstat()ed microseconds ago. This is a major concern with utilities such as /tmp cleaners and other automated tools of this nature, because fooling the tool may result in compromising the security of applications that create temporary files in sticky-bit world-writable directories. As of today, all of them either have the vulnerability, or have a limited functionality (such as being able to remove non-root files only)... or, in some cases, both. How to use funlink()? Errr, first, patch your kernel, add sys_funlink to .S files for an appropriate architecture, recompile, install... Then, do the following: - fd=open("/file/to/test",O_NOFOLLOW|O_NONBLOCK|O_RDONLY) - fstat(fd...) - and decide what to do with the file - funlink(fd,"/file/to/test") funlink() will return EPERM if /file/to/test is no longer the same inode as the file open as 'fd'; EBADFD if fd is not open, normal unlink() behavior occurs otherwise. Things that are worth implementing to make this thing complete: - O_SYMLINK; so that you can 'open' a symlink and get a dummy file descriptor that can be stated or read for link target and written for the same. It would be also pretty useful for fchown(), fchmod(), etc. - frename - frmdir - flink and fsymlink diff -uri linux-2.4-old/fs/open.c linux-2.4/fs/open.c --- linux-2.4-old/fs/open.c Fri Oct 12 13:48:42 2001 +++ linux-2.4/fs/open.c Fri Dec 6 18:50:16 2002 @@ -616,6 +616,83 @@ return error; } + +asmlinkage long sys_funlink(unsigned int fd, const char * filename) +{ + int error; + char * name; + struct dentry * target; + struct file * cmp_file; + struct nameidata nid; + + /* Lookup the file descriptor */ + cmp_file = fget(fd); + + if (!cmp_file) + return -EBADFD; + + /* Fetch filename */ + name = getname(filename); + + if(IS_ERR(name)) { + error=PTR_ERR(name); + goto funlink_exit5; + } + + /* Get path data */ + if (path_init(name, LOOKUP_PARENT, &nid)) { + error = path_walk(name, &nid); + if (error) + goto funlink_exit4; + } + + /* Don't unlink directories, you. */ + if (nid.last_type != LAST_NORM) { + error = -EISDIR; + goto funlink_exit3; + } + + /* Grab the victim and hold him */ + down(&nid.dentry->d_inode->i_sem); + target = lookup_hash(&nid.last, nid.dentry); + error = PTR_ERR(target); + + if (IS_ERR(target)) + goto funlink_exit2; + + /* Whoops. */ + if (nid.last.name[nid.last.len]) { + error = !target->d_inode ? -ENOENT : + S_ISDIR(target->d_inode->i_mode) ? -EISDIR : -ENOTDIR; + goto funlink_exit1; + } + + /* Compare the victim and the descriptor */ + if (target != cmp_file->f_dentry) { + error = -EPERM; + goto funlink_exit1; + } + + /* Execute the victim */ + error = vfs_unlink(nid.dentry->d_inode, target); + +funlink_exit1: + dput(target); +funlink_exit2: + up(&nid.dentry->d_inode->i_sem); +funlink_exit3: + path_release(&nid); +funlink_exit4: + putname(name); +funlink_exit5: + fput(cmp_file); + + return error; + +} + + + /* * Note that while the flag value (low two bits) for sys_open means: * 00 - read-only