戏说Intel f00f Bug

f00f是Intel Pentium CPU的一个臭名昭著的bug,就算你不知道它的具体成因但估计也对其大名有所耳闻了。 它影响到了Intel Pentium,Pentium MMX和Pentium OverDrive系列的CPU,属于设计上的缺陷。最早出现在1997年11月,从当时的一些邮件存档来看,这个bug造成了不小的轰动。当然了,Intel自然也不弱(尤其是它的“纠错”和“兼容”技术,对i386稍微有所了解的人都知道),很快就给出了解决方案来应对。当然,在那以后的Intel CPU中就没有这个bug了。

好了,让我们现在一起来看看f00f bug的原理,恶劣影响,解决方法,以及对应到Linux内核源代码中的实际解决方案。

还得先从名字说起,这个bug之所以叫这么个名字,是因为它是用来指代f0 0f c7 c8这个16进制数序列,而这个序列是表示一条i386汇编指令,用AT&T语法来表示就是:


lock cmpxchg8b %eax

用Intel语法来表示是:


LOCK CMPXCHG8B EAX

这条指令明显是错误的,因为:

1. cmpxchg8b是用来比较两个64bit的数,其中一个是隐含的edx:eax,另一个是由后面的操作数表示的指针来指出。上面那条指令显然违反了这条规则。

2. lock前缀只能用于基于内存的“读—修改—写”型的指令,而上面的指令同样也不符合这一要求。

按理说,上面的任何一个错误都会导致invalid opcode的错误。可是意外发生了,当这两种错误按照上面的方式叠加到一起时,CPU会自己锁死!解决方法只有重启!换句话说,我们可以构造这么一个短小的C程序来触发这个bug:

char x [5] = { 0xf0, 0x0f, 0xc7, 0xc8 };

int main(void){
void (*f)() = x;
f();
return 0;
}

而且不需要任何特权,任何用户都能使用!这是头一次“崭露头角”的 硬件错误,操作系统对此也无可奈何。这样,我们可以轻易让某台配有此bug的CPU的服务器挂掉!由此可见,这个bug有多么严重!

这到底是怎么回事?为什么本该是错上加错的指令却酿成大祸?其实是这样,当CPU发现cmpxchg8b %eax是错误的之后,会产生一个invalid opcode的异常,然后寻找其对应的处理函数来处理。当CPU读取这个处理函数的地址时,错误地判断出了LOCK#信号,就对总线进行加锁,然后等待一个对该地址的加锁的写入,但在这中间,CPU是不会有任何写操作的,于是就把自己挂起了!这明显是一个设计上的失误!

Intel老大马上给出了两套解决方案,都可行,但第一个不怎么聪明,甚至说“和问题本身一样糟糕”;而另一个算是一个聪明的方案。我们一起来看一下第二个,也就是Linux内核使用的这个。

Intel这样建议:把包含IDT前7项的页面设置为只读,也把CR0的 WP位置为1。现在,当bug发生时,它先会找invalid opcode的异常处理函数,进而产生一个缺页异常,因为CPU企图写一个只读页面。而缺页异常不会被锁住,这样控制权又回到操作系统手中。然后就是要修改缺页异常的处理例程,让它判断,如果异常发生在内核态并且无效地址正好是invalid opcode异常处理函数的地址,那么就是f00f bug了。这时操作系统就应该转入处理invalid opcode异常。

下面就是Linux内核源代码中的 实际处理方法:

这是把IDT放到固定映射区域的代码,很明显,设置了只读。
(arch/i386/kernel/traps.c)

void init trap_init_f00f_bug(void)
{
set_fixmap(FIX_F00F_IDT, __pa(&idt_table), PAGE_KERNEL_RO);

/*
 * Update the IDT descriptor and reload the IDT so that
 * it uses the read-only mapped virtual address.
 */
idt_descr.address = fix_to_virt(FIX_F00F_IDT);
load_idt(&idt_descr);

}

这里是do_page_fault(),也就是缺页异常处理函数中的部分代码:
(arch/i386/mm/fault.c)

//…
if (boot_cpu_data.f00f_bug) {
unsigned long nr;

    nr = (address - idt_descr.address) >> 3;

    if (nr == 6) {
        do_invalid_op(regs, 0);
        return;
    }
}

//…

和上面的Intel的方案完全吻合!

参考资料:

[1] Wikipedia, http://en.wikipedia.org/wiki/F00f
[2] x86.org, http://www.x86.org/errata/dec97/f00fbug.htm
[3] Understanding the Linux Kernel
[4] Intel 64 and IA-32 Architectures Software Developer’s Manual, Volume 3A