aboutsummaryrefslogtreecommitdiffstats
path: root/fs/fat
diff options
context:
space:
mode:
authorOGAWA Hirofumi <hirofumi@mail.parknet.co.jp>2007-05-08 03:31:28 -0400
committerLinus Torvalds <torvalds@woody.linux-foundation.org>2007-05-08 14:15:14 -0400
commitc483bab099cb89e92b7cad94a52fcdaf37e56657 (patch)
tree3cfbd483b70def6032d51029f4cd0edf49458c38 /fs/fat
parentb247e8aaf2837715d31eb25828fa8b4eb0a659cd (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/fat')
-rw-r--r--fs/fat/dir.c199
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:
422EXPORT_SYMBOL_GPL(fat_search_long); 422EXPORT_SYMBOL_GPL(fat_search_long);
423 423
424struct fat_ioctl_filldir_callback { 424struct 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
650static 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) 651static 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; \
696efault: \
697 buf->result = -EFAULT; \
698 return -EFAULT; \
699}
700
701FAT_IOCTL_FILLDIR_FUNC(fat_ioctl_filldir, dirent)
702
703static 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);
695efault: 719 if (ret >= 0)
696 buf->result = -EFAULT; 720 ret = buf.result;
697 return -EFAULT; 721 return ret;
698} 722}
699 723
700static int fat_dir_ioctl(struct inode * inode, struct file * filp, 724static 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
749static long fat_compat_put_dirent32(struct dirent *d, 761FAT_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; 763static long fat_compat_dir_ioctl(struct file *filp, unsigned cmd,
762}
763
764static 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