diff options
author | OGAWA Hirofumi <hirofumi@mail.parknet.co.jp> | 2007-05-08 03:31:28 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@woody.linux-foundation.org> | 2007-05-08 14:15:14 -0400 |
commit | c483bab099cb89e92b7cad94a52fcdaf37e56657 (patch) | |
tree | 3cfbd483b70def6032d51029f4cd0edf49458c38 /fs | |
parent | b247e8aaf2837715d31eb25828fa8b4eb0a659cd (diff) |
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>
Diffstat (limited to 'fs')
-rw-r--r-- | fs/fat/dir.c | 199 |
1 files changed, 100 insertions, 99 deletions
diff --git a/fs/fat/dir.c b/fs/fat/dir.c index c16af246d245..ccf161dffb63 100644 --- a/fs/fat/dir.c +++ b/fs/fat/dir.c | |||
@@ -422,7 +422,7 @@ EODir: | |||
422 | EXPORT_SYMBOL_GPL(fat_search_long); | 422 | EXPORT_SYMBOL_GPL(fat_search_long); |
423 | 423 | ||
424 | struct fat_ioctl_filldir_callback { | 424 | struct fat_ioctl_filldir_callback { |
425 | struct dirent __user *dirent; | 425 | void __user *dirent; |
426 | int result; | 426 | int result; |
427 | /* for dir ioctl */ | 427 | /* for dir ioctl */ |
428 | const char *longname; | 428 | const char *longname; |
@@ -647,62 +647,85 @@ static int fat_readdir(struct file *filp, void *dirent, filldir_t filldir) | |||
647 | return __fat_readdir(inode, filp, dirent, filldir, 0, 0); | 647 | return __fat_readdir(inode, filp, dirent, filldir, 0, 0); |
648 | } | 648 | } |
649 | 649 | ||
650 | static int fat_ioctl_filldir(void *__buf, const char *name, int name_len, | 650 | #define FAT_IOCTL_FILLDIR_FUNC(func, dirent_type) \ |
651 | loff_t offset, u64 ino, unsigned int d_type) | 651 | static int func(void *__buf, const char *name, int name_len, \ |
652 | loff_t offset, u64 ino, unsigned int d_type) \ | ||
653 | { \ | ||
654 | struct fat_ioctl_filldir_callback *buf = __buf; \ | ||
655 | struct dirent_type __user *d1 = buf->dirent; \ | ||
656 | struct dirent_type __user *d2 = d1 + 1; \ | ||
657 | \ | ||
658 | if (buf->result) \ | ||
659 | return -EINVAL; \ | ||
660 | buf->result++; \ | ||
661 | \ | ||
662 | if (name != NULL) { \ | ||
663 | /* dirent has only short name */ \ | ||
664 | if (name_len >= sizeof(d1->d_name)) \ | ||
665 | name_len = sizeof(d1->d_name) - 1; \ | ||
666 | \ | ||
667 | if (put_user(0, d2->d_name) || \ | ||
668 | put_user(0, &d2->d_reclen) || \ | ||
669 | copy_to_user(d1->d_name, name, name_len) || \ | ||
670 | put_user(0, d1->d_name + name_len) || \ | ||
671 | put_user(name_len, &d1->d_reclen)) \ | ||
672 | goto efault; \ | ||
673 | } else { \ | ||
674 | /* dirent has short and long name */ \ | ||
675 | const char *longname = buf->longname; \ | ||
676 | int long_len = buf->long_len; \ | ||
677 | const char *shortname = buf->shortname; \ | ||
678 | int short_len = buf->short_len; \ | ||
679 | \ | ||
680 | if (long_len >= sizeof(d1->d_name)) \ | ||
681 | long_len = sizeof(d1->d_name) - 1; \ | ||
682 | if (short_len >= sizeof(d1->d_name)) \ | ||
683 | short_len = sizeof(d1->d_name) - 1; \ | ||
684 | \ | ||
685 | if (copy_to_user(d2->d_name, longname, long_len) || \ | ||
686 | put_user(0, d2->d_name + long_len) || \ | ||
687 | put_user(long_len, &d2->d_reclen) || \ | ||
688 | put_user(ino, &d2->d_ino) || \ | ||
689 | put_user(offset, &d2->d_off) || \ | ||
690 | copy_to_user(d1->d_name, shortname, short_len) || \ | ||
691 | put_user(0, d1->d_name + short_len) || \ | ||
692 | put_user(short_len, &d1->d_reclen)) \ | ||
693 | goto efault; \ | ||
694 | } \ | ||
695 | return 0; \ | ||
696 | efault: \ | ||
697 | buf->result = -EFAULT; \ | ||
698 | return -EFAULT; \ | ||
699 | } | ||
700 | |||
701 | FAT_IOCTL_FILLDIR_FUNC(fat_ioctl_filldir, dirent) | ||
702 | |||
703 | static int fat_ioctl_readdir(struct inode *inode, struct file *filp, | ||
704 | void __user *dirent, filldir_t filldir, | ||
705 | int short_only, int both) | ||
652 | { | 706 | { |
653 | struct fat_ioctl_filldir_callback *buf = __buf; | 707 | struct fat_ioctl_filldir_callback buf; |
654 | struct dirent __user *d1 = buf->dirent; | 708 | int ret; |
655 | struct dirent __user *d2 = d1 + 1; | 709 | |
656 | 710 | buf.dirent = dirent; | |
657 | if (buf->result) | 711 | buf.result = 0; |
658 | return -EINVAL; | 712 | mutex_lock(&inode->i_mutex); |
659 | buf->result++; | 713 | ret = -ENOENT; |
660 | 714 | if (!IS_DEADDIR(inode)) { | |
661 | if (name != NULL) { | 715 | ret = __fat_readdir(inode, filp, &buf, filldir, |
662 | /* dirent has only short name */ | 716 | short_only, both); |
663 | if (name_len >= sizeof(d1->d_name)) | ||
664 | name_len = sizeof(d1->d_name) - 1; | ||
665 | |||
666 | if (put_user(0, d2->d_name) || | ||
667 | put_user(0, &d2->d_reclen) || | ||
668 | copy_to_user(d1->d_name, name, name_len) || | ||
669 | put_user(0, d1->d_name + name_len) || | ||
670 | put_user(name_len, &d1->d_reclen)) | ||
671 | goto efault; | ||
672 | } else { | ||
673 | /* dirent has short and long name */ | ||
674 | const char *longname = buf->longname; | ||
675 | int long_len = buf->long_len; | ||
676 | const char *shortname = buf->shortname; | ||
677 | int short_len = buf->short_len; | ||
678 | |||
679 | if (long_len >= sizeof(d1->d_name)) | ||
680 | long_len = sizeof(d1->d_name) - 1; | ||
681 | if (short_len >= sizeof(d1->d_name)) | ||
682 | short_len = sizeof(d1->d_name) - 1; | ||
683 | |||
684 | if (copy_to_user(d2->d_name, longname, long_len) || | ||
685 | put_user(0, d2->d_name + long_len) || | ||
686 | put_user(long_len, &d2->d_reclen) || | ||
687 | put_user(ino, &d2->d_ino) || | ||
688 | put_user(offset, &d2->d_off) || | ||
689 | copy_to_user(d1->d_name, shortname, short_len) || | ||
690 | put_user(0, d1->d_name + short_len) || | ||
691 | put_user(short_len, &d1->d_reclen)) | ||
692 | goto efault; | ||
693 | } | 717 | } |
694 | return 0; | 718 | mutex_unlock(&inode->i_mutex); |
695 | efault: | 719 | if (ret >= 0) |
696 | buf->result = -EFAULT; | 720 | ret = buf.result; |
697 | return -EFAULT; | 721 | return ret; |
698 | } | 722 | } |
699 | 723 | ||
700 | static int fat_dir_ioctl(struct inode * inode, struct file * filp, | 724 | static int fat_dir_ioctl(struct inode *inode, struct file *filp, |
701 | unsigned int cmd, unsigned long arg) | 725 | unsigned int cmd, unsigned long arg) |
702 | { | 726 | { |
703 | struct fat_ioctl_filldir_callback buf; | 727 | struct dirent __user *d1 = (struct dirent __user *)arg; |
704 | struct dirent __user *d1; | 728 | int short_only, both; |
705 | int ret, short_only, both; | ||
706 | 729 | ||
707 | switch (cmd) { | 730 | switch (cmd) { |
708 | case VFAT_IOCTL_READDIR_SHORT: | 731 | case VFAT_IOCTL_READDIR_SHORT: |
@@ -717,7 +740,6 @@ static int fat_dir_ioctl(struct inode * inode, struct file * filp, | |||
717 | return fat_generic_ioctl(inode, filp, cmd, arg); | 740 | return fat_generic_ioctl(inode, filp, cmd, arg); |
718 | } | 741 | } |
719 | 742 | ||
720 | d1 = (struct dirent __user *)arg; | ||
721 | if (!access_ok(VERIFY_WRITE, d1, sizeof(struct dirent[2]))) | 743 | if (!access_ok(VERIFY_WRITE, d1, sizeof(struct dirent[2]))) |
722 | return -EFAULT; | 744 | return -EFAULT; |
723 | /* | 745 | /* |
@@ -728,69 +750,48 @@ static int fat_dir_ioctl(struct inode * inode, struct file * filp, | |||
728 | if (put_user(0, &d1->d_reclen)) | 750 | if (put_user(0, &d1->d_reclen)) |
729 | return -EFAULT; | 751 | return -EFAULT; |
730 | 752 | ||
731 | buf.dirent = d1; | 753 | return fat_ioctl_readdir(inode, filp, d1, fat_ioctl_filldir, |
732 | buf.result = 0; | 754 | short_only, both); |
733 | mutex_lock(&inode->i_mutex); | ||
734 | ret = -ENOENT; | ||
735 | if (!IS_DEADDIR(inode)) { | ||
736 | ret = __fat_readdir(inode, filp, &buf, fat_ioctl_filldir, | ||
737 | short_only, both); | ||
738 | } | ||
739 | mutex_unlock(&inode->i_mutex); | ||
740 | if (ret >= 0) | ||
741 | ret = buf.result; | ||
742 | return ret; | ||
743 | } | 755 | } |
744 | 756 | ||
745 | #ifdef CONFIG_COMPAT | 757 | #ifdef CONFIG_COMPAT |
746 | #define VFAT_IOCTL_READDIR_BOTH32 _IOR('r', 1, struct compat_dirent[2]) | 758 | #define VFAT_IOCTL_READDIR_BOTH32 _IOR('r', 1, struct compat_dirent[2]) |
747 | #define VFAT_IOCTL_READDIR_SHORT32 _IOR('r', 2, struct compat_dirent[2]) | 759 | #define VFAT_IOCTL_READDIR_SHORT32 _IOR('r', 2, struct compat_dirent[2]) |
748 | 760 | ||
749 | static long fat_compat_put_dirent32(struct dirent *d, | 761 | FAT_IOCTL_FILLDIR_FUNC(fat_compat_ioctl_filldir, compat_dirent) |
750 | struct compat_dirent __user *d32) | ||
751 | { | ||
752 | if (!access_ok(VERIFY_WRITE, d32, sizeof(struct compat_dirent))) | ||
753 | return -EFAULT; | ||
754 | |||
755 | __put_user(d->d_ino, &d32->d_ino); | ||
756 | __put_user(d->d_off, &d32->d_off); | ||
757 | __put_user(d->d_reclen, &d32->d_reclen); | ||
758 | if (__copy_to_user(d32->d_name, d->d_name, d->d_reclen)) | ||
759 | return -EFAULT; | ||
760 | 762 | ||
761 | return 0; | 763 | static long fat_compat_dir_ioctl(struct file *filp, unsigned cmd, |
762 | } | ||
763 | |||
764 | static long fat_compat_dir_ioctl(struct file *file, unsigned cmd, | ||
765 | unsigned long arg) | 764 | unsigned long arg) |
766 | { | 765 | { |
767 | struct compat_dirent __user *p = compat_ptr(arg); | 766 | struct inode *inode = filp->f_path.dentry->d_inode; |
768 | int ret; | 767 | struct compat_dirent __user *d1 = compat_ptr(arg); |
769 | mm_segment_t oldfs = get_fs(); | 768 | int short_only, both; |
770 | struct dirent d[2]; | ||
771 | 769 | ||
772 | switch (cmd) { | 770 | switch (cmd) { |
773 | case VFAT_IOCTL_READDIR_BOTH32: | ||
774 | cmd = VFAT_IOCTL_READDIR_BOTH; | ||
775 | break; | ||
776 | case VFAT_IOCTL_READDIR_SHORT32: | 771 | case VFAT_IOCTL_READDIR_SHORT32: |
777 | cmd = VFAT_IOCTL_READDIR_SHORT; | 772 | short_only = 1; |
773 | both = 0; | ||
774 | break; | ||
775 | case VFAT_IOCTL_READDIR_BOTH32: | ||
776 | short_only = 0; | ||
777 | both = 1; | ||
778 | break; | 778 | break; |
779 | default: | 779 | default: |
780 | return -ENOIOCTLCMD; | 780 | return -ENOIOCTLCMD; |
781 | } | 781 | } |
782 | 782 | ||
783 | set_fs(KERNEL_DS); | 783 | if (!access_ok(VERIFY_WRITE, d1, sizeof(struct compat_dirent[2]))) |
784 | lock_kernel(); | 784 | return -EFAULT; |
785 | ret = fat_dir_ioctl(file->f_path.dentry->d_inode, file, | 785 | /* |
786 | cmd, (unsigned long) &d); | 786 | * Yes, we don't need this put_user() absolutely. However old |
787 | unlock_kernel(); | 787 | * code didn't return the right value. So, app use this value, |
788 | set_fs(oldfs); | 788 | * in order to check whether it is EOF. |
789 | if (ret >= 0) { | 789 | */ |
790 | ret |= fat_compat_put_dirent32(&d[0], p); | 790 | if (put_user(0, &d1->d_reclen)) |
791 | ret |= fat_compat_put_dirent32(&d[1], p + 1); | 791 | return -EFAULT; |
792 | } | 792 | |
793 | return ret; | 793 | return fat_ioctl_readdir(inode, filp, d1, fat_compat_ioctl_filldir, |
794 | short_only, both); | ||
794 | } | 795 | } |
795 | #endif /* CONFIG_COMPAT */ | 796 | #endif /* CONFIG_COMPAT */ |
796 | 797 | ||