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