xorl %eax, %eax

CVE-2009-0923: Solaris Kerberos KDC DoS

with one comment

When I saw this vulnerability a couple of minutes ago, I was wondering what did they meant by “unspecified vulnerability“… A quick look at the OpenSolaris source code reveals that it’s not that much “unspecified“. Just have a look at usr/src/cmd/krb5/slave/kpropd.c. There you can find this:

307 int do_standalone(iprop_role iproprole)
308 {
309    struct    linger linger;
310    struct    servent *sp;
311    int    finet, fromlen, s;
312    int    on = 1;
313    int    ret, status = 0;
314    struct    sockaddr_in6 sin6 = { AF_INET6 };
315    int sin6_size = sizeof (sin6);
316
317    /* listen for either ipv4 or ipv6 */
318    finet = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP);
319    if (finet < 0 ) {
320    com_err(progname, errno, gettext("while obtaining socket"));
321    exit(1);
322    }
     ...
324    if(!port) {
325    sp = getservbyname(KPROP_SERVICE, "tcp")
     ...
335    /*
336     * We need to close the socket immediately if iprop is enabled,
337     * since back-to-back full resyncs are possible, so we do not
338     * linger around for too long
339     */
340    if (iproprole == IPROP_SLAVE) {
341        if (setsockopt(finet, SOL_SOCKET, SO_REUSEADDR,
342            (char *)&on, sizeof(on)) < 0)
     ...
352    if ((ret = bind(finet, (struct sockaddr *)&sin6, sizeof(sin6))) < 0) {
     ...
405    while (1) {
406        int child_pid;
407
408        s = accept(finet, (struct sockaddr *) &sin6, &sin6_size);
409
410        if (s < 0) {
411            if (errno != EINTR) {
412                /* Solaris Kerberos: Keep error messages consistent */
413                com_err(progname, errno,
414            gettext("while accepting connection"));
415            }
416            continue;
     ...
437        if (wait(&status) < 0) {
438        com_err(progname, errno,
439            gettext("while waiting to receive database"));
440        exit(1);
441        }
442
443        close(s);
     ...
451    if (iproprole == IPROP_SLAVE)
452        break;
453    }
454
455   

The problem with the above code is that when it attempts a full resynchronization it can stuck inside the while loop at lines 405-441 and the slave will be waiting for ever the updates from the master KDC. A malicious attacker which controls a slave KDC could request full resynchronization from the master KDC and force kpropd into looping in this infinite while loop. To fix this, a new constant and a global variable were defined in the above source code file:

#define    INITIAL_TIMER 10

and:

/*
 * Global fd to close upon alarm time-out.
 */
volatile int gfd = -1;

In addition, a new signal handler function was introduced to handle SIGALRM which is used to exit the loop. Here is the new function:

void resync_alarm(int sn)
{
    close(gfd);
    if (debug)
        fprintf(stderr, gettext("resync_alarm: closing fd: %d\n"), gfd);
    gfd = -1;
}

At last, do_standalone() was patched in a few places like this:

    int sin6_size = sizeof (sin6);
+   /*
+    * Timer for accept/read calls, in case of network type errors.
+    */
+    int backoff_timer = INITIAL_TIMER;

+ retry:
       /* listen for either ipv4 or ipv6 */
       finet = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP);

Which is used as a counter and it is used later at lines 370-387 like this:

            com_err(progname, errno,
                gettext("while setting socket option (SO_LINGER)"));
+        /*
+         * We also want to set a timer so that the slave is not waiting
+         * until infinity for an update from the master.
+         */
+        gfd = finet;
+        signal(SIGALRM, resync_alarm);
+        if (debug) {
+        fprintf(stderr, "do_standalone: setting resync alarm to %d\n",
+            backoff_timer);
+        }
+        if (alarm(backoff_timer) != 0) {
+          if (debug) {
+            fprintf(stderr,
+            gettext("%s: alarm already set\n"), progname);
+          }
+        }
+        backoff_timer *= 2;
       }
       if ((ret = bind(finet, (struct sockaddr *)&sin6, sizeof(sin6))) < 0) {
    if (debug) {

Additionally, the error handling at line 411 was changed to support this new feature and jump back to the new label “retry” if the backoff_timer is less than 120 (which stands for seconds). This was performed like this:

        if (s < 0) {
-            if (errno != EINTR) {
-                /* Solaris Kerberos: Keep error messages consistent */
-                com_err(progname, errno,
-            gettext("while accepting connection"));
-            }
-            continue;
+            int e = errno; 
+            if (e != EINTR) { 
+                /* 
+                 * Solaris Kerberos: Keep error messages 
+                 * consistent 
+                 */ 
+                com_err(progname, e, 
+                    gettext("while accepting connection")); 
+                backoff_timer = INITIAL_TIMER;
+            }
+            /* 
+             * If we got EBADF, an alarm signal handler closed 
+             * the file descriptor on us. 
+             */ 
+            if (e != EBADF) 
+                close(finet); 
+            /* 
+             * An alarm could have been set and the fd closed, we 
+             * should retry in case of transient network error for 
+             * up to a couple of minutes. 
+             */ 
+            if (backoff_timer > 120) 
+                return (EINTR); 
+            goto retry; 
+        }
+        alarm(0);
+        gfd = -1;
        if (debug && (iproprole != IPROP_SLAVE))
            child_pid = 0;


Clearly, the same patching should be also applied on lines 437-441 where there was a direct call to exit() library routine. This is now handled like this:

        default:
        /* parent */
+        /*
+         * Errors should not be considered fatal in the iprop case as we
+         * could have transient type errors, such as network outage, etc.
+         * Sleeping 3s for 2s linger interval.
+         */
        if (wait(&status) < 0) {
        com_err(progname, errno,
            gettext("while waiting to receive database"));
-        exit(1);
+         if (iproprole != IPROP_SLAVE)
+            exit(1);
+        sleep(3);
        }

At last, two more functions were patched to support the new signal handler. The first one was doit() which is the initialization function and the patch is extremely simple, just initialization of the new mechanism:

        }
    log_ctx = kpropd_context->kdblog_context;
-    if (log_ctx && (log_ctx->iproprole == IPROP_SLAVE))
+    if (log_ctx && (log_ctx->iproprole == IPROP_SLAVE)) {
        ulog_set_role(doit_context, IPROP_SLAVE);
+        /*
+         * We also want to set a timer so that the slave is not waiting
+         * until infinity for an update from the master.
+         */
+        if (debug)
+            fprintf(stderr, "doit: setting resync alarm to %ds\n",
+                INITIAL_TIMER);
+        signal(SIGALRM, resync_alarm);
+        gfd = fd;
+        if (alarm(INITIAL_TIMER) != 0) {
+            if (debug) {
+                fprintf(stderr,
+                    gettext("%s: alarm already set\n"), progname);
+            }
+        }
+    }

    fromlen = (socklen_t)sizeof (from);
    if (getpeername(fd, (struct sockaddr *) &from, &fromlen) < 0) {


And later on, exactly after performing the Kerberos authentication it sets the alarm accordingly:

    /* Solaris Kerberos */
    kerberos_authenticate(doit_context, fd, &client, &etype, &from);
+    /*
+     * Turn off alarm upon successful authentication from master.+     */
+    alarm(0);
+    gfd = -1;
+
    if (!authorized_principal(doit_context, client, etype)) {
        char    *name;

Finally, the last routine being patched is krb5_error_code do_iprop() which is responsible for handling incremental update transfers from master KDC to slaves. Actually, this is how you can trigger this vulnerability but anyway. Here is the patch on this function:

                                */
                ret = do_standalone(log_ctx->iproprole);
-                if (ret)
-                    syslog(LOG_WARNING,
-                        gettext("kpropd: Full resync, "
-                        "invalid return."));
                if (debug)
                    if (ret)
                        fprintf(stderr,
                            gettext("Full resync "
                            "was unsuccessful\n"));
                    else
                        fprintf(stderr,
                            gettext("Full resync "
                            "was successful\n"));
-                 frdone = B_TRUE;
+                if (ret) {
+                    syslog(LOG_WARNING,
+                        gettext("kpropd: Full resync, "
+                        "invalid return."));
+                    /*
+                     * Start backing-off immediately after
+                     * failure.
+                     */
+                    backoff_cnt++;
+                    frdone = B_FALSE;
+                } else
+                    frdone = B_TRUE;
                break;

That’s all with this Solaris KDC DoS. I still wonder… Why it was identified as “unspecified“? It’s even funnier that all of the public advisories regarding this bug are classifying it as unspecified! Isn’t that just retarded? Here are a few:
Secunia
Vupen
XForce
CVE Mitre
SecurityDatabase
SecurityLab
This is at least pathetic.

Written by xorl

March 30, 2009 at 14:14

Posted in bugs, solaris

One Response

Subscribe to comments with RSS.

  1. VDBs used “unspecified” because the vendor released no details, and until this blog post, had no additional information or analysis. If researchers would do what you did, “unspecified” would be a thing of the past. =)

    Jericho

    April 3, 2009 at 09:07


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s