ip_vs_add_service() and ip_vs_edit_service() convert the persistent timeout from seconds to jiffies with u->timeout * HZ. Both operands are unsigned int and the value comes unchecked from the socket API or from IPVS_SVC_ATTR_TIMEOUT, so timeouts above UINT_MAX / HZ wrap. With HZ=1000, 4294968 seconds becomes 704 jiffies and is reported back as 0 seconds. Persistence templates copy svc->timeout, so their timer is armed for 0.7 seconds and a client can be moved to another real server long before the configured timeout. A related boundary exists on 32-bit kernels. Some products that fit in unsigned int exceed the maximum safe jiffy delay. For example, 4294967 seconds becomes 4294967000 jiffies with HZ=1000. Adding that delay to jiffies wraps, and the timer code can treat the result as already expired. Limit the seconds value using both the storage width and MAX_JIFFY_OFFSET before multiplying by HZ. Saturate out-of-range values instead of rejecting requests that previously succeeded. Also print the stored unsigned timeout with %u in /proc/net/ip_vs, since a valid stored value can exceed INT_MAX. The overflow was reproduced in an x86-64 QEMU guest (HZ=1000) through the IPVS socket API. A persistent one-packet UDP service with two real servers was set to 4294968 seconds, and a client in another network namespace sent two datagrams 1.5 seconds apart. On the base kernel the template expired between the datagrams, as it did for a 1-second control, and the second datagram went to the other real server. With this change the template remained present and both datagrams went to the same server. The 300-second control was unchanged. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: Yingjie Wang --- Notes: ip_vs_set_timeout() rejects global TCP/UDP timeouts above INT_MAX / HZ with -EINVAL. This path saturates instead because the persistent timeout is a __u32 whose full range has always been accepted. Saturation avoids adding a new error result while keeping the stored delay within both the storage width and the safe jiffy range. net/netfilter/ipvs/ip_vs_ctl.c | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/net/netfilter/ipvs/ip_vs_ctl.c b/net/netfilter/ipvs/ip_vs_ctl.c index ce9fe20d6106a..0d4a113eb5a7f 100644 --- a/net/netfilter/ipvs/ip_vs_ctl.c +++ b/net/netfilter/ipvs/ip_vs_ctl.c @@ -1749,6 +1749,15 @@ static void ip_vs_dest_trash_expire(struct timer_list *t) spin_unlock(&ipvs->dest_trash_lock); } +/* Convert seconds without overflowing or exceeding a safe jiffy delay. */ +static unsigned int ip_vs_svc_timeout(unsigned int timeout) +{ + unsigned long max_secs; + + max_secs = min_t(unsigned long, UINT_MAX, MAX_JIFFY_OFFSET) / HZ; + return min_t(unsigned long, timeout, max_secs) * HZ; +} + /* * Add a service into the service hash table */ @@ -1864,7 +1873,7 @@ ip_vs_add_service(struct netns_ipvs *ipvs, struct ip_vs_service_user_kern *u, svc->port = u->port; svc->fwmark = u->fwmark; svc->flags = u->flags & ~IP_VS_SVC_F_HASHED; - svc->timeout = u->timeout * HZ; + svc->timeout = ip_vs_svc_timeout(u->timeout); svc->netmask = u->netmask; svc->ipvs = ipvs; @@ -2031,7 +2040,7 @@ ip_vs_edit_service(struct ip_vs_service *svc, struct ip_vs_service_user_kern *u) * Set the flags and timeout value */ svc->flags = u->flags | IP_VS_SVC_F_HASHED; - svc->timeout = u->timeout * HZ; + svc->timeout = ip_vs_svc_timeout(u->timeout); svc->netmask = u->netmask; old_pe = rcu_dereference_protected(svc->pe, 1); @@ -3064,9 +3073,9 @@ static int ip_vs_info_seq_show(struct seq_file *seq, void *v) } if (svc->flags & IP_VS_SVC_F_PERSISTENT) - seq_printf(seq, "persistent %d %08X\n", - svc->timeout, - ntohl(svc->netmask)); + seq_printf(seq, "persistent %u %08X\n", + svc->timeout, + ntohl(svc->netmask)); else seq_putc(seq, '\n'); base-commit: 734bef548a96e8774d6eef5dfa2026c6edc4e80b -- 2.43.0