diff options
author | Arnd Bergmann <arnd@arndb.de> | 2017-11-27 12:29:50 +0100 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2017-11-28 16:54:00 +0100 |
commit | 9a450484089dfa8b6348eff2a918f3c8f38585b9 (patch) | |
tree | 91cecd53a41c70f00531226cccf1303a168ca6d1 /drivers/char | |
parent | 02543a4e96760a347fb9733bc8a0506a19270ec2 (diff) | |
download | linux-9a450484089dfa8b6348eff2a918f3c8f38585b9.tar.bz2 |
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>
Diffstat (limited to 'drivers/char')
-rw-r--r-- | drivers/char/lp.c | 67 |
1 files changed, 51 insertions, 16 deletions
diff --git a/drivers/char/lp.c b/drivers/char/lp.c index 8249762192d5..be14abf70da1 100644 --- a/drivers/char/lp.c +++ b/drivers/char/lp.c @@ -659,17 +659,31 @@ static int lp_do_ioctl(unsigned int minor, unsigned int cmd, return retval; } -static int lp_set_timeout(unsigned int minor, struct timeval *par_timeout) +static int lp_set_timeout(unsigned int minor, s64 tv_sec, long tv_usec) { long to_jiffies; /* Convert to jiffies, place in lp_table */ - if ((par_timeout->tv_sec < 0) || - (par_timeout->tv_usec < 0)) { + if (tv_sec < 0 || tv_usec < 0) return -EINVAL; + + /* + * we used to not check, so let's not make this fatal, + * but deal with user space passing a 32-bit tv_nsec in + * a 64-bit field, capping the timeout to 1 second + * worth of microseconds, and capping the total at + * MAX_JIFFY_OFFSET. + */ + if (tv_usec > 999999) + tv_usec = 999999; + + if (tv_sec >= MAX_SEC_IN_JIFFIES - 1) { + to_jiffies = MAX_JIFFY_OFFSET; + } else { + to_jiffies = DIV_ROUND_UP(tv_usec, 1000000/HZ); + to_jiffies += tv_sec * (long) HZ; } - to_jiffies = DIV_ROUND_UP(par_timeout->tv_usec, 1000000/HZ); - to_jiffies += par_timeout->tv_sec * (long) HZ; + if (to_jiffies <= 0) { return -EINVAL; } @@ -677,23 +691,43 @@ static int lp_set_timeout(unsigned int minor, struct timeval *par_timeout) return 0; } +static int lp_set_timeout32(unsigned int minor, void __user *arg) +{ + s32 karg[2]; + + if (copy_from_user(karg, arg, sizeof(karg))) + return -EFAULT; + + return lp_set_timeout(minor, karg[0], karg[1]); +} + +static int lp_set_timeout64(unsigned int minor, void __user *arg) +{ + s64 karg[2]; + + if (copy_from_user(karg, arg, sizeof(karg))) + return -EFAULT; + + return lp_set_timeout(minor, karg[0], karg[1]); +} + static long lp_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { unsigned int minor; - struct timeval par_timeout; int ret; minor = iminor(file_inode(file)); mutex_lock(&lp_mutex); switch (cmd) { - case LPSETTIMEOUT: - if (copy_from_user(&par_timeout, (void __user *)arg, - sizeof (struct timeval))) { - ret = -EFAULT; + case LPSETTIMEOUT_OLD: + if (BITS_PER_LONG == 32) { + ret = lp_set_timeout32(minor, (void __user *)arg); break; } - ret = lp_set_timeout(minor, &par_timeout); + /* fallthrough for 64-bit */ + case LPSETTIMEOUT_NEW: + ret = lp_set_timeout64(minor, (void __user *)arg); break; default: ret = lp_do_ioctl(minor, cmd, arg, (void __user *)arg); @@ -709,18 +743,19 @@ static long lp_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { unsigned int minor; - struct timeval par_timeout; int ret; minor = iminor(file_inode(file)); mutex_lock(&lp_mutex); switch (cmd) { - case LPSETTIMEOUT: - if (compat_get_timeval(&par_timeout, compat_ptr(arg))) { - ret = -EFAULT; + case LPSETTIMEOUT_OLD: + if (!COMPAT_USE_64BIT_TIME) { + ret = lp_set_timeout32(minor, (void __user *)arg); break; } - ret = lp_set_timeout(minor, &par_timeout); + /* fallthrough for x32 mode */ + case LPSETTIMEOUT_NEW: + ret = lp_set_timeout64(minor, (void __user *)arg); break; #ifdef LP_STATS case LPGETSTATS: |