Commit message: Fix krb5_read_message handling [CVE-2014-5355]

In recvauth_common, do not use strcmp against the data fields of
krb5_data objects populated by krb5_read_message(), as there is no
guarantee that they are C strings.  Instead, create an expected
krb5_data value and use data_eq().

In the sample user-to-user server application, check that the received
client principal name is null-terminated before using it with printf
and krb5_parse_name.

CVE-2014-5355:

In MIT krb5, when a server process uses the krb5_recvauth function, an
unauthenticated remote attacker can cause a NULL dereference by
sending a zero-byte version string, or a read beyond the end of
allocated storage by sending a non-null-terminated version string.
The example user-to-user server application (uuserver) is similarly
vulnerable to a zero-length or non-null-terminated principal name
string.

The krb5_recvauth function reads two version strings from the client
using krb5_read_message(), which produces a krb5_data structure
containing a length and a pointer to an octet sequence.  krb5_recvauth
assumes that the data pointer is a valid C string and passes it to
strcmp() to verify the versions.  If the client sends an empty octet
sequence, the data pointer will be NULL and strcmp() will dereference
a NULL pointer, causing the process to crash.  If the client sends a
non-null-terminated octet sequence, strcmp() will read beyond the end
of the allocated storage, possibly causing the process to crash.

uuserver similarly uses krb5_read_message() to read a client principal
name, and then passes it to printf() and krb5_parse_name() without
verifying that it is a valid C string.

The krb5_recvauth function is used by kpropd and the Kerberized
versions of the BSD rlogin and rsh daemons.  These daemons are usually
run out of inetd or in a mode which forks before processing incoming
connections, so a process crash will generally not result in a
complete denial of service.

Thanks to Tim Uglow for discovering this issue.

CVSSv2: AV:N/AC:L/Au:N/C:N/I:N/A:P/E:POC/RL:OF/RC:C

[tlyu@mit.edu: CVSS score]

ticket: 8050 (new)
target_version: 1.13.1
tags: pullup
CVE: CVE-2014-5355
CWE: CWE-703
Vulnerability description: MIT Kerberos 5 (aka krb5) through 1.13.1 incorrectly expects that a krb5_read_message data field is represented as a string ending with a '\0' character, which allows remote attackers to (1) cause a denial of service (NULL pointer dereference) via a zero-byte version string or (2) cause a denial of service (out-of-bounds read) by omitting the '\0' character, related to appl/user_user/server.c and lib/krb5/krb/recvauth.c.

Project: krb5
File: server.c

int main(argc, argv)
    int argc;
    char *argv[];
{
    krb5_data pname_data, tkt_data;
    int sock = 0;
    socklen_t l;
    int retval;
    struct sockaddr_in l_inaddr, f_inaddr;        /* local, foreign address */
    krb5_creds creds, *new_creds;
    krb5_ccache cc;
    krb5_data msgtext, msg;
    krb5_context context;
    krb5_auth_context auth_context = NULL;

#ifndef DEBUG
    freopen("/tmp/uu-server.log", "w", stderr);
#endif

    retval = krb5_init_context(&context);
    if (retval) {
        com_err(argv[0], retval, "while initializing krb5");
        exit(1);
    }

#ifdef DEBUG
    {
        int one = 1;
        int acc;
        struct servent *sp;
        socklen_t namelen = sizeof(f_inaddr);

        if ((sock = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
            com_err("uu-server", errno, "creating socket");
            exit(3);
        }

        l_inaddr.sin_family = AF_INET;
        l_inaddr.sin_addr.s_addr = 0;
        if (argc == 2) {
            l_inaddr.sin_port = htons(atoi(argv[1]));
        } else  {
            if (!(sp = getservbyname("uu-sample", "tcp"))) {
                com_err("uu-server", 0, "can't find uu-sample/tcp service");
                exit(3);
            }
            l_inaddr.sin_port = sp->s_port;
        }

        (void) setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&one, sizeof (one));
        if (bind(sock, (struct sockaddr *)&l_inaddr, sizeof(l_inaddr))) {
            com_err("uu-server", errno, "binding socket");
            exit(3);
        }
        if (listen(sock, 1) == -1) {
            com_err("uu-server", errno, "listening");
            exit(3);
        }

        printf("Server started\n");
        fflush(stdout);

        if ((acc = accept(sock, (struct sockaddr *)&f_inaddr, &namelen)) == -1) {
            com_err("uu-server", errno, "accepting");
            exit(3);
        }
        dup2(acc, 0);
        close(sock);
        sock = 0;
     }
 #endif
 
    /* principal name must be sent null-terminated. */
     retval = krb5_read_message(context, (krb5_pointer) &sock, &pname_data);
    if (retval || pname_data.length == 0 ||
        pname_data.data[pname_data.length - 1] != '\0') {
         com_err ("uu-server", retval, "reading pname");
         return 2;
     }

    retval = krb5_read_message(context, (krb5_pointer) &sock, &tkt_data);
    if (retval) {
        com_err ("uu-server", retval, "reading ticket data");
        return 2;
    }

    retval = krb5_cc_default(context, &cc);
    if (retval) {
        com_err("uu-server", retval, "getting credentials cache");
        return 4;
    }

    memset (&creds, 0, sizeof(creds));
    retval = krb5_cc_get_principal(context, cc, &creds.client);
    if (retval) {
        com_err("uu-client", retval, "getting principal name");
        return 6;
    }

    /* client sends it already null-terminated. */
    printf ("uu-server: client principal is \"%s\".\n", pname_data.data);

    retval = krb5_parse_name(context, pname_data.data, &creds.server);
    if (retval) {
        com_err("uu-server", retval, "parsing client name");
        return 3;
    }

    creds.second_ticket = tkt_data;
    printf ("uu-server: client ticket is %d bytes.\n",
            creds.second_ticket.length);

    retval = krb5_get_credentials(context, KRB5_GC_USER_USER, cc,
                                  &creds, &new_creds);
    if (retval) {
        com_err("uu-server", retval, "getting user-user ticket");
        return 5;
    }

#ifndef DEBUG
    l = sizeof(f_inaddr);
    if (getpeername(0, (struct sockaddr *)&f_inaddr, &l) == -1)
    {
        com_err("uu-server", errno, "getting client address");
        return 6;
    }
#endif
    l = sizeof(l_inaddr);
    if (getsockname(0, (struct sockaddr *)&l_inaddr, &l) == -1)
    {
        com_err("uu-server", errno, "getting local address");
        return 6;
    }

    /* send a ticket/authenticator to the other side, so it can get the key
       we're using for the krb_safe below. */

    retval = krb5_auth_con_init(context, &auth_context);
    if (retval) {
        com_err("uu-server", retval, "making auth_context");
        return 8;
    }

    retval = krb5_auth_con_setflags(context, auth_context,
                                    KRB5_AUTH_CONTEXT_DO_SEQUENCE);
    if (retval) {
        com_err("uu-server", retval, "initializing the auth_context flags");
        return 8;
    }

    retval =
        krb5_auth_con_genaddrs(context, auth_context, sock,
                               KRB5_AUTH_CONTEXT_GENERATE_LOCAL_FULL_ADDR |
                               KRB5_AUTH_CONTEXT_GENERATE_REMOTE_FULL_ADDR);
    if (retval) {
        com_err("uu-server", retval, "generating addrs for auth_context");
        return 9;
    }

#if 1
    retval = krb5_mk_req_extended(context, &auth_context,
                                  AP_OPTS_USE_SESSION_KEY,
                                  NULL, new_creds, &msg);
    if (retval) {
        com_err("uu-server", retval, "making AP_REQ");
        return 8;
    }
    retval = krb5_write_message(context, (krb5_pointer) &sock, &msg);
#else
    retval = krb5_sendauth(context, &auth_context, (krb5_pointer)&sock, "???",
                           0, 0,
                           AP_OPTS_MUTUAL_REQUIRED | AP_OPTS_USE_SESSION_KEY,
                           NULL, &creds, cc, NULL, NULL, NULL);
#endif
    if (retval)
        goto cl_short_wrt;

    free(msg.data);

    msgtext.length = 32;
    msgtext.data = "Hello, other end of connection.";

    retval = krb5_mk_safe(context, auth_context, &msgtext, &msg, NULL);
    if (retval) {
        com_err("uu-server", retval, "encoding message to client");
        return 6;
    }

    retval = krb5_write_message(context, (krb5_pointer) &sock, &msg);
    if (retval) {
    cl_short_wrt:
        com_err("uu-server", retval, "writing message to client");
        return 7;
    }


    krb5_free_data_contents(context, &msg);
    krb5_free_data_contents(context, &pname_data);
    /* tkt_data freed with creds */
    krb5_free_cred_contents(context, &creds);
    krb5_free_creds(context, new_creds);
    krb5_cc_close(context, cc);
    krb5_auth_con_free(context, auth_context);
    krb5_free_context(context);
    return 0;
}