[PATCH] select: fix returned timeval

With David Woodhouse <dwmw2@infradead.org>

select() presently has a habit of increasing the value of the user's
`timeout' argument on return.

We were writing back a timeout larger than the original.  We _deliberately_
round up, since we know we must wait at _least_ as long as the caller asks
us to.

The patch adds a couple of helper functions for magnitude comparison of
timespecs and of timevals, and uses them to prevent the various poll and
select functions from returning a timeout which is larger than the one which
was passed in.

The patch also fixes a bug in compat_sys_pselect7(): it was adding the new
timeout value to the old one and was returning that.  It should just return
the new timeout value.

(We have various handy timespec/timeval-to-from-nsec conversion functions in
time.h.  But this code open-codes it all).

Cc: "David S. Miller" <davem@davemloft.net>
Cc: Andi Kleen <ak@muc.de>
Cc: Ulrich Drepper <drepper@redhat.com>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: george anzinger <george@mvista.com>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
This commit is contained in:
Andrew Morton 2006-02-11 17:55:52 -08:00 committed by Linus Torvalds
parent 33042a9ff4
commit 643a654540
4 changed files with 92 additions and 22 deletions

View file

@ -1751,11 +1751,15 @@ asmlinkage long compat_sys_select(int n, compat_ulong_t __user *inp,
ret = compat_core_sys_select(n, inp, outp, exp, &timeout); ret = compat_core_sys_select(n, inp, outp, exp, &timeout);
if (tvp) { if (tvp) {
struct compat_timeval rtv;
if (current->personality & STICKY_TIMEOUTS) if (current->personality & STICKY_TIMEOUTS)
goto sticky; goto sticky;
tv.tv_usec = jiffies_to_usecs(do_div((*(u64*)&timeout), HZ)); rtv.tv_usec = jiffies_to_usecs(do_div((*(u64*)&timeout), HZ));
tv.tv_sec = timeout; rtv.tv_sec = timeout;
if (copy_to_user(tvp, &tv, sizeof(tv))) { if (compat_timeval_compare(&rtv, &tv) < 0)
rtv = tv;
if (copy_to_user(tvp, &rtv, sizeof(rtv))) {
sticky: sticky:
/* /*
* If an application puts its timeval in read-only * If an application puts its timeval in read-only
@ -1822,13 +1826,17 @@ asmlinkage long compat_sys_pselect7(int n, compat_ulong_t __user *inp,
} while (!ret && !timeout && tsp && (ts.tv_sec || ts.tv_nsec)); } while (!ret && !timeout && tsp && (ts.tv_sec || ts.tv_nsec));
if (tsp && !(current->personality & STICKY_TIMEOUTS)) { if (tsp && !(current->personality & STICKY_TIMEOUTS)) {
ts.tv_sec += timeout / HZ; struct compat_timespec rts;
ts.tv_nsec += (timeout % HZ) * (1000000000/HZ);
if (ts.tv_nsec >= 1000000000) { rts.tv_sec = timeout / HZ;
ts.tv_sec++; rts.tv_nsec = (timeout % HZ) * (NSEC_PER_SEC/HZ);
ts.tv_nsec -= 1000000000; if (rts.tv_nsec >= NSEC_PER_SEC) {
rts.tv_sec++;
rts.tv_nsec -= NSEC_PER_SEC;
} }
(void)copy_to_user(tsp, &ts, sizeof(ts)); if (compat_timespec_compare(&rts, &ts) < 0)
rts = ts;
copy_to_user(tsp, &rts, sizeof(rts));
} }
if (ret == -ERESTARTNOHAND) { if (ret == -ERESTARTNOHAND) {
@ -1918,12 +1926,17 @@ asmlinkage long compat_sys_ppoll(struct pollfd __user *ufds,
sigprocmask(SIG_SETMASK, &sigsaved, NULL); sigprocmask(SIG_SETMASK, &sigsaved, NULL);
if (tsp && timeout >= 0) { if (tsp && timeout >= 0) {
struct compat_timespec rts;
if (current->personality & STICKY_TIMEOUTS) if (current->personality & STICKY_TIMEOUTS)
goto sticky; goto sticky;
/* Yes, we know it's actually an s64, but it's also positive. */ /* Yes, we know it's actually an s64, but it's also positive. */
ts.tv_nsec = jiffies_to_usecs(do_div((*(u64*)&timeout), HZ)) * 1000; rts.tv_nsec = jiffies_to_usecs(do_div((*(u64*)&timeout), HZ)) *
ts.tv_sec = timeout; 1000;
if (copy_to_user(tsp, &ts, sizeof(ts))) { rts.tv_sec = timeout;
if (compat_timespec_compare(&rts, &ts) < 0)
rts = ts;
if (copy_to_user(tsp, &rts, sizeof(rts))) {
sticky: sticky:
/* /*
* If an application puts its timeval in read-only * If an application puts its timeval in read-only

View file

@ -398,11 +398,15 @@ asmlinkage long sys_select(int n, fd_set __user *inp, fd_set __user *outp,
ret = core_sys_select(n, inp, outp, exp, &timeout); ret = core_sys_select(n, inp, outp, exp, &timeout);
if (tvp) { if (tvp) {
struct timeval rtv;
if (current->personality & STICKY_TIMEOUTS) if (current->personality & STICKY_TIMEOUTS)
goto sticky; goto sticky;
tv.tv_usec = jiffies_to_usecs(do_div((*(u64*)&timeout), HZ)); rtv.tv_usec = jiffies_to_usecs(do_div((*(u64*)&timeout), HZ));
tv.tv_sec = timeout; rtv.tv_sec = timeout;
if (copy_to_user(tvp, &tv, sizeof(tv))) { if (timeval_compare(&rtv, &tv) < 0)
rtv = tv;
if (copy_to_user(tvp, &rtv, sizeof(rtv))) {
sticky: sticky:
/* /*
* If an application puts its timeval in read-only * If an application puts its timeval in read-only
@ -460,11 +464,16 @@ asmlinkage long sys_pselect7(int n, fd_set __user *inp, fd_set __user *outp,
ret = core_sys_select(n, inp, outp, exp, &timeout); ret = core_sys_select(n, inp, outp, exp, &timeout);
if (tsp) { if (tsp) {
struct timespec rts;
if (current->personality & STICKY_TIMEOUTS) if (current->personality & STICKY_TIMEOUTS)
goto sticky; goto sticky;
ts.tv_nsec = jiffies_to_usecs(do_div((*(u64*)&timeout), HZ)) * 1000; rts.tv_nsec = jiffies_to_usecs(do_div((*(u64*)&timeout), HZ)) *
ts.tv_sec = timeout; 1000;
if (copy_to_user(tsp, &ts, sizeof(ts))) { rts.tv_sec = timeout;
if (timespec_compare(&rts, &ts) < 0)
rts = ts;
if (copy_to_user(tsp, &rts, sizeof(rts))) {
sticky: sticky:
/* /*
* If an application puts its timeval in read-only * If an application puts its timeval in read-only
@ -758,12 +767,17 @@ asmlinkage long sys_ppoll(struct pollfd __user *ufds, unsigned int nfds,
sigprocmask(SIG_SETMASK, &sigsaved, NULL); sigprocmask(SIG_SETMASK, &sigsaved, NULL);
if (tsp && timeout >= 0) { if (tsp && timeout >= 0) {
struct timespec rts;
if (current->personality & STICKY_TIMEOUTS) if (current->personality & STICKY_TIMEOUTS)
goto sticky; goto sticky;
/* Yes, we know it's actually an s64, but it's also positive. */ /* Yes, we know it's actually an s64, but it's also positive. */
ts.tv_nsec = jiffies_to_usecs(do_div((*(u64*)&timeout), HZ)) * 1000; rts.tv_nsec = jiffies_to_usecs(do_div((*(u64*)&timeout), HZ)) *
ts.tv_sec = timeout; 1000;
if (copy_to_user(tsp, &ts, sizeof(ts))) { rts.tv_sec = timeout;
if (timespec_compare(&rts, &ts) < 0)
rts = ts;
if (copy_to_user(tsp, &rts, sizeof(rts))) {
sticky: sticky:
/* /*
* If an application puts its timeval in read-only * If an application puts its timeval in read-only

View file

@ -161,5 +161,25 @@ int copy_siginfo_to_user32(struct compat_siginfo __user *to, siginfo_t *from);
int get_compat_sigevent(struct sigevent *event, int get_compat_sigevent(struct sigevent *event,
const struct compat_sigevent __user *u_event); const struct compat_sigevent __user *u_event);
static inline int compat_timeval_compare(struct compat_timeval *lhs,
struct compat_timeval *rhs)
{
if (lhs->tv_sec < rhs->tv_sec)
return -1;
if (lhs->tv_sec > rhs->tv_sec)
return 1;
return lhs->tv_usec - rhs->tv_usec;
}
static inline int compat_timespec_compare(struct compat_timespec *lhs,
struct compat_timespec *rhs)
{
if (lhs->tv_sec < rhs->tv_sec)
return -1;
if (lhs->tv_sec > rhs->tv_sec)
return 1;
return lhs->tv_nsec - rhs->tv_nsec;
}
#endif /* CONFIG_COMPAT */ #endif /* CONFIG_COMPAT */
#endif /* _LINUX_COMPAT_H */ #endif /* _LINUX_COMPAT_H */

View file

@ -33,11 +33,34 @@ struct timezone {
#define NSEC_PER_SEC 1000000000L #define NSEC_PER_SEC 1000000000L
#define NSEC_PER_USEC 1000L #define NSEC_PER_USEC 1000L
static __inline__ int timespec_equal(struct timespec *a, struct timespec *b) static inline int timespec_equal(struct timespec *a, struct timespec *b)
{ {
return (a->tv_sec == b->tv_sec) && (a->tv_nsec == b->tv_nsec); return (a->tv_sec == b->tv_sec) && (a->tv_nsec == b->tv_nsec);
} }
/*
* lhs < rhs: return <0
* lhs == rhs: return 0
* lhs > rhs: return >0
*/
static inline int timespec_compare(struct timespec *lhs, struct timespec *rhs)
{
if (lhs->tv_sec < rhs->tv_sec)
return -1;
if (lhs->tv_sec > rhs->tv_sec)
return 1;
return lhs->tv_nsec - rhs->tv_nsec;
}
static inline int timeval_compare(struct timeval *lhs, struct timeval *rhs)
{
if (lhs->tv_sec < rhs->tv_sec)
return -1;
if (lhs->tv_sec > rhs->tv_sec)
return 1;
return lhs->tv_usec - rhs->tv_usec;
}
extern unsigned long mktime(const unsigned int year, const unsigned int mon, extern unsigned long mktime(const unsigned int year, const unsigned int mon,
const unsigned int day, const unsigned int hour, const unsigned int day, const unsigned int hour,
const unsigned int min, const unsigned int sec); const unsigned int min, const unsigned int sec);