fmount system call, second attempt

Ricky Beam (Volker.Lendecke@SerNet.DE)
Sun, 14 Sep 1997 00:02:45 +0200


Hello, all!

This is my second attempt towards the fmount system call. I patched
around a bit in fs/super.c, and cleaned up the code path in sys_mount
a bit. At least I think it is a bit easier to follow. And it pushes
dentries a bit deeper :-)

For those who did not read my first posting regarding the fmount call:
This patch implements a new system call which takes a file descriptor
as a mount point. This removes a race condition in smbmount and
ncpmount that could be exploited by playing around with symbolic
links.

Looking forward to your comments,

Volker

diff -urN 2.1.55/arch/i386/kernel/entry.S linux/arch/i386/kernel/entry.S
--- 2.1.55/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.55/include/asm-i386/unistd.h linux/include/asm-i386/unistd.h
--- 2.1.55/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> */

--- 2.1.55/fs/super.c Fri Sep 5 02:07:07 1997
+++ linux/fs/super.c Sat Sep 13 23:54:00 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;
@@ -107,11 +107,10 @@
strcpy(lptr->mnt_devname, tmp);
putname(tmp);
}
- if (dir_name && !IS_ERR(tmp = getname(dir_name))) {
+ if (dir_name) {
if ((lptr->mnt_dirname =
- (char *) kmalloc(strlen(tmp)+1, GFP_KERNEL)) != (char *)NULL)
- strcpy(lptr->mnt_dirname, tmp);
- putname(tmp);
+ (char *) kmalloc(strlen(dir_name)+1, GFP_KERNEL)) != (char *)NULL)
+ strcpy(lptr->mnt_dirname, dir_name);
}

if (vfsmntlist == (struct vfsmount *)NULL) {
@@ -123,7 +122,7 @@
return (lptr);
}

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

@@ -760,11 +759,13 @@
* Anyone using this new feature must know what he/she is doing.
*/

-int do_mount(kdev_t dev, const char * dev_name, const char * dir_name, const char * type, int flags, void * data)
+
+int do_mount(kdev_t dev, const char * dev_name, struct dentry * dir_d, const char * type, int flags, void * data)
{
- struct dentry * dir_d = NULL;
struct super_block * sb;
struct vfsmount *vfsmnt;
+ char *dir_name;
+ unsigned long dir_page;
int error;

error = -EACCES;
@@ -772,18 +773,13 @@
goto out;
/*flags |= MS_RDONLY;*/

- dir_d = namei(dir_name);
- error = PTR_ERR(dir_d);
- if (IS_ERR(dir_d))
- goto out;
-
error = -ENOTDIR;
if (!S_ISDIR(dir_d->d_inode->i_mode))
- goto dput_and_out;
+ goto out;

error = -EBUSY;
if (dir_d->d_covers != dir_d)
- goto dput_and_out;
+ goto out;

/*
* Check whether to read the super block
@@ -793,7 +789,7 @@
error = -EINVAL;
sb = read_super(dev,type,flags,data,0);
if (!sb)
- goto dput_and_out;
+ goto out;
}

/*
@@ -802,24 +798,26 @@
*/
error = -EBUSY;
if (!fs_may_mount(dev))
- goto dput_and_out;
+ goto out;

error = -ENOMEM;
+ if (!(dir_page = __get_free_page(GFP_KERNEL)))
+ goto out;
+ dir_name = d_path(dir_d, (char *) dir_page, PAGE_SIZE);
vfsmnt = add_vfsmnt(dev, dev_name, dir_name);
+ free_page(dir_page);
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 */
+ dget(dir_d); /* see umount */
+ return 0;
}

-dput_and_out:
- dput(dir_d);
out:
return error;
}

-
/*
* Alters the mount flags of a mounted file system. Only the mount point
* is used as a reference - file system type and the device are ignored.
@@ -850,24 +848,6 @@
return 0;
}

-static int do_remount(const char *dir,int flags,char *data)
-{
- struct dentry *dentry;
- int retval;
-
- dentry = namei(dir);
- retval = PTR_ERR(dentry);
- if (!IS_ERR(dentry)) {
- struct super_block * sb = dentry->d_inode->i_sb;
-
- retval = -EINVAL;
- if (dentry == sb->s_root)
- retval = do_remount_sb(sb, flags, data);
- dput(dentry);
- }
- return retval;
-}
-
static int copy_mount_options (const void * data, unsigned long *where)
{
int i;
@@ -911,7 +891,7 @@
* aren't used, as the syscall assumes we are talking to an older
* version that didn't understand them.
*/
-asmlinkage int sys_mount(char * dev_name, char * dir_name, char * type,
+static int sys_dmount(char * dev_name, struct dentry * dir_d, char * type,
unsigned long new_flags, void * data)
{
struct file_system_type * fstype;
@@ -923,19 +903,25 @@
const char * t;
unsigned long flags = 0;
unsigned long page = 0;
+ unsigned long mount_data = 0;
+
+ if ((new_flags & MS_MGC_MSK) == MS_MGC_VAL) {
+ flags = new_flags & ~MS_MGC_MSK;
+ retval = copy_mount_options(data, &mount_data);
+ if (retval < 0)
+ return retval;
+ }

- lock_kernel();
- if (!suser())
- goto out;
if ((new_flags &
(MS_MGC_MSK | MS_REMOUNT)) == (MS_MGC_VAL | MS_REMOUNT)) {
- retval = copy_mount_options (data, &page);
- if (retval < 0)
- goto out;
- retval = do_remount(dir_name,
- new_flags & ~MS_MGC_MSK & ~MS_REMOUNT,
- (char *) page);
- free_page(page);
+ struct super_block * sb = dir_d->d_inode->i_sb;
+
+ retval = -EINVAL;
+ if (dir_d == sb->s_root) {
+ new_flags &= ~MS_MGC_MSK & ~MS_REMOUNT;
+ retval = do_remount_sb(sb, new_flags,
+ (char *) mount_data);
+ }
goto out;
}
retval = copy_mount_options (type, &page);
@@ -989,23 +975,59 @@
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_mount(dev,dev_name,dir_name,t,flags,(void *) page);
- free_page(page);
+ retval = do_mount(dev,dev_name,dir_d,t,flags,(void *) mount_data);
if (retval && fops && fops->release) {
fops->release(inode, NULL);
put_unnamed_dev(dev);
}
dput_and_out:
dput(dentry);
+out:
+ free_page(mount_data);
+ return retval;
+}
+
+asmlinkage int sys_mount(char * dev_name, char * dir_name, char * type,
+ unsigned long new_flags, void * data)
+{
+ struct dentry * dir_d;
+ int retval = -EPERM;
+
+ lock_kernel();
+ if (!suser())
+ goto out;
+
+ dir_d = namei(dir_name);
+ retval = PTR_ERR(dir_d);
+ if (IS_ERR(dir_d))
+ goto out;
+
+ retval = sys_dmount(dev_name, dir_d, type, new_flags, data);
+ dput(dir_d);
+out:
+ unlock_kernel();
+ return retval;
+}
+
+asmlinkage int sys_fmount(char * dev_name, unsigned int dir_fd, char * type,
+ unsigned long new_flags, void * data)
+{
+ struct file *dir_file;
+ struct dentry * dir_d;
+ int retval = -EPERM;
+
+ lock_kernel();
+ if (!suser())
+ goto out;
+
+ if (dir_fd >= NR_OPEN ||
+ !(dir_file = current->files->fd[dir_fd]) ||
+ !(dir_d = dir_file->f_dentry)) {
+ retval = -EBADF;
+ goto out;
+ }
+
+ retval = sys_dmount(dev_name, dir_d, type, new_flags, data);
out:
unlock_kernel();
return retval;