Search code examples
clinuxsocketstcpglibc

recv() returning zero but cannot see any TCP disconnection


I am using the below code to receive on a socket. recv() is returning zero bytes received, implying the connection was disconnected, except it doesn't look like it was:

while( true )
{                  
    buffer = onGetBuffer( maxBytesToRecv );

    const int flags = MSG_WAITALL;

    errno = 0;
    const ssize_t bytesReceived = ::recv( descriptor() , buffer , maxBytesToRecv , flags );

    if ( bytesReceived > 0 ){
        processData( receivedTime , bytesReceived );   
    }
    else if ( bytesReceived < 0 ){
        const int eno = errno;
        if ( EINTR != eno ){
            // Log error code
        }
    }
    else{
        // Peer disconnected
        // ** Code is reaching here **
    }                   
}

and I am hitting the branch implying peer has disconnected because bytesReceived is zero. I have checked a pcap dump and we did not receive a TCP FIN message to close the connection.

I ran strace (filtering on network messages) during this to check we didn't close the connection ourselves and for the socket in question it recorded:

sendto(16, "DATA"..., 88, MSG_NOSIGNAL, NULL, 0) = 88
sendto(16, "DATA"..., 88, MSG_NOSIGNAL, NULL, 0) = 88
sendto(16, "DATA"..., 5584, MSG_NOSIGNAL, NULL, 0) = 5584
sendto(16, "DATA"..., 5654, MSG_NOSIGNAL, NULL, 0) = 5654
sendto(16, "DATA"..., 5651, MSG_NOSIGNAL, NULL, 0) = 5651
sendto(16, "DATA"..., 5593, MSG_NOSIGNAL, NULL, 0) = 5593
sendto(16, "DATA"..., 5635, MSG_NOSIGNAL, NULL, 0) = 5635
sendto(16, "DATA"..., 5563, MSG_NOSIGNAL, NULL, 0) = 5563
sendto(16, "DATA"..., 5608, MSG_NOSIGNAL, NULL, 0) = 5608
sendto(16, "DATA"..., 5662, MSG_NOSIGNAL, NULL, 0 <unfinished ...>
sendto(16, "DATA"..., 5583, MSG_NOSIGNAL, NULL, 0) = 5583
sendto(16, "DATA"..., 5579, MSG_NOSIGNAL, NULL, 0) = 5579
sendto(16, "DATA"..., 3373, MSG_NOSIGNAL, NULL, 0) = 3373
sendto(16, "DATA"..., 201, MSG_NOSIGNAL, NULL, 0) = 201
recvfrom(16, "DATA"..., 7126, MSG_WAITALL, NULL, NULL) = 7126
recvfrom(16, "DATA"..., 6187, MSG_WAITALL, NULL, NULL) = 6187
recvfrom(16, "DATA"..., 7079, MSG_WAITALL, NULL, NULL) = 7079
recvfrom(16, "", 0, MSG_WAITALL, NULL, NULL) = 0

where 16 is the descriptor for the socket. At the end you can see the final receive returns 0.

If we haven't disconnected ourselves and we didn't receive a TCP FIN from the other side to disconnect us. There is no error code being returned, errno is zero too.

Why is recv() returning zero if there was no disconnection? Or what else can I check?


Solution

  • Your final recv call was for 0 bytes.

    From the recv man page on Linux:

    The value 0 may also be returned if the requested number of bytes to receive from a stream socket was 0.

    As commentary on the program, it looks to me that maxBytesToRecv is some kind of global or class member which is weird. It also looks like a possibly huge security hole if the program is trusting the data stream to declare how large the pieces are.

    Trust, but VERIFY. Bounds check that length value. Handle zero size to solve this problem but also check for too large of a size. Many, many exploits have happened because of huge size values and integer overflows. Or worse, copying an unsigned length into a signed integer and treating 0xFFFF as 65535 some places and -1 other places.