Skip to content

Commit

Permalink
fs: fix overflow in sys_mount() for in-kernel calls
Browse files Browse the repository at this point in the history
sys_mount() reads/copies a whole page for its "type" parameter.  When
do_mount_root() passes a kernel address that points to an object which is
smaller than a whole page, copy_mount_options() will happily go past this
memory object, possibly dereferencing "wild" pointers that could be in any
state (hence the kmemcheck warning, which shows that parts of the next
page are not even allocated).

(The likelihood of something going wrong here is pretty low -- first of
all this only applies to kernel calls to sys_mount(), which are mostly
found in the boot code.  Secondly, I guess if the page was not mapped,
exact_copy_from_user() _would_ in fact handle it correctly because of its
access_ok(), etc.  checks.)

But it is much nicer to avoid the dubious reads altogether, by stopping as
soon as we find a NUL byte.  Is there a good reason why we can't do
something like this, using the already existing strndup_from_user()?

[[email protected]: make copy_mount_string() static]
[AV: fix compat mount breakage, which involves undoing akpm's change above]

Reported-by: Ingo Molnar <[email protected]>
Signed-off-by: Vegard Nossum <[email protected]>
Cc: Al Viro <[email protected]>
Cc: Pekka Enberg <[email protected]>
Signed-off-by: Andrew Morton <[email protected]>
Signed-off-by: al <[email protected]>
  • Loading branch information
vegard authored and al committed Sep 24, 2009
1 parent 6d729e4 commit eca6f53
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 42 deletions.
24 changes: 12 additions & 12 deletions fs/compat.c
Original file line number Diff line number Diff line change
Expand Up @@ -768,13 +768,13 @@ asmlinkage long compat_sys_mount(char __user * dev_name, char __user * dir_name,
char __user * type, unsigned long flags,
void __user * data)
{
unsigned long type_page;
char *kernel_type;
unsigned long data_page;
unsigned long dev_page;
char *kernel_dev;
char *dir_page;
int retval;

retval = copy_mount_options (type, &type_page);
retval = copy_mount_string(type, &kernel_type);
if (retval < 0)
goto out;

Expand All @@ -783,38 +783,38 @@ asmlinkage long compat_sys_mount(char __user * dev_name, char __user * dir_name,
if (IS_ERR(dir_page))
goto out1;

retval = copy_mount_options (dev_name, &dev_page);
retval = copy_mount_string(dev_name, &kernel_dev);
if (retval < 0)
goto out2;

retval = copy_mount_options (data, &data_page);
retval = copy_mount_options(data, &data_page);
if (retval < 0)
goto out3;

retval = -EINVAL;

if (type_page && data_page) {
if (!strcmp((char *)type_page, SMBFS_NAME)) {
if (kernel_type && data_page) {
if (!strcmp(kernel_type, SMBFS_NAME)) {
do_smb_super_data_conv((void *)data_page);
} else if (!strcmp((char *)type_page, NCPFS_NAME)) {
} else if (!strcmp(kernel_type, NCPFS_NAME)) {
do_ncp_super_data_conv((void *)data_page);
} else if (!strcmp((char *)type_page, NFS4_NAME)) {
} else if (!strcmp(kernel_type, NFS4_NAME)) {
if (do_nfs4_super_data_conv((void *) data_page))
goto out4;
}
}

retval = do_mount((char*)dev_page, dir_page, (char*)type_page,
retval = do_mount(kernel_dev, dir_page, kernel_type,
flags, (void*)data_page);

out4:
free_page(data_page);
out3:
free_page(dev_page);
kfree(kernel_dev);
out2:
putname(dir_page);
out1:
free_page(type_page);
kfree(kernel_type);
out:
return retval;
}
Expand Down
1 change: 1 addition & 0 deletions fs/internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ extern int check_unsafe_exec(struct linux_binprm *);
* namespace.c
*/
extern int copy_mount_options(const void __user *, unsigned long *);
extern int copy_mount_string(const void __user *, char **);

extern void free_vfsmnt(struct vfsmount *);
extern struct vfsmount *alloc_vfsmnt(const char *);
Expand Down
77 changes: 47 additions & 30 deletions fs/namespace.c
Original file line number Diff line number Diff line change
Expand Up @@ -1640,7 +1640,7 @@ static int do_new_mount(struct path *path, char *type, int flags,
{
struct vfsmount *mnt;

if (!type || !memchr(type, 0, PAGE_SIZE))
if (!type)
return -EINVAL;

/* we need capabilities... */
Expand Down Expand Up @@ -1871,6 +1871,23 @@ int copy_mount_options(const void __user * data, unsigned long *where)
return 0;
}

int copy_mount_string(const void __user *data, char **where)
{
char *tmp;

if (!data) {
*where = NULL;
return 0;
}

tmp = strndup_user(data, PAGE_SIZE);
if (IS_ERR(tmp))
return PTR_ERR(tmp);

*where = tmp;
return 0;
}

/*
* Flags is a 32-bit value that allows up to 31 non-fs dependent flags to
* be given to the mount() call (ie: read-only, no-dev, no-suid etc).
Expand Down Expand Up @@ -1900,8 +1917,6 @@ long do_mount(char *dev_name, char *dir_name, char *type_page,

if (!dir_name || !*dir_name || !memchr(dir_name, 0, PAGE_SIZE))
return -EINVAL;
if (dev_name && !memchr(dev_name, 0, PAGE_SIZE))
return -EINVAL;

if (data_page)
((char *)data_page)[PAGE_SIZE - 1] = 0;
Expand Down Expand Up @@ -2070,40 +2085,42 @@ EXPORT_SYMBOL(create_mnt_ns);
SYSCALL_DEFINE5(mount, char __user *, dev_name, char __user *, dir_name,
char __user *, type, unsigned long, flags, void __user *, data)
{
int retval;
int ret;
char *kernel_type;
char *kernel_dir;
char *kernel_dev;
unsigned long data_page;
unsigned long type_page;
unsigned long dev_page;
char *dir_page;

retval = copy_mount_options(type, &type_page);
if (retval < 0)
return retval;
ret = copy_mount_string(type, &kernel_type);
if (ret < 0)
goto out_type;

dir_page = getname(dir_name);
retval = PTR_ERR(dir_page);
if (IS_ERR(dir_page))
goto out1;
kernel_dir = getname(dir_name);
if (IS_ERR(kernel_dir)) {
ret = PTR_ERR(kernel_dir);
goto out_dir;
}

retval = copy_mount_options(dev_name, &dev_page);
if (retval < 0)
goto out2;
ret = copy_mount_string(dev_name, &kernel_dev);
if (ret < 0)
goto out_dev;

retval = copy_mount_options(data, &data_page);
if (retval < 0)
goto out3;
ret = copy_mount_options(data, &data_page);
if (ret < 0)
goto out_data;

retval = do_mount((char *)dev_page, dir_page, (char *)type_page,
flags, (void *)data_page);
free_page(data_page);
ret = do_mount(kernel_dev, kernel_dir, kernel_type, flags,
(void *) data_page);

out3:
free_page(dev_page);
out2:
putname(dir_page);
out1:
free_page(type_page);
return retval;
free_page(data_page);
out_data:
kfree(kernel_dev);
out_dev:
putname(kernel_dir);
out_dir:
kfree(kernel_type);
out_type:
return ret;
}

/*
Expand Down

0 comments on commit eca6f53

Please sign in to comment.