Bug 104601

Summary: Description of getname-related syscalls wrong
Product: Documentation Reporter: mattator
Component: man-pagesAssignee: documentation_man-pages (documentation_man-pages)
Status: RESOLVED INVALID    
Severity: high CC: mtk.manpages
Priority: P1    
Hardware: All   
OS: Linux   
Kernel Version: Subsystem:
Regression: No Bisected commit-id:

Description mattator 2015-09-15 15:28:35 UTC
For instance, if I look at the getsockname man page on ubuntu 15.04:
===
       getsockname()  returns  the current address to which the socket sockfd is bound, in the buffer
       pointed to by addr.  The addrlen argument should be initialized  to  indicate  the  amount  of
       space  (in  bytes)  pointed  to  by addr.  On return it contains the actual size of the socket
       address.

       The returned address is truncated if the buffer provided is too
       small; in this case, addrlen will return a value greater than was
       supplied to the call.
===

I've looked at some getname function for different protocols in the 4.3 and 3.14 kernels and the addrlen parameter was never considered as an input or the returned sockaddr* truncated.
This was the cause of a stack smashing in a userspace application I use and possibly many others so I ranked it high.

More details here:
http://stackoverflow.com/questions/32522031/mismatch-between-manpage-and-kernel-behavior-about-getsockname
Comment 1 Michael Kerrisk 2016-03-11 20:28:42 UTC
In my tests, getsockname *does* do truncation if the length argument is smaller than the address size.

Please post a *minimal* working example that demonstrates otherwise.

My example is below. Here are two sample runs:

$ ./a.out 
returned len = 16
 0: <0x2>
 1: <0x0>
 2: <0x15>
 3: <0xb3>
 4: <0x0>
 5: <0x0>
 6: <0x0>
 7: <0x0>
 8: <0x0>
 9: <0x0>
10: <0x0>
11: <0x0>
12: <0x0>
13: <0x0>
14: <0x0>
15: <0x0>
16: <0xff>
17: <0xff>
18: <0xff>
19: <0xff>
$ ./a.out 8
returned len = 16
 0: <0x2>
 1: <0x0>
 2: <0x15>
 3: <0xb3>
 4: <0x0>
 5: <0x0>
 6: <0x0>
 7: <0x0>
 8: <0xff>
 9: <0xff>
10: <0xff>
11: <0xff>
12: <0xff>
13: <0xff>
14: <0xff>
15: <0xff>
16: <0xff>
17: <0xff>
18: <0xff>
19: <0xff>

One can see that truncation has occurred in the second run, were a short length argument was supplied to getsockname().



#define _GNU_SOURCE
#include <netdb.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>

#define errExit(msg)    do { perror(msg); exit(EXIT_FAILURE); \
                        } while (0)

#define fatal(msg)      do { fprintf(stderr, "%s\n", msg); \
                             exit(EXIT_FAILURE); } while (0)

#define usageErr(msg, progName) \
                        do { fprintf(stderr, "Usage: "); \
                             fprintf(stderr, msg, progName); \
                             exit(EXIT_FAILURE); } while (0)

int
main(int argc, char *argv[])
{
    int lfd, optval;
    struct addrinfo hints;
    struct addrinfo *result, *rp;

    /* Call getaddrinfo() to obtain a list of addresses that
       we can try binding to */

    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_canonname = NULL;
    hints.ai_addr = NULL;
    hints.ai_next = NULL;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_family = AF_UNSPEC;        /* Allows IPv4 or IPv6 */
    hints.ai_flags = AI_PASSIVE | AI_NUMERICSERV;
                        /* Wildcard IP address; service name is numeric */

    if (getaddrinfo(NULL, "5555", &hints, &result) != 0)
        errExit("getaddrinfo");
                
    /* Walk through returned list until we find an address structure
       that can be used to successfully create and bind a socket */

    optval = 1;
    for (rp = result; rp != NULL; rp = rp->ai_next) {
        lfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
        if (lfd == -1)
            continue;                   /* On error, try next address */

        if (setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval))
                == -1)
             errExit("setsockopt");

        if (bind(lfd, rp->ai_addr, rp->ai_addrlen) == 0)
            break;                      /* Success */

        /* bind() failed: close this socket and try next address */

        close(lfd);
    }

    if (rp == NULL)
        fatal("Could not bind socket to any address");

    freeaddrinfo(result);

    struct sockaddr_storage saddr;
    socklen_t len;

    /* Preinitialize address structure with visually distinctive bytes */

    memset(&saddr, 0xff, sizeof(saddr));

    /* By default, we'll use the size of 'struct sockaddr' as the length
       argument for getsockname(). The user can override this by
       specifying a lengh value on the command line */

    len = sizeof(saddr);
    if (argc > 1)
        len = atoi(argv[1]);

    if (getsockname(lfd, (struct sockaddr *) &saddr, &len) == -1)
        errExit("getsockname");

    printf("returned len = %ld\n", (long) len);
    int j;
    unsigned char c;

    /* Inspect bytes in returned structure */

    for (j = 0; j < 20; j++) {
        c = (((char *) &saddr) [j]);
        printf("%2d: <0x%x>\n", j, c);
    }

}
Comment 2 Michael Kerrisk 2016-03-11 20:42:09 UTC
See also this code in net/socket.c

static int move_addr_to_user(struct sockaddr_storage *kaddr, int klen,
                             void __user *uaddr, int __user *ulen)
{
        int err;
        int len;

        BUG_ON(klen > sizeof(struct sockaddr_storage));
        err = get_user(len, ulen);
        if (err)
                return err;
        if (len > klen)
                len = klen;
        if (len < 0)
                return -EINVAL;
        if (len) {
                if (audit_sockaddr(klen, kaddr))
                        return -ENOMEM;
                if (copy_to_user(uaddr, kaddr, len))
                        return -EFAULT;
        }
        /*
         *      "fromlen shall refer to the value before truncation.."
         *                      1003.1g
         */
        return __put_user(klen, ulen);
}
Comment 3 Michael Kerrisk 2016-03-25 19:40:43 UTC
Closing as I believe this is not a bug. Please reopen if you can provide information to show otherwise.