[PATCH] poll(2) timeout values

From: Peter Staubach
Date: Thu Nov 10 2005 - 11:31:18 EST


Hi.

On 32 bit platforms, the poll(2) system call support can not correctly
handle the range of timeout values which are possible to call it with.
This is due to some limitations based on the clock speed, but also due
to concerns about integer arithmetic overflow. The former is due to,
the number of jiffies which correspond to the number of milliseconds in
the timeout value, being larger than allowed. Clock speeds of 1000 HZ
or less do not have this problem, so this is not a very common situation
to find. The latter is due to problems in the ordering of the arithmetic
operations used to convert milliseconds to jiffies.

The problem being reported is that despite the man page descriptions, on
a typical 32 bit system with the clock rate set at 1000 HZ, the poll system
call can not handle timeout values larger than about 2,147 seconds. On
such a system, the limit should be about 2,147,483 seconds.

Another problem was also discovered in the definition of the arguments to
the kernel poll implementation. The poll(2) system takes an int as its
third argument. The kernel implementation was using a long.

Once this problem is corrected, then the first problem can also be
corrected because the length of the timeout value from the user level
is limited to 31 bits. 64 bit arithmetic can be used to correctly do
the conversion between milliseconds and clock jiffies without loss of
precision.

These changes were tested by running a program which issues a poll(2)
call with a timeout value of 2,200 seconds. Without these changes, the
poll call in this program hangs forever. With the changes, the poll
call times out after approximately 2,200 seconds, matching the semantics
as described in the man page.

Concerns have been expressed regarding the change in the arguments to
the sys_poll() routine. This is not an exported symbol, so there should
be compatibility issues with existing callers. The routine implements
the system call interface underneath the glibc poll(2) call, so, if there
are architectures for which this would be a problem, then these changes
are not good enough.

Clearly, the timeout calculations problem can be fixed without changing
the arguments to the sys_poll() routine. However, it is cleaner to fix
it this way by ensuring the sizes and types of arguments match.

Thanx...

ps

Signed-off-by: Peter Staubach <staubach@xxxxxxxxxx> --- linux-2.6.14/fs/select.c.org
+++ linux-2.6.14/fs/select.c
@@ -25,6 +25,7 @@
#include <linux/rcupdate.h>

#include <asm/uaccess.h>
+#include <asm/div64.h>

#define ROUND_UP(x,y) (((x)+(y)-1)/(y))
#define DEFAULT_POLLMASK (POLLIN | POLLOUT | POLLRDNORM | POLLWRNORM)
@@ -464,7 +465,7 @@ static int do_poll(unsigned int nfds, s
return count;
}

-asmlinkage long sys_poll(struct pollfd __user * ufds, unsigned int nfds, long timeout)
+asmlinkage long sys_poll(struct pollfd __user * ufds, unsigned int nfds, int timeout_msecs)
{
struct poll_wqueues table;
int fdcount, err;
@@ -473,6 +474,8 @@ asmlinkage long sys_poll(struct pollfd _
struct poll_list *walk;
struct fdtable *fdt;
int max_fdset;
+ long timeout;
+ int64_t lltimeout;

/* Do a sanity check on nfds ... */
rcu_read_lock();
@@ -482,13 +485,20 @@ asmlinkage long sys_poll(struct pollfd _
if (nfds > max_fdset && nfds > OPEN_MAX)
return -EINVAL;

- if (timeout) {
- /* Careful about overflow in the intermediate values */
- if ((unsigned long) timeout < MAX_SCHEDULE_TIMEOUT / HZ)
- timeout = (unsigned long)(timeout*HZ+999)/1000+1;
- else /* Negative or overflow */
+ if (timeout_msecs) {
+ if (timeout_msecs < 0)
timeout = MAX_SCHEDULE_TIMEOUT;
- }
+ else {
+ lltimeout = (int64_t)timeout_msecs * HZ + 999;
+ do_div(lltimeout, 1000);
+ lltimeout++;
+ if (lltimeout > MAX_SCHEDULE_TIMEOUT)
+ timeout = MAX_SCHEDULE_TIMEOUT;
+ else
+ timeout = (long)lltimeout;
+ }
+ } else
+ timeout = 0;

poll_initwait(&table);

--- linux-2.6.14/include/linux/syscalls.h.org
+++ linux-2.6.14/include/linux/syscalls.h
@@ -420,7 +420,7 @@ asmlinkage long sys_socketpair(int, int,
asmlinkage long sys_socketcall(int call, unsigned long __user *args);
asmlinkage long sys_listen(int, int);
asmlinkage long sys_poll(struct pollfd __user *ufds, unsigned int nfds,
- long timeout);
+ int timeout_msecs);
asmlinkage long sys_select(int n, fd_set __user *inp, fd_set __user *outp,
fd_set __user *exp, struct timeval __user *tvp);
asmlinkage long sys_epoll_create(int size);