| From: Lance Richardson <lance.richardson.net@gmail.com> |
| Date: Wed, 25 Apr 2018 10:21:54 -0400 |
| Subject: net: support compat 64-bit time in {s,g}etsockopt |
| |
| commit 988bf7243e03ef69238381594e0334a79cef74a6 upstream. |
| |
| For the x32 ABI, struct timeval has two 64-bit fields. However |
| the kernel currently interprets the user-space values used for |
| the SO_RCVTIMEO and SO_SNDTIMEO socket options as having a pair |
| of 32-bit fields. |
| |
| When the seconds portion of the requested timeout is less than 2**32, |
| the seconds portion of the effective timeout is correct but the |
| microseconds portion is zero. When the seconds portion of the |
| requested timeout is zero and the microseconds portion is non-zero, |
| the kernel interprets the timeout as zero (never timeout). |
| |
| Fix by using 64-bit time for SO_RCVTIMEO/SO_SNDTIMEO as required |
| for the ABI. |
| |
| The code included below demonstrates the problem. |
| |
| Results before patch: |
| $ gcc -m64 -Wall -O2 -o socktmo socktmo.c && ./socktmo |
| recv time: 2.008181 seconds |
| send time: 2.015985 seconds |
| |
| $ gcc -m32 -Wall -O2 -o socktmo socktmo.c && ./socktmo |
| recv time: 2.016763 seconds |
| send time: 2.016062 seconds |
| |
| $ gcc -mx32 -Wall -O2 -o socktmo socktmo.c && ./socktmo |
| recv time: 1.007239 seconds |
| send time: 1.023890 seconds |
| |
| Results after patch: |
| $ gcc -m64 -O2 -Wall -o socktmo socktmo.c && ./socktmo |
| recv time: 2.010062 seconds |
| send time: 2.015836 seconds |
| |
| $ gcc -m32 -O2 -Wall -o socktmo socktmo.c && ./socktmo |
| recv time: 2.013974 seconds |
| send time: 2.015981 seconds |
| |
| $ gcc -mx32 -O2 -Wall -o socktmo socktmo.c && ./socktmo |
| recv time: 2.030257 seconds |
| send time: 2.013383 seconds |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <sys/socket.h> |
| #include <sys/types.h> |
| #include <sys/time.h> |
| |
| void checkrc(char *str, int rc) |
| { |
| if (rc >= 0) |
| return; |
| |
| perror(str); |
| exit(1); |
| } |
| |
| static char buf[1024]; |
| int main(int argc, char **argv) |
| { |
| int rc; |
| int socks[2]; |
| struct timeval tv; |
| struct timeval start, end, delta; |
| |
| rc = socketpair(AF_UNIX, SOCK_STREAM, 0, socks); |
| checkrc("socketpair", rc); |
| |
| /* set timeout to 1.999999 seconds */ |
| tv.tv_sec = 1; |
| tv.tv_usec = 999999; |
| rc = setsockopt(socks[0], SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof tv); |
| rc = setsockopt(socks[0], SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof tv); |
| checkrc("setsockopt", rc); |
| |
| /* measure actual receive timeout */ |
| gettimeofday(&start, NULL); |
| rc = recv(socks[0], buf, sizeof buf, 0); |
| gettimeofday(&end, NULL); |
| timersub(&end, &start, &delta); |
| |
| printf("recv time: %ld.%06ld seconds\n", |
| (long)delta.tv_sec, (long)delta.tv_usec); |
| |
| /* fill send buffer */ |
| do { |
| rc = send(socks[0], buf, sizeof buf, 0); |
| } while (rc > 0); |
| |
| /* measure actual send timeout */ |
| gettimeofday(&start, NULL); |
| rc = send(socks[0], buf, sizeof buf, 0); |
| gettimeofday(&end, NULL); |
| timersub(&end, &start, &delta); |
| |
| printf("send time: %ld.%06ld seconds\n", |
| (long)delta.tv_sec, (long)delta.tv_usec); |
| exit(0); |
| } |
| |
| Fixes: 515c7af85ed9 ("x32: Use compat shims for {g,s}etsockopt") |
| Reported-by: Gopal RajagopalSai <gopalsr83@gmail.com> |
| Signed-off-by: Lance Richardson <lance.richardson.net@gmail.com> |
| Signed-off-by: David S. Miller <davem@davemloft.net> |
| Signed-off-by: Ben Hutchings <ben@decadent.org.uk> |
| --- |
| net/compat.c | 6 ++++-- |
| 1 file changed, 4 insertions(+), 2 deletions(-) |
| |
| --- a/net/compat.c |
| +++ b/net/compat.c |
| @@ -386,7 +386,8 @@ static int compat_sock_setsockopt(struct |
| if (optname == SO_ATTACH_FILTER) |
| return do_set_attach_filter(sock, level, optname, |
| optval, optlen); |
| - if (optname == SO_RCVTIMEO || optname == SO_SNDTIMEO) |
| + if (!COMPAT_USE_64BIT_TIME && |
| + (optname == SO_RCVTIMEO || optname == SO_SNDTIMEO)) |
| return do_set_sock_timeout(sock, level, optname, optval, optlen); |
| |
| return sock_setsockopt(sock, level, optname, optval, optlen); |
| @@ -451,7 +452,8 @@ static int do_get_sock_timeout(struct so |
| static int compat_sock_getsockopt(struct socket *sock, int level, int optname, |
| char __user *optval, int __user *optlen) |
| { |
| - if (optname == SO_RCVTIMEO || optname == SO_SNDTIMEO) |
| + if (!COMPAT_USE_64BIT_TIME && |
| + (optname == SO_RCVTIMEO || optname == SO_SNDTIMEO)) |
| return do_get_sock_timeout(sock, level, optname, optval, optlen); |
| return sock_getsockopt(sock, level, optname, optval, optlen); |
| } |