From: Igor Shevlyakov <[email protected]>

The kernel looks the fd up before validating the rest of the call, so a
negative fd is EBADF whatever else is wrong with the arguments.  Several
arms of do_setsockopt()/do_getsockopt() reject optlen or optname and
return without ever reaching the host syscall, so the host never gets to
supply that EBADF and the guest sees the argument error instead:

    setsockopt(-1, SOL_TCP, TCP_NODELAY, ptr, 0)    EINVAL, want EBADF
    setsockopt(-1, 0x4321, 1, ptr, sizeof(int))     ENOPROTOOPT
    getsockopt(-1, 0x4321, 1, ptr, &len)            EOPNOTSUPP

Caught by glibc's posix/test-errno.

Signed-off-by: Igor Shevlyakov <[email protected]>
Reviewed-by: Helge Deller <[email protected]>
Signed-off-by: Helge Deller <[email protected]>
---
 linux-user/syscall.c | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/linux-user/syscall.c b/linux-user/syscall.c
index 9f454502cb..e7c9fb33a3 100644
--- a/linux-user/syscall.c
+++ b/linux-user/syscall.c
@@ -2119,6 +2119,15 @@ static abi_long do_setsockopt(int sockfd, int level, int 
optname,
     abi_long ret;
     int val;
 
+    /*
+     * The kernel looks the fd up before it validates anything else, so a bad
+     * fd is EBADF even when the rest of the call is also wrong.  Several arms
+     * below reject optlen or optname without ever reaching the host syscall,
+     * and would report that instead.
+     */
+    if (sockfd < 0) {
+        return -TARGET_EBADF;
+    }
     switch(level) {
     case SOL_TCP:
     case SOL_UDP:
@@ -2623,6 +2632,10 @@ static abi_long do_getsockopt(int sockfd, int level, int 
optname,
     int len, val;
     socklen_t lv;
 
+    /* EBADF wins over the argument checks below; see do_setsockopt(). */
+    if (sockfd < 0) {
+        return -TARGET_EBADF;
+    }
     switch(level) {
     case TARGET_SOL_SOCKET:
         level = SOL_SOCKET;
-- 
2.54.0


Reply via email to