xorl %eax, %eax

Linux kernel Multiple Capabilities Missing Checks

with 4 comments

From 2.6.31.5’s ChangeLog file we can find a series of vulnerabilities because of missing capabilities checks in kernel code. The first bug was reported by Philipp Reisner of LinBit and it affects the frame-buffer driver for VBE 2.0+ compliant video cards for Linux which resides at drivers/video/uvesafb.c. Here is the susceptible routine:

/*
 * A handler for replies from userspace.
 *
 * Make sure each message passes consistency checks and if it does,
 * find the kernel part of the task struct, copy the registers and
 * the buffer contents and then complete the task.
 */
static void uvesafb_cn_callback(void *data)
{
        struct cn_msg *msg = data;
     ...
        utask = (struct uvesafb_task *)msg->data;

        /* Sanity checks for the buffer length. */
     ...
        uvfb_tasks[msg->seq] = NULL;
        mutex_unlock(&uvfb_lock);

        memcpy(&task->t, utask, sizeof(*utask));

        if (task->t.buf_len && task->buf)
                memcpy(task->buf, utask + 1, task->t.buf_len);

        complete(task->done);
        return;
}
      ...
static int __devinit uvesafb_init(void)
{
      ...
        err = cn_add_callback(&uvesafb_cn_id, "uvesafb", uvesafb_cn_callback);
      ...
}

This callback function which is initialized by uvesafb_init() it handles user controlled data stored in ‘msg->data’ and it uses them to perform a new task. The bug was that there was no check on the caller’s capabilities, this means that unprivileged users were able to call the above routine even if they did not have CAP_SYS_ADMIN capability set.
This was fixed by applying the following patch:

 	struct uvesafb_task *utask;
 	struct uvesafb_ktask *task;
 
+	if (!cap_raised(nsp->eff_cap, CAP_SYS_ADMIN))
+		return;
+
 	if (msg->seq >= UVESAFB_TASKS_MAX)

By doing this, they check for the CAP_SYS_ADMIN capability.
Another three similar issues were also reported by Philipp Reisner. The second one is located in the POHMELFS driver which can be found at drivers/staging/pohmelfs/config.c like this:

static void pohmelfs_cn_callback(void *data)
{
        struct cn_msg *msg = data;
        int err;

        switch (msg->flags) {
                case POHMELFS_FLAGS_ADD:
                case POHMELFS_FLAGS_DEL:
                case POHMELFS_FLAGS_MODIFY:
                        err = pohmelfs_cn_ctl(msg, msg->flags);
                        break;
                case POHMELFS_FLAGS_SHOW:
                        err = pohmelfs_cn_disp(msg);
                        break;
                case POHMELFS_FLAGS_CRYPTO:
                        err = pohmelfs_cn_crypto(msg);
                        break;
                default:
                        err = -ENOSYS;
                        break;
        }
}
      ...
int __init pohmelfs_config_init(void)
{
        return cn_add_callback(&pohmelfs_cn_id, "pohmelfs", pohmelfs_cn_callback);
}

Once again, ‘msg’ contains the user controlled data and there is no check of the administration capability and thus, an unprivileged user can issue a call to that routine. Of course, this was patched like this:

 	int err;
 
+	if (!cap_raised(nsp->eff_cap, CAP_SYS_ADMIN))
+		return;
+
 	switch (msg->flags) {

The third one can be found at drivers/staging/dst/dcore.c where part of the DST driver resides. Here is the equivalent code:

/*
 * Configuration parser.
 */
static void cn_dst_callback(void *data)
{
        struct dst_ctl *ctl;
        struct cn_msg *msg = data;
    ...
        ctl = (struct dst_ctl *)msg->data;
    ...
        err = dst_commands[ctl->cmd](n, ctl, msg->data + sizeof(struct dst_ctl),
                        msg->len - sizeof(struct dst_ctl));
    ...
}
    ...
static int __init dst_sys_init(void)
{
    ...
        err = cn_add_callback(&cn_dst_id, "DST", cn_dst_callback);

The concept is pretty much the same, and the patch was also identical…

 	unsigned int hash;
 
+	if (!cap_raised(nsp->eff_cap, CAP_SYS_ADMIN)) {
+		err = -EPERM;
+		goto out;
+	}
+
 	if (msg->len < sizeof(struct dst_ctl)) {

The last one, was on the MD driver and specifically, at drivers/md/dm-log-userspace-transfer.c file in the following routine:

/*
 * This is the connector callback that delivers data
 * that was sent from userspace.
 */
static void cn_ulog_callback(void *data)
{
        struct cn_msg *msg = (struct cn_msg *)data;
        struct dm_ulog_request *tfr = (struct dm_ulog_request *)(msg + 1);

        spin_lock(&receiving_list_lock);
        if (msg->len == 0)
                fill_pkg(msg, NULL);
        else if (msg->len < sizeof(*tfr))
                DMERR("Incomplete message received (expected %u, got %u): [%u]",
                      (unsigned)sizeof(*tfr), msg->len, msg->seq);
        else
                fill_pkg(NULL, tfr);
        spin_unlock(&receiving_list_lock);
}
     ...
int dm_ulog_tfr_init(void)
{
     ...
        r = cn_add_callback(&ulog_cn_id, "dmlogusr", cn_ulog_callback);
     ...
}

And the patch was:

 	struct dm_ulog_request *tfr = (struct dm_ulog_request *)(msg + 1);
 
+	if (!cap_raised(nsp->eff_cap, CAP_SYS_ADMIN))
+		return;
+
 	spin_lock(&receiving_list_lock);

In any of the above cases, an unprivileged user could perform privileged tasks that could eventually lead to code execution in the context of the kernel. I wonder why there is no CVE assigned to those bugs…

Written by xorl

October 31, 2009 at 00:34

Posted in bugs, linux

4 Responses

Subscribe to comments with RSS.

  1. I have this in my TODO list. If you think this is security relevant, feel free to send an email to oss-security to request a CVE name. You can do your part too! Thanks.

    Eugene

    November 1, 2009 at 00:54

  2. Mr. Eugene I’m not good with official stuff at all.
    This is the main reason why I don’t contribute to mailing lists that have some really interesting threads from time to time such as LKML, DailyDave etc.
    I have a natural trolling attitude. I would be more than useless in such lists (if not banned). :P

    xorl

    November 1, 2009 at 01:45

  3. These are interesting issues. At least for the uvesafb vulnerability, I am finding that the netlink messages are passed through the netlink connector in versions of the kernel >= 2.6.14.

    As a result of this, I can’t bind to the required netlink group [s_addr.nl_groups = 1 << (CN_IDX_V86D-1)] as an unprivileged user. I'm probably doing something stupid though.

    anomie

    November 6, 2009 at 00:27

  4. Anon

    November 10, 2009 at 07:20


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