Commit 44d066a2 authored by Paolo Bonzini's avatar Paolo Bonzini
Browse files

target-i386: check for PKU even for non-writable pages



Xiao Guangrong ran kvm-unit-tests on an actual machine with PKU and
found that it fails:

test pte.p pte.user pde.p pde.user pde.a pde.pse pkru.wd pkey=1 user write efer.nx cr4.pke: FAIL: error code 27 expected 7
Dump mapping: address: 0x123400000000
------L4: 2ebe007
------L3: 2ebf007
------L2: 8000000020000a5

(All failures are combinations of "pde.user pde.p pkru.wd pkey=1",
plus either "pde.pse" or "pte.p pte.user", plus one of "user cr0.wp",
"cr0.wp" or "user", plus unimportant bits such as accessed/dirty or
efer.nx).

So PFEC.PKEY is set even if the ordinary check failed (which it did
because pde.w is zero).  Adjust QEMU to match behavior of silicon.

Signed-off-by: default avatarPaolo Bonzini <pbonzini@redhat.com>
parent 57a6c059
Loading
Loading
Loading
Loading
+10 −8
Original line number Diff line number Diff line
@@ -919,29 +919,31 @@ do_check_protect_pse36:
         !((env->cr[4] & CR4_SMEP_MASK) && (ptep & PG_USER_MASK)))) {
        prot |= PAGE_EXEC;
    }

    if ((prot & (1 << is_write1)) == 0) {
        goto do_fault_protect;
    }

    if ((env->cr[4] & CR4_PKE_MASK) && (env->hflags & HF_LMA_MASK) &&
        (ptep & PG_USER_MASK) && env->pkru) {
        uint32_t pk = (pte & PG_PKRU_MASK) >> PG_PKRU_BIT;
        uint32_t pkru_ad = (env->pkru >> pk * 2) & 1;
        uint32_t pkru_wd = (env->pkru >> pk * 2) & 2;
        uint32_t pkru_prot = PAGE_READ | PAGE_WRITE | PAGE_EXEC;

        if (pkru_ad) {
            prot &= ~(PAGE_READ | PAGE_WRITE);
            pkru_prot &= ~(PAGE_READ | PAGE_WRITE);
        } else if (pkru_wd && (is_user || env->cr[0] & CR0_WP_MASK)) {
            prot &= ~PAGE_WRITE;
            pkru_prot &= ~PAGE_WRITE;
        }
        if ((prot & (1 << is_write1)) == 0) {

        prot &= pkru_prot;
        if ((pkru_prot & (1 << is_write1)) == 0) {
            assert(is_write1 != 2);
            error_code |= PG_ERROR_PK_MASK;
            goto do_fault_protect;
        }
    }

    if ((prot & (1 << is_write1)) == 0) {
        goto do_fault_protect;
    }

    /* yes, it can! */
    is_dirty = is_write && !(pte & PG_DIRTY_MASK);
    if (!(pte & PG_ACCESSED_MASK) || is_dirty) {