Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

lp: support 64-bit time_t user space

Once we get a glibc with 64-bit time_t, the LPSETTIMEOUT ioctl stops
working, since the command number and data structure no longer match.

To work around that, this introduces a new command number LPSETTIMEOUT_NEW
that is used whenever the modified user space evaluates the LPSETTIMEOUT
macro.

The trick we use is a bit convoluted but necessary: we cannot check for
any macros set by the C library in linux/lp.h, because this particular
header can be included before including sys/time.h. However, we can assume
that by the time that LPSETTIMEOUT is seen in the code, the definition
for 'timeval' and 'time_t' has been seen as well, so we can use the
sizeof() operator to determine whether we should use the old or the
new definition. We use the old one not only for traditional 32-bit user
space with 32-bit time_t, but also for all 64-bit architectures and x32,
which always use a 64-bit time_t, the new definition will be used only for
32-bit user space with 64-bit time_t, which also requires a newer kernel.

The compat_ioctl() handler now implements both commands, but has to
use a special case for existing x32 binaries. The native ioctl handler
now implements both command numbers on both 32-bit and 64-bit, though
the latter version use the same interpretation for both.

This is based on an earlier patch from Bamvor.

Cc: Bamvor Jian Zhang <bamv2005@gmail.com>
Link: http://www.spinics.net/lists/y2038/msg01162.html
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

authored by

Arnd Bergmann and committed by
Greg Kroah-Hartman
9a450484 02543a4e

+62 -17
+51 -16
drivers/char/lp.c
··· 659 659 return retval; 660 660 } 661 661 662 - static int lp_set_timeout(unsigned int minor, struct timeval *par_timeout) 662 + static int lp_set_timeout(unsigned int minor, s64 tv_sec, long tv_usec) 663 663 { 664 664 long to_jiffies; 665 665 666 666 /* Convert to jiffies, place in lp_table */ 667 - if ((par_timeout->tv_sec < 0) || 668 - (par_timeout->tv_usec < 0)) { 667 + if (tv_sec < 0 || tv_usec < 0) 669 668 return -EINVAL; 669 + 670 + /* 671 + * we used to not check, so let's not make this fatal, 672 + * but deal with user space passing a 32-bit tv_nsec in 673 + * a 64-bit field, capping the timeout to 1 second 674 + * worth of microseconds, and capping the total at 675 + * MAX_JIFFY_OFFSET. 676 + */ 677 + if (tv_usec > 999999) 678 + tv_usec = 999999; 679 + 680 + if (tv_sec >= MAX_SEC_IN_JIFFIES - 1) { 681 + to_jiffies = MAX_JIFFY_OFFSET; 682 + } else { 683 + to_jiffies = DIV_ROUND_UP(tv_usec, 1000000/HZ); 684 + to_jiffies += tv_sec * (long) HZ; 670 685 } 671 - to_jiffies = DIV_ROUND_UP(par_timeout->tv_usec, 1000000/HZ); 672 - to_jiffies += par_timeout->tv_sec * (long) HZ; 686 + 673 687 if (to_jiffies <= 0) { 674 688 return -EINVAL; 675 689 } ··· 691 677 return 0; 692 678 } 693 679 680 + static int lp_set_timeout32(unsigned int minor, void __user *arg) 681 + { 682 + s32 karg[2]; 683 + 684 + if (copy_from_user(karg, arg, sizeof(karg))) 685 + return -EFAULT; 686 + 687 + return lp_set_timeout(minor, karg[0], karg[1]); 688 + } 689 + 690 + static int lp_set_timeout64(unsigned int minor, void __user *arg) 691 + { 692 + s64 karg[2]; 693 + 694 + if (copy_from_user(karg, arg, sizeof(karg))) 695 + return -EFAULT; 696 + 697 + return lp_set_timeout(minor, karg[0], karg[1]); 698 + } 699 + 694 700 static long lp_ioctl(struct file *file, unsigned int cmd, 695 701 unsigned long arg) 696 702 { 697 703 unsigned int minor; 698 - struct timeval par_timeout; 699 704 int ret; 700 705 701 706 minor = iminor(file_inode(file)); 702 707 mutex_lock(&lp_mutex); 703 708 switch (cmd) { 704 - case LPSETTIMEOUT: 705 - if (copy_from_user(&par_timeout, (void __user *)arg, 706 - sizeof (struct timeval))) { 707 - ret = -EFAULT; 709 + case LPSETTIMEOUT_OLD: 710 + if (BITS_PER_LONG == 32) { 711 + ret = lp_set_timeout32(minor, (void __user *)arg); 708 712 break; 709 713 } 710 - ret = lp_set_timeout(minor, &par_timeout); 714 + /* fallthrough for 64-bit */ 715 + case LPSETTIMEOUT_NEW: 716 + ret = lp_set_timeout64(minor, (void __user *)arg); 711 717 break; 712 718 default: 713 719 ret = lp_do_ioctl(minor, cmd, arg, (void __user *)arg); ··· 743 709 unsigned long arg) 744 710 { 745 711 unsigned int minor; 746 - struct timeval par_timeout; 747 712 int ret; 748 713 749 714 minor = iminor(file_inode(file)); 750 715 mutex_lock(&lp_mutex); 751 716 switch (cmd) { 752 - case LPSETTIMEOUT: 753 - if (compat_get_timeval(&par_timeout, compat_ptr(arg))) { 754 - ret = -EFAULT; 717 + case LPSETTIMEOUT_OLD: 718 + if (!COMPAT_USE_64BIT_TIME) { 719 + ret = lp_set_timeout32(minor, (void __user *)arg); 755 720 break; 756 721 } 757 - ret = lp_set_timeout(minor, &par_timeout); 722 + /* fallthrough for x32 mode */ 723 + case LPSETTIMEOUT_NEW: 724 + ret = lp_set_timeout64(minor, (void __user *)arg); 758 725 break; 759 726 #ifdef LP_STATS 760 727 case LPGETSTATS:
+11 -1
include/uapi/linux/lp.h
··· 8 8 #ifndef _UAPI_LINUX_LP_H 9 9 #define _UAPI_LINUX_LP_H 10 10 11 + #include <linux/types.h> 12 + #include <linux/ioctl.h> 11 13 12 14 /* 13 15 * Per POSIX guidelines, this module reserves the LP and lp prefixes ··· 90 88 #define LPGETSTATS 0x060d /* get statistics (struct lp_stats) */ 91 89 #endif 92 90 #define LPGETFLAGS 0x060e /* get status flags */ 93 - #define LPSETTIMEOUT 0x060f /* set parport timeout */ 91 + #define LPSETTIMEOUT_OLD 0x060f /* set parport timeout */ 92 + #define LPSETTIMEOUT_NEW \ 93 + _IOW(0x6, 0xf, __s64[2]) /* set parport timeout */ 94 + #if __BITS_PER_LONG == 64 95 + #define LPSETTIMEOUT LPSETTIMEOUT_OLD 96 + #else 97 + #define LPSETTIMEOUT (sizeof(time_t) > sizeof(__kernel_long_t) ? \ 98 + LPSETTIMEOUT_NEW : LPSETTIMEOUT_OLD) 99 + #endif 94 100 95 101 /* timeout for printk'ing a timeout, in jiffies (100ths of a second). 96 102 This is also used for re-checking error conditions if LP_ABORT is