xorl %eax, %eax

CVE-2009-0870: Solaris NFSv4 Local DoS

leave a comment »

To clarify, this is only a lame infinite loop DoS. If you still want to know the details just keep reading. This was released by Sun Microsystems on 6 March 2009 and affects Sun Solaris up to 10 and OpenSolaris up to snv_111 build. The following code is (of course) from OpenSolaris…
The vulnerability can be found under uts/common/fs/nfs4_srv_readdir.c. This source code file is responsible for providing sufficient reading routines for the NFSv4 service. Here is the vulnerable function:

343 /*
344  * If readdir only needs to return FILEID, we can take it from the
345  * dirent struct and save doing the lookup.
346  */
347 /* ARGSUSED */
348 void
349 rfs4_op_readdir(nfs_argop4 *argop, nfs_resop4 *resop,
350    struct svc_req *req, struct compound_state *cs)
351 {
352    READDIR4args *args = &argop->nfs_argop4_u.opreaddir;
353    READDIR4res *resp = &resop->nfs_resop4_u.opreaddir;
       ...
370    int iseofdir;
371    uint_t eof;
       ...
601 readagain:
602
603    no_space = FALSE;
604    iseofdir = FALSE;
605
606    vp = NULL;
607
608    /* Move on to reading the directory contents */
       ...
616    uio.uio_resid = rddir_data_len;
       ...
620    error = VOP_READDIR(dvp, &uio, cs->cr, &iseofdir, NULL, 0);
       ...
634    rddir_result_size = rddir_data_len - uio.uio_resid;
635
636    /* Reading at the end of the directory */
637    if (iseofdir && (rddir_result_size == 0)) {
638        /* encode the BOOLEAN marking no further entries */
639        IXDR_PUT_U_INT32(ptr, false);
640        /* encode the BOOLEAN signifying end of directory */
641        IXDR_PUT_U_INT32(ptr, true);
       ...
647    }
       ...
650    no_space = 0;
651    for (dp = (struct dirent64 *)rddir_data;
652        !no_space && rddir_result_size > 0; dp = nextdp(dp)) {
       ...
665        rddir_result_size -= dp->d_reclen;
666
667        /* skip "." and ".." entries */
       ...
1444    /*
1445     * Check for the case that another VOP_READDIR() has to be done.
1446     * - no space encoding error
1447     * - no entry successfully encoded
1448     * - still more directory to read
1449     */
1450    if (!no_space && nents == 0 && !iseofdir)
1451        goto readagain;
       ...
1497 }

As you can see at line 601 is where label ‘readagain‘ begins. This is used to iteratevely read the directories to be shared using the NFSv4. At line 637 the directory is checked wether is over (is End-Of-File directory) which was found at line 620 using the VOP_READDIR() macro and in addition, it checks if its size is zero which was calculated at line 634. This label, ‘readagain‘ is being invoked (lines 1444-1451) until it succeeds in the three cases which are described in the provided comments at lines 1444-1449. However, we may have reached the end of the directory and still not read any data which means that even though the listing should be terminated, the condition of line 637 will remain false since only its second statement will evaluate to true. In addition, another mistake appears at line 641 where macro IXDR_PUT_U_INT32() of usr/src/uts/common/rpc/xdr.h is being used. This macro is defined like this:

333 #define    IXDR_PUT_INT32(buf, v)        (*(buf)++ = (int32_t)htonl((uint32_t)v))
       ...
335 #define    IXDR_PUT_U_INT32(buf, v)    IXDR_PUT_INT32((buf), ((int32_t)(v)))

This means that it incorrectly always encodes the ptr as being the end of the directory. In order to trigger this vulnerability and enter this infinte loop inside ‘readagain‘ you have to use a filesystem that is able to support such structures. This is the HSFS (High Sierra & ISO 9660 CD-ROM file system) which is commonly used on CD-ROM/DVD-ROM directories. Consequently, you can share directories from a CD-ROM of this format using NFSv4 in order to trigger this bug while it attempts to list the directories of the media. The patch was simple:

-    /* Reading at the end of the directory */
-    if (iseofdir && (rddir_result_size == 0)) {
+       /* No data were read. Check if we reached the end of the directory. */
+       if (rddir_result_size == 0) {
        /* encode the BOOLEAN marking no further entries */
        IXDR_PUT_U_INT32(ptr, false);
        /* encode the BOOLEAN signifying end of directory */
-       IXDR_PUT_U_INT32(ptr, true);
+       IXDR_PUT_U_INT32(ptr, iseofdir ? true : false);
        resp->data_len = (char *)ptr - (char *)beginning_ptr;
        resp->mblk->b_wptr += resp->data_len;

Using this, the condition checks just for the size of the data read instead of the end of the directory and the assignment of the Boolean flag now correctly indicates the end of directory if this is the case.

Written by xorl

March 16, 2009 at 15:56

Posted in bugs, solaris

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