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:
parent
b247e8aaf2
commit
c483bab099
1 changed files with 101 additions and 100 deletions
201
fs/fat/dir.c
201
fs/fat/dir.c
|
@ -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 */
|
||||
|
||||
|
|
Loading…
Reference in a new issue