xorl %eax, %eax

Linux kernel FUSE Uninitialized Pointer Access

leave a comment »

I saw this vulnerability on oss-security mailing list, reported by Eugene Teo of Red Hat. As he says, the bug affects Linux kernel between versions 2.6.14-rc1 and 2.6.32-rc7. The bug however, was discovered and reported by Anand V. Avati. If we have a look at fs/fuse/file.c of 2.6.31 release of the Linux kernel, we’ll see the following.

ssize_t fuse_direct_io(struct file *file, const char __user *buf,
                       size_t count, loff_t *ppos, int write)
{
     ...
        struct fuse_req *req;
     ...
        while (count) {
     ...
                int err = fuse_get_user_pages(req, buf, &nbytes, write);
                if (err) {
                        res = err;
                        break;
                }
     ...
                fuse_release_user_pages(req, !write);
                if (req->out.h.error) {
     ...
                } else if (nres > nbytes) {
                        res = -EIO;
                        break;
     ...
                if (nres != nbytes)
                        break;
     ...
                        req = fuse_get_req(fc);
                        if (IS_ERR(req))
                                break;
                }
        }
        fuse_put_request(fc, req);
        if (res > 0)
                *ppos = pos;

        return res;
}

The above routine is used by the FUSE filesystem to allocate the requests to the client represented by a ‘fuse_req’ structure which is defined at fs/fuse/fuse_i.h. Anand V. Avati noticed that when breaking out of the allocation loop because of an error condition, fuse_put_request() will be invoked in a probably invalid pointer since request could contain an invalid pointer and that also could be the reason of breaking out of the ‘while’ loop.
This was fixed by adding an error check for the ‘req’ pointer like this:

                }
        }
-       fuse_put_request(fc, req);
+       if (!IS_ERR(req))
+               fuse_put_request(fc, req);
        if (res > 0)
                *ppos = pos;

Additionally, Eugene Teo discussed a code path used to trigger the bug. Specifically, he says that the call to fuse_get_req() inside fuse_direct_io() shown above will lead to execution of the following:

struct fuse_req *fuse_get_req(struct fuse_conn *fc)
{
        struct fuse_req *req;
     ...
        atomic_inc(&fc->num_waiting);
     ...
        req = fuse_request_alloc();
        err = -ENOMEM;
        if (!req)
                goto out;
     ...
 out:
        atomic_dec(&fc->num_waiting);
        return ERR_PTR(err);
}

Function fuse_request_alloc() is using kmem_cache_alloc() to initialize ‘req’ pointer but under certain circumstances, it could fail to allocate the required space and return the ‘req’ pointer uninitialized. If this is the case, fuse_get_req() will fall to the ‘out’ label that will return will error.
Back to fuse_direct_io() we can read that if the call to fuse_get_req() fails, it will break out of the loop and call fuse_put_request() on the uninitialized/invalid pointer ‘req’. This will lead to the following routine which can be found at fs/fuse/dev.c:

void fuse_put_request(struct fuse_conn *fc, struct fuse_req *req)
{
        if (atomic_dec_and_test(&req->count)) {
                if (req->waiting)
                        atomic_dec(&fc->num_waiting);

                if (req->stolen_file)
                        put_reserved_req(fc, req);
                else
                        fuse_request_free(req);
        }
}
EXPORT_SYMBOL_GPL(fuse_put_request);

The first call to atomic_dec_and_test() will lead to an invalid pointer access when it’ll attempt to decrement the ‘count’ member of that pointer which would be located in something like ‘&(uninitialized_ptr)->count’. The same would also happen in the ‘req->stolen_file’ check that will call either put_reserved_req() or fuse_request_free().
The first case of calling put_reserved_req() will lead to a possibly exploitable situation because of all these operations on the invalid pointer:

/*
 * Put stolen request back into fuse_file->reserved_req
 */
static void put_reserved_req(struct fuse_conn *fc, struct fuse_req *req)
{
        struct file *file = req->stolen_file;
        struct fuse_file *ff = file->private_data;
 
        spin_lock(&fc->lock);
        fuse_request_init(req);
        BUG_ON(ff->reserved_req);
        ff->reserved_req = req;
        wake_up_all(&fc->reserved_req_waitq);
        spin_unlock(&fc->lock);
        fput(file);
}

Whereas, the latter scenario, it leads to an already known to be exploitable call to this:

void fuse_request_free(struct fuse_req *req)
{
        kmem_cache_free(fuse_req_cachep, req);
}

Interesting vulnerability although it will require some clever way to make the kmem_cache_alloc() fail and predict the location of the uninitialized pointer in order to have a controlled cache de-allocation that can lead to code execution in the kernel context.

Written by xorl

November 19, 2009 at 20:49

Posted in bugs, linux

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