xorl %eax, %eax

Linux kernel KVM Memory Corruption on MCE setup

leave a comment »

I saw this vulnerability in securityfocus, the bug was reported by Jan Kiszka of Siemens as we can read from 2.6.32-rc7’s ChangeLog file.
Here is the buggy code as seen in 2.6.32-rc6 release of the Linux kernel…

static int kvm_vcpu_ioctl_x86_setup_mce(struct kvm_vcpu *vcpu,
					u64 mcg_cap)
{
	int r;
	unsigned bank_num = mcg_cap & 0xff, bank;

	r = -EINVAL;
	if (!bank_num)
		goto out;
	if (mcg_cap & ~(KVM_MCE_CAP_SUPPORTED | 0xff | 0xff0000))
		goto out;
	r = 0;
	vcpu->arch.mcg_cap = mcg_cap;
	/* Init IA32_MCG_CTL to all 1s */
	if (mcg_cap & MCG_CTL_P)
		vcpu->arch.mcg_ctl = ~(u64)0;
	/* Init IA32_MCi_CTL to all 1s */
	for (bank = 0; bank < bank_num; bank++)
		vcpu->arch.mce_banks[bank*4] = ~(u64)0;
out:
	return r;
}

The above routine was ripped from arch/x86/kvm/x86.c file, and it is used to setup MCE (Machine Check Exception) support in the virtual processor, this is a recently added feature in KVM as we can find here. Anyway, the above code assumes that the user controlled MCE banks’ value (stored in ‘bank_num’) will not be more than 32 (which is declared by the ‘KVM_MAX_MCE_BANKS’ constant) but on the other hand, it allows a user to specify up to 255 MCE banks because of the:

unsigned bank_num = mcg_cap & 0xff, bank;

That masks it with 0xFF and doesn’t perform any range checks after doing so. The subsequent ‘for’ loop in kvm_vcpu_ioctl_x86_setup_mce() will result in kernel memory corruption since it will attempt to copy data beyond ‘vcpu->arch.mce_banks[]’ array’s bounds. This array has a static size which you can find in kvm_arch_vcpu_init() function.

int kvm_arch_vcpu_init(struct kvm_vcpu *vcpu)
{
	struct page *page;
	struct kvm *kvm;
	int r;

	BUG_ON(vcpu->kvm == NULL);
	kvm = vcpu->kvm;
   ...
	vcpu->arch.mce_banks = kzalloc(KVM_MAX_MCE_BANKS * sizeof(u64) * 4,
				       GFP_KERNEL);
   ...
	return r;
}

Of course, the patch was to add that missing check like this:

        unsigned bank_num = mcg_cap & 0xff, bank;
 
        r = -EINVAL;
-       if (!bank_num)
+       if (!bank_num || bank_num >= KVM_MAX_MCE_BANKS)
                goto out;
        if (mcg_cap & ~(KVM_MCE_CAP_SUPPORTED | 0xff | 0xff0000))

Finally, this code can be reached through ‘KVM_X86_SETUP_MCE’ IOCTL command, here is the exact code path in the IOCTL handler.

long kvm_arch_vcpu_ioctl(struct file *filp,
			 unsigned int ioctl, unsigned long arg)
{
	struct kvm_vcpu *vcpu = filp->private_data;
	void __user *argp = (void __user *)arg;
	int r;
	struct kvm_lapic_state *lapic = NULL;

	switch (ioctl) {
      ...
	case KVM_X86_SETUP_MCE: {
		u64 mcg_cap;

		r = -EFAULT;
		if (copy_from_user(&mcg_cap, argp, sizeof mcg_cap))
			goto out;
		r = kvm_vcpu_ioctl_x86_setup_mce(vcpu, mcg_cap);
		break;
	}
      ...
	return r;
}

Written by xorl

November 17, 2009 at 21:29

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