Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

fs: fix overflow in sys_mount() for in-kernel calls

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()?

[akpm@linux-foundation.org: make copy_mount_string() static]
[AV: fix compat mount breakage, which involves undoing akpm's change above]

Reported-by: Ingo Molnar <mingo@elte.hu>
Signed-off-by: Vegard Nossum <vegard.nossum@gmail.com>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Cc: Pekka Enberg <penberg@cs.helsinki.fi>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: al <al@dizzy.pdmi.ras.ru>

authored by

Vegard Nossum and committed by
al
eca6f534 6d729e44

+60 -42
+12 -12
fs/compat.c
··· 768 768 char __user * type, unsigned long flags, 769 769 void __user * data) 770 770 { 771 - unsigned long type_page; 771 + char *kernel_type; 772 772 unsigned long data_page; 773 - unsigned long dev_page; 773 + char *kernel_dev; 774 774 char *dir_page; 775 775 int retval; 776 776 777 - retval = copy_mount_options (type, &type_page); 777 + retval = copy_mount_string(type, &kernel_type); 778 778 if (retval < 0) 779 779 goto out; 780 780 ··· 783 783 if (IS_ERR(dir_page)) 784 784 goto out1; 785 785 786 - retval = copy_mount_options (dev_name, &dev_page); 786 + retval = copy_mount_string(dev_name, &kernel_dev); 787 787 if (retval < 0) 788 788 goto out2; 789 789 790 - retval = copy_mount_options (data, &data_page); 790 + retval = copy_mount_options(data, &data_page); 791 791 if (retval < 0) 792 792 goto out3; 793 793 794 794 retval = -EINVAL; 795 795 796 - if (type_page && data_page) { 797 - if (!strcmp((char *)type_page, SMBFS_NAME)) { 796 + if (kernel_type && data_page) { 797 + if (!strcmp(kernel_type, SMBFS_NAME)) { 798 798 do_smb_super_data_conv((void *)data_page); 799 - } else if (!strcmp((char *)type_page, NCPFS_NAME)) { 799 + } else if (!strcmp(kernel_type, NCPFS_NAME)) { 800 800 do_ncp_super_data_conv((void *)data_page); 801 - } else if (!strcmp((char *)type_page, NFS4_NAME)) { 801 + } else if (!strcmp(kernel_type, NFS4_NAME)) { 802 802 if (do_nfs4_super_data_conv((void *) data_page)) 803 803 goto out4; 804 804 } 805 805 } 806 806 807 - retval = do_mount((char*)dev_page, dir_page, (char*)type_page, 807 + retval = do_mount(kernel_dev, dir_page, kernel_type, 808 808 flags, (void*)data_page); 809 809 810 810 out4: 811 811 free_page(data_page); 812 812 out3: 813 - free_page(dev_page); 813 + kfree(kernel_dev); 814 814 out2: 815 815 putname(dir_page); 816 816 out1: 817 - free_page(type_page); 817 + kfree(kernel_type); 818 818 out: 819 819 return retval; 820 820 }
+1
fs/internal.h
··· 57 57 * namespace.c 58 58 */ 59 59 extern int copy_mount_options(const void __user *, unsigned long *); 60 + extern int copy_mount_string(const void __user *, char **); 60 61 61 62 extern void free_vfsmnt(struct vfsmount *); 62 63 extern struct vfsmount *alloc_vfsmnt(const char *);
+47 -30
fs/namespace.c
··· 1640 1640 { 1641 1641 struct vfsmount *mnt; 1642 1642 1643 - if (!type || !memchr(type, 0, PAGE_SIZE)) 1643 + if (!type) 1644 1644 return -EINVAL; 1645 1645 1646 1646 /* we need capabilities... */ ··· 1871 1871 return 0; 1872 1872 } 1873 1873 1874 + int copy_mount_string(const void __user *data, char **where) 1875 + { 1876 + char *tmp; 1877 + 1878 + if (!data) { 1879 + *where = NULL; 1880 + return 0; 1881 + } 1882 + 1883 + tmp = strndup_user(data, PAGE_SIZE); 1884 + if (IS_ERR(tmp)) 1885 + return PTR_ERR(tmp); 1886 + 1887 + *where = tmp; 1888 + return 0; 1889 + } 1890 + 1874 1891 /* 1875 1892 * Flags is a 32-bit value that allows up to 31 non-fs dependent flags to 1876 1893 * be given to the mount() call (ie: read-only, no-dev, no-suid etc). ··· 1916 1899 /* Basic sanity checks */ 1917 1900 1918 1901 if (!dir_name || !*dir_name || !memchr(dir_name, 0, PAGE_SIZE)) 1919 - return -EINVAL; 1920 - if (dev_name && !memchr(dev_name, 0, PAGE_SIZE)) 1921 1902 return -EINVAL; 1922 1903 1923 1904 if (data_page) ··· 2085 2070 SYSCALL_DEFINE5(mount, char __user *, dev_name, char __user *, dir_name, 2086 2071 char __user *, type, unsigned long, flags, void __user *, data) 2087 2072 { 2088 - int retval; 2073 + int ret; 2074 + char *kernel_type; 2075 + char *kernel_dir; 2076 + char *kernel_dev; 2089 2077 unsigned long data_page; 2090 - unsigned long type_page; 2091 - unsigned long dev_page; 2092 - char *dir_page; 2093 2078 2094 - retval = copy_mount_options(type, &type_page); 2095 - if (retval < 0) 2096 - return retval; 2079 + ret = copy_mount_string(type, &kernel_type); 2080 + if (ret < 0) 2081 + goto out_type; 2097 2082 2098 - dir_page = getname(dir_name); 2099 - retval = PTR_ERR(dir_page); 2100 - if (IS_ERR(dir_page)) 2101 - goto out1; 2083 + kernel_dir = getname(dir_name); 2084 + if (IS_ERR(kernel_dir)) { 2085 + ret = PTR_ERR(kernel_dir); 2086 + goto out_dir; 2087 + } 2102 2088 2103 - retval = copy_mount_options(dev_name, &dev_page); 2104 - if (retval < 0) 2105 - goto out2; 2089 + ret = copy_mount_string(dev_name, &kernel_dev); 2090 + if (ret < 0) 2091 + goto out_dev; 2106 2092 2107 - retval = copy_mount_options(data, &data_page); 2108 - if (retval < 0) 2109 - goto out3; 2093 + ret = copy_mount_options(data, &data_page); 2094 + if (ret < 0) 2095 + goto out_data; 2110 2096 2111 - retval = do_mount((char *)dev_page, dir_page, (char *)type_page, 2112 - flags, (void *)data_page); 2097 + ret = do_mount(kernel_dev, kernel_dir, kernel_type, flags, 2098 + (void *) data_page); 2099 + 2113 2100 free_page(data_page); 2114 - 2115 - out3: 2116 - free_page(dev_page); 2117 - out2: 2118 - putname(dir_page); 2119 - out1: 2120 - free_page(type_page); 2121 - return retval; 2101 + out_data: 2102 + kfree(kernel_dev); 2103 + out_dev: 2104 + putname(kernel_dir); 2105 + out_dir: 2106 + kfree(kernel_type); 2107 + out_type: 2108 + return ret; 2122 2109 } 2123 2110 2124 2111 /*