fmount system call

Volker.Lendecke (lendecke@math.uni-goettingen.de)
Wed, 23 Jul 1997 18:18:18 +0200


-----BEGIN PGP SIGNED MESSAGE-----

Linus,

during the smbfs-rewrite I remembered the security problem in smbmount
and ncpmount. Both programs are designed to let the user mount remote
file systems of his own. This is necessary because the remote file
systems are per-user password protected. To do this, they check
whether the user has write permissions on the mount point and then
mount the file. Playing with links between the check and the real
mount creates quite well-known races.

As far as I can see this problem can be removed by a system call that
accepts a file descriptor as a mount point. The following patch
implements a fmount system call that does exactly this. It is a quick
hack that has to be cleaned up. Before putting more work into this, I
would like to ask whether this has could get into the standard
kernel.

Regards,

Volker

diff -urN 2.1.46/arch/i386/kernel/entry.S linux/arch/i386/kernel/entry.S
- --- 2.1.46/arch/i386/kernel/entry.S Thu Jul 10 16:20:34 1997
+++ linux/arch/i386/kernel/entry.S Wed Jul 23 16:33:55 1997
@@ -528,6 +528,7 @@
.long SYMBOL_NAME(sys_nfsservctl)
.long SYMBOL_NAME(sys_setresgid) /* 170 */
.long SYMBOL_NAME(sys_getresgid)
- - .rept NR_syscalls-171
+ .long SYMBOL_NAME(sys_fmount)
+ .rept NR_syscalls-172
.long SYMBOL_NAME(sys_ni_syscall)
.endr
diff -urN 2.1.46/fs/super.c linux/fs/super.c
- --- 2.1.46/fs/super.c Sat Jul 19 21:10:37 1997
+++ linux/fs/super.c Wed Jul 23 17:28:31 1997
@@ -89,7 +89,7 @@
/* NOTREACHED */
}

- -struct vfsmount *add_vfsmnt(kdev_t dev, const char *dev_name, const char *dir_name)
+static struct vfsmount *add_vfsmnt(kdev_t dev, const char *dev_name, const char *dir_name)
{
struct vfsmount *lptr;
char *tmp;
@@ -123,7 +123,7 @@
return (lptr);
}

- -void remove_vfsmnt(kdev_t dev)
+static void remove_vfsmnt(kdev_t dev)
{
struct vfsmount *lptr, *tofree;

@@ -945,6 +945,157 @@
}
}
retval = do_mount(dev,dev_name,dir_name,t,flags,(void *) page);
+ free_page(page);
+ if (retval && fops && fops->release) {
+ fops->release(inode, NULL);
+ put_unnamed_dev(dev);
+ }
+dput_and_out:
+ dput(dentry);
+out:
+ unlock_kernel();
+ return retval;
+}
+
+int do_fmount(kdev_t dev, const char * dev_name, struct dentry *dir_d, const char * type, int flags, void * data)
+{
+ struct super_block * sb;
+ struct vfsmount *vfsmnt;
+ int error;
+
+ if (!(flags & MS_RDONLY) && dev && is_read_only(dev))
+ return -EACCES;
+ /*flags |= MS_RDONLY;*/
+
+ dir_d = dget(dir_d);
+ error = PTR_ERR(dir_d);
+ if (IS_ERR(dir_d))
+ return error;
+
+ if (dir_d->d_covers != dir_d) {
+ dput(dir_d);
+ return -EBUSY;
+ }
+ if (!S_ISDIR(dir_d->d_inode->i_mode)) {
+ dput(dir_d);
+ return -ENOTDIR;
+ }
+ if (!fs_may_mount(dev)) {
+ dput(dir_d);
+ return -EBUSY;
+ }
+ sb = read_super(dev,type,flags,data,0);
+ if (!sb) {
+ dput(dir_d);
+ return -EINVAL;
+ }
+ if (sb->s_root->d_covers != sb->s_root) {
+ dput(dir_d);
+ return -EBUSY;
+ }
+ vfsmnt = add_vfsmnt(dev, dev_name, NULL);
+ if (vfsmnt) {
+ vfsmnt->mnt_sb = sb;
+ vfsmnt->mnt_flags = flags;
+ }
+ d_mount(dir_d, sb->s_root);
+ return 0; /* we don't dput(dir) - see umount */
+}
+
+asmlinkage int sys_fmount(char * dev_name, unsigned int fd, char * type,
+ unsigned long new_flags, void * data)
+{
+ struct file_system_type * fstype;
+ struct dentry * dentry = NULL;
+ struct inode * inode = NULL;
+ struct file_operations * fops;
+ kdev_t dev;
+ int retval = -EPERM;
+ const char * t;
+ unsigned long flags = 0;
+ unsigned long page = 0;
+
+ struct file * dir_file;
+ struct dentry * dir_dentry;
+
+ lock_kernel();
+ if (!suser())
+ goto out;
+
+ if (fd >= NR_OPEN ||
+ !(dir_file = current->files->fd[fd]) ||
+ !(dir_dentry = dir_file->f_dentry)) {
+ retval = -EBADF;
+ goto out;
+ }
+
+ if ((new_flags &
+ (MS_MGC_MSK | MS_REMOUNT)) == (MS_MGC_VAL | MS_REMOUNT)) {
+ /* remounting is not possible here */
+ retval = -EINVAL;
+ goto out;
+ }
+ retval = copy_mount_options (type, &page);
+ if (retval < 0)
+ goto out;
+ fstype = get_fs_type((char *) page);
+ free_page(page);
+ retval = -ENODEV;
+ if (!fstype)
+ goto out;
+ t = fstype->name;
+ fops = NULL;
+ if (fstype->fs_flags & FS_REQUIRES_DEV) {
+ dentry = namei(dev_name);
+ retval = PTR_ERR(dentry);
+ if (IS_ERR(dentry))
+ goto out;
+
+ inode = dentry->d_inode;
+ retval = -ENOTBLK;
+ if (!S_ISBLK(inode->i_mode))
+ goto dput_and_out;
+
+ retval = -EACCES;
+ if (IS_NODEV(inode))
+ goto dput_and_out;
+
+ dev = inode->i_rdev;
+ retval = -ENXIO;
+ if (MAJOR(dev) >= MAX_BLKDEV)
+ goto dput_and_out;
+
+ fops = get_blkfops(MAJOR(dev));
+ retval = -ENOTBLK;
+ if (!fops)
+ goto dput_and_out;
+
+ if (fops->open) {
+ struct file dummy; /* allows read-write or read-only flag */
+ memset(&dummy, 0, sizeof(dummy));
+ dummy.f_dentry = dentry;
+ dummy.f_mode = (new_flags & MS_RDONLY) ? 1 : 3;
+ retval = fops->open(inode, &dummy);
+ if (retval)
+ goto dput_and_out;
+ }
+
+ } else {
+ retval = -EMFILE;
+ if (!(dev = get_unnamed_dev()))
+ goto out;
+ }
+
+ page = 0;
+ if ((new_flags & MS_MGC_MSK) == MS_MGC_VAL) {
+ flags = new_flags & ~MS_MGC_MSK;
+ retval = copy_mount_options(data, &page);
+ if (retval < 0) {
+ put_unnamed_dev(dev);
+ goto dput_and_out;
+ }
+ }
+ retval = do_fmount(dev,dev_name,dir_dentry,t,flags,(void *) page);
free_page(page);
if (retval && fops && fops->release) {
fops->release(inode, NULL);
diff -urN 2.1.46/include/asm-i386/unistd.h linux/include/asm-i386/unistd.h
- --- 2.1.46/include/asm-i386/unistd.h Thu Jul 10 16:20:54 1997
+++ linux/include/asm-i386/unistd.h Wed Jul 23 16:48:49 1997
@@ -177,6 +177,7 @@
#define __NR_nfsservctl 169
#define __NR_setresgid 170
#define __NR_getresgid 171
+#define __NR_fmount 172

/* user-visible error numbers are in the range -1 - -122: see <asm-i386/errno.h> */

-----BEGIN PGP SIGNATURE-----
Version: 2.6.3i
Charset: noconv

iQCVAwUBM9YurT/9BWnmOc5FAQGk/wQAhyQYM4X2kZwxOuOzSxWitikUF8QeBIwe
bxYSFi3SwcqNYIvos4dDxTI7ed3ou1yoGMi98sHZvB8yd5d1T/LCezuB47x0kWXl
vQkaKEuexs89ubX9XX6q4FkJ9Dvz97I5GxKd408W1KWOgM04z5HvN4kdnhzHV3p3
am+WaRgG2Bk=
=7wfw
-----END PGP SIGNATURE-----