fat: fix VFAT compat ioctls on 64-bit systems

If you compile and run the below test case in an msdos or vfat directory on
an x86-64 system with -m32 you'll get garbage in the kernel_dirent struct
followed by a SIGSEGV.

The patch fixes this.

Reported and initial fix by Bart Oldeman

#include <sys/types.h>
#include <sys/ioctl.h>
#include <dirent.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
struct kernel_dirent {
         long            d_ino;
         long		d_off;
         unsigned short  d_reclen;
         char            d_name[256]; /* We must not include limits.h! */
};
#define VFAT_IOCTL_READDIR_BOTH  _IOR('r', 1, struct kernel_dirent [2])
#define VFAT_IOCTL_READDIR_SHORT  _IOR('r', 2, struct kernel_dirent [2])

int main(void)
{
         int fd = open(".", O_RDONLY);
         struct kernel_dirent de[2];

         while (1) {
                 int i = ioctl(fd, VFAT_IOCTL_READDIR_BOTH, (long)de);
                 if (i == -1) break;
                 if (de[0].d_reclen == 0) break;
                 printf("SFN: reclen=%2d off=%d ino=%d, %-12s",
 		       de[0].d_reclen, de[0].d_off, de[0].d_ino, de[0].d_name);
 		if (de[1].d_reclen)
 		  printf("\tLFN: reclen=%2d off=%d ino=%d, %s",
 		    de[1].d_reclen, de[1].d_off, de[1].d_ino, de[1].d_name);
 		printf("\n");
         }
         return 0;
}

Signed-off-by: Bart Oldeman <bartoldeman@users.sourceforge.net>
Signed-off-by: OGAWA Hirofumi <hirofumi@mail.parknet.co.jp>
Cc: <stable@kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
This commit is contained in:
OGAWA Hirofumi 2007-05-08 00:31:28 -07:00 committed by Linus Torvalds
parent b247e8aaf2
commit c483bab099

View file

@ -422,7 +422,7 @@ int fat_search_long(struct inode *inode, const unsigned char *name,
EXPORT_SYMBOL_GPL(fat_search_long);
struct fat_ioctl_filldir_callback {
struct dirent __user *dirent;
void __user *dirent;
int result;
/* for dir ioctl */
const char *longname;
@ -647,62 +647,85 @@ static int fat_readdir(struct file *filp, void *dirent, filldir_t filldir)
return __fat_readdir(inode, filp, dirent, filldir, 0, 0);
}
static int fat_ioctl_filldir(void *__buf, const char *name, int name_len,
loff_t offset, u64 ino, unsigned int d_type)
{
struct fat_ioctl_filldir_callback *buf = __buf;
struct dirent __user *d1 = buf->dirent;
struct dirent __user *d2 = d1 + 1;
if (buf->result)
return -EINVAL;
buf->result++;
if (name != NULL) {
/* dirent has only short name */
if (name_len >= sizeof(d1->d_name))
name_len = sizeof(d1->d_name) - 1;
if (put_user(0, d2->d_name) ||
put_user(0, &d2->d_reclen) ||
copy_to_user(d1->d_name, name, name_len) ||
put_user(0, d1->d_name + name_len) ||
put_user(name_len, &d1->d_reclen))
goto efault;
} else {
/* dirent has short and long name */
const char *longname = buf->longname;
int long_len = buf->long_len;
const char *shortname = buf->shortname;
int short_len = buf->short_len;
if (long_len >= sizeof(d1->d_name))
long_len = sizeof(d1->d_name) - 1;
if (short_len >= sizeof(d1->d_name))
short_len = sizeof(d1->d_name) - 1;
if (copy_to_user(d2->d_name, longname, long_len) ||
put_user(0, d2->d_name + long_len) ||
put_user(long_len, &d2->d_reclen) ||
put_user(ino, &d2->d_ino) ||
put_user(offset, &d2->d_off) ||
copy_to_user(d1->d_name, shortname, short_len) ||
put_user(0, d1->d_name + short_len) ||
put_user(short_len, &d1->d_reclen))
goto efault;
}
return 0;
efault:
buf->result = -EFAULT;
return -EFAULT;
#define FAT_IOCTL_FILLDIR_FUNC(func, dirent_type) \
static int func(void *__buf, const char *name, int name_len, \
loff_t offset, u64 ino, unsigned int d_type) \
{ \
struct fat_ioctl_filldir_callback *buf = __buf; \
struct dirent_type __user *d1 = buf->dirent; \
struct dirent_type __user *d2 = d1 + 1; \
\
if (buf->result) \
return -EINVAL; \
buf->result++; \
\
if (name != NULL) { \
/* dirent has only short name */ \
if (name_len >= sizeof(d1->d_name)) \
name_len = sizeof(d1->d_name) - 1; \
\
if (put_user(0, d2->d_name) || \
put_user(0, &d2->d_reclen) || \
copy_to_user(d1->d_name, name, name_len) || \
put_user(0, d1->d_name + name_len) || \
put_user(name_len, &d1->d_reclen)) \
goto efault; \
} else { \
/* dirent has short and long name */ \
const char *longname = buf->longname; \
int long_len = buf->long_len; \
const char *shortname = buf->shortname; \
int short_len = buf->short_len; \
\
if (long_len >= sizeof(d1->d_name)) \
long_len = sizeof(d1->d_name) - 1; \
if (short_len >= sizeof(d1->d_name)) \
short_len = sizeof(d1->d_name) - 1; \
\
if (copy_to_user(d2->d_name, longname, long_len) || \
put_user(0, d2->d_name + long_len) || \
put_user(long_len, &d2->d_reclen) || \
put_user(ino, &d2->d_ino) || \
put_user(offset, &d2->d_off) || \
copy_to_user(d1->d_name, shortname, short_len) || \
put_user(0, d1->d_name + short_len) || \
put_user(short_len, &d1->d_reclen)) \
goto efault; \
} \
return 0; \
efault: \
buf->result = -EFAULT; \
return -EFAULT; \
}
static int fat_dir_ioctl(struct inode * inode, struct file * filp,
unsigned int cmd, unsigned long arg)
FAT_IOCTL_FILLDIR_FUNC(fat_ioctl_filldir, dirent)
static int fat_ioctl_readdir(struct inode *inode, struct file *filp,
void __user *dirent, filldir_t filldir,
int short_only, int both)
{
struct fat_ioctl_filldir_callback buf;
struct dirent __user *d1;
int ret, short_only, both;
int ret;
buf.dirent = dirent;
buf.result = 0;
mutex_lock(&inode->i_mutex);
ret = -ENOENT;
if (!IS_DEADDIR(inode)) {
ret = __fat_readdir(inode, filp, &buf, filldir,
short_only, both);
}
mutex_unlock(&inode->i_mutex);
if (ret >= 0)
ret = buf.result;
return ret;
}
static int fat_dir_ioctl(struct inode *inode, struct file *filp,
unsigned int cmd, unsigned long arg)
{
struct dirent __user *d1 = (struct dirent __user *)arg;
int short_only, both;
switch (cmd) {
case VFAT_IOCTL_READDIR_SHORT:
@ -717,7 +740,6 @@ static int fat_dir_ioctl(struct inode * inode, struct file * filp,
return fat_generic_ioctl(inode, filp, cmd, arg);
}
d1 = (struct dirent __user *)arg;
if (!access_ok(VERIFY_WRITE, d1, sizeof(struct dirent[2])))
return -EFAULT;
/*
@ -728,69 +750,48 @@ static int fat_dir_ioctl(struct inode * inode, struct file * filp,
if (put_user(0, &d1->d_reclen))
return -EFAULT;
buf.dirent = d1;
buf.result = 0;
mutex_lock(&inode->i_mutex);
ret = -ENOENT;
if (!IS_DEADDIR(inode)) {
ret = __fat_readdir(inode, filp, &buf, fat_ioctl_filldir,
short_only, both);
}
mutex_unlock(&inode->i_mutex);
if (ret >= 0)
ret = buf.result;
return ret;
return fat_ioctl_readdir(inode, filp, d1, fat_ioctl_filldir,
short_only, both);
}
#ifdef CONFIG_COMPAT
#define VFAT_IOCTL_READDIR_BOTH32 _IOR('r', 1, struct compat_dirent[2])
#define VFAT_IOCTL_READDIR_SHORT32 _IOR('r', 2, struct compat_dirent[2])
static long fat_compat_put_dirent32(struct dirent *d,
struct compat_dirent __user *d32)
{
if (!access_ok(VERIFY_WRITE, d32, sizeof(struct compat_dirent)))
return -EFAULT;
FAT_IOCTL_FILLDIR_FUNC(fat_compat_ioctl_filldir, compat_dirent)
__put_user(d->d_ino, &d32->d_ino);
__put_user(d->d_off, &d32->d_off);
__put_user(d->d_reclen, &d32->d_reclen);
if (__copy_to_user(d32->d_name, d->d_name, d->d_reclen))
return -EFAULT;
return 0;
}
static long fat_compat_dir_ioctl(struct file *file, unsigned cmd,
static long fat_compat_dir_ioctl(struct file *filp, unsigned cmd,
unsigned long arg)
{
struct compat_dirent __user *p = compat_ptr(arg);
int ret;
mm_segment_t oldfs = get_fs();
struct dirent d[2];
struct inode *inode = filp->f_path.dentry->d_inode;
struct compat_dirent __user *d1 = compat_ptr(arg);
int short_only, both;
switch (cmd) {
case VFAT_IOCTL_READDIR_BOTH32:
cmd = VFAT_IOCTL_READDIR_BOTH;
break;
case VFAT_IOCTL_READDIR_SHORT32:
cmd = VFAT_IOCTL_READDIR_SHORT;
short_only = 1;
both = 0;
break;
case VFAT_IOCTL_READDIR_BOTH32:
short_only = 0;
both = 1;
break;
default:
return -ENOIOCTLCMD;
}
set_fs(KERNEL_DS);
lock_kernel();
ret = fat_dir_ioctl(file->f_path.dentry->d_inode, file,
cmd, (unsigned long) &d);
unlock_kernel();
set_fs(oldfs);
if (ret >= 0) {
ret |= fat_compat_put_dirent32(&d[0], p);
ret |= fat_compat_put_dirent32(&d[1], p + 1);
}
return ret;
if (!access_ok(VERIFY_WRITE, d1, sizeof(struct compat_dirent[2])))
return -EFAULT;
/*
* Yes, we don't need this put_user() absolutely. However old
* code didn't return the right value. So, app use this value,
* in order to check whether it is EOF.
*/
if (put_user(0, &d1->d_reclen))
return -EFAULT;
return fat_ioctl_readdir(inode, filp, d1, fat_compat_ioctl_filldir,
short_only, both);
}
#endif /* CONFIG_COMPAT */