Thread (178 messages) 178 messages, 11 authors, 2022-06-06

Re: [PATCH Part2 RFC v4 09/40] x86/fault: Add support to dump RMP entry on fault

From: Dave Hansen <hidden>
Date: 2021-07-08 15:30:15
Also in: kvm, linux-coco, linux-crypto, linux-efi, linux-mm, platform-driver-x86

On 7/8/21 8:02 AM, Brijesh Singh wrote:
...
quoted
quoted
+    pgd = __va(read_cr3_pa());
+    pgd += pgd_index(address);
+
+    pte = lookup_address_in_pgd(pgd, address, &level);
+    if (unlikely(!pte))
+        return;
It's a little annoying this is doing *another* separate page walk.
Don't we already do this for dumping the page tables themselves at oops
time?
Yes, we already do the walk in oops function, I'll extend the
dump_rmpentry() to use the level from the oops to avoid the duplicate walk.
I was even thinking that you could use the pmd/pte entries that come
from the walk in dump_pagetable().

BTW, I think the snp_lookup_page_in_rmptable() interface is probably
wrong.  It takes a 'struct page':

+struct rmpentry *snp_lookup_page_in_rmptable(struct page *page, int *level)

but then immediately converts it to a paddr:
+	unsigned long phys = page_to_pfn(page) << PAGE_SHIFT;
If you just had it take a paddr, you wouldn't have to mess with all of
this pfn_valid() and phys_to_page() error checking.
quoted
quoted
+    case PG_LEVEL_2M: {
+        pfn = pmd_pfn(*(pmd_t *)pte);
+        break;
+    }
+    case PG_LEVEL_1G: {
+        pfn = pud_pfn(*(pud_t *)pte);
+        break;
+    }
+    case PG_LEVEL_512G: {
+        pfn = p4d_pfn(*(p4d_t *)pte);
+        break;
+    }
+    default:
+        return;
+    }
+
+    e = snp_lookup_page_in_rmptable(pfn_to_page(pfn), &level);
So, lookup_address_in_pgd() looks to me like it will return pretty
random page table entries as long as the entry isn't
p{gd,4d,ud,md,te}_none().  It can certainly return !p*_present()
entries.  Those are *NOT* safe to call pfn_to_page() on.
I will add some checks to make sure that we are accessing only safe pfn's.
Or fix the snp_lookup_page_in_rmptable() interface, please.
quoted
quoted
+    if (rmpentry_assigned(e)) {
+        pr_alert("RMPEntry paddr 0x%lx [assigned=%d immutable=%d
pagesize=%d gpa=0x%lx"
+            " asid=%d vmsa=%d validated=%d]\n", pfn << PAGE_SHIFT,
+            rmpentry_assigned(e), rmpentry_immutable(e),
rmpentry_pagesize(e),
+            rmpentry_gpa(e), rmpentry_asid(e), rmpentry_vmsa(e),
+            rmpentry_validated(e));
+
+        pr_alert("RMPEntry paddr 0x%lx %016llx %016llx\n", pfn <<
PAGE_SHIFT,
+            e->high, e->low);
Could you please include an entire oops in the changelog that also
includes this information?  It would be really nice if this was at least
consistent in style to the stuff around it.
Here is one example: (in this case page was immutable and HV attempted
to write to it).

BUG: unable to handle page fault for address: ffff98c78ee00000
#PF: supervisor write access in kernel mode
#PF: error_code(0x80000003) - rmp violation
Let's capitalize "RMP" here, please.
PGD 304b201067 P4D 304b201067 PUD 20c7f06063 PMD 20c8976063 PTE
80000020cee00163
RMPEntry paddr 0x20cee00000 [assigned=1 immutable=1 pagesize=0 gpa=0x0
asid=0 vmsa=0 validated=0]
RMPEntry paddr 0x20cee00000 000000000000000f 8000000000000ffd
That's a good example, thanks!

But, it does make me think that we shouldn't be spitting out
"immutable".  Should we call it "readonly" or something so that folks
have a better chance of figuring out what's wrong?  Even better, should
we be looking specifically for X86_PF_RMP *and* immutable=1 and spitting
out something in english about it?

This also *looks* to be spitting out the same "RMPEntry paddr
0x20cee00000" more than once.  Maybe we should just indent the extra
entries instead of repeating things.  The high/low are missing a "0x"
prefix, they also don't have any kind of text label.
quoted
Also, how much of this stuff like rmpentry_asid() is duplicated in the
"raw" dump of e->high and e->low?
Most of the rmpentry_xxx assessors read the e->low. The RMP entry is a
16-bytes. AMD APM defines only a few bits and keeps everything else
reserved. We are in the process of updating APM to document few more
bits. I am not adding assessors for the undocumented fields. Until then,
we dump the entire 16-bytes.

I agree that we are duplicating the information. I can live with just a
raw dump. That means anyone who is debugging the crash will look at the
APM to decode the fields.
I actually really like processing the fields.  I think it's a good
investment to make the error messages as self-documenting as possible
and not require the poor souls who are decoding oopses to also keep each
vendor's architecture manuals at hand.
quoted
This also needs a comment about *WHY* this case is looking at a 2MB
region.
Actually the comment above says why we are looking for the 2MB region.
Let me rearrange the comment block so that its more clear.

The reason for iterating through 2MB region is; if the faulting address
is not assigned in the RMP table, and page table walk level is 2MB then
one of entry within the large page is the root cause of the fault. Since
we don't know which entry hence I dump all the non-zero entries.
Logically you can figure this out though, right?  Why throw 511 entries
at the console when we *know* they're useless?
quoted
quoted
+        pfn_end = pfn + PTRS_PER_PMD;
+
+        while (pfn < pfn_end) {
+            e = snp_lookup_page_in_rmptable(pfn_to_page(pfn), &level);
+
+            if (unlikely(!e))
+                return;
+
+            if (e->low || e->high)
+                pr_alert("RMPEntry paddr 0x%lx: %016llx %016llx\n",
+                    pfn << PAGE_SHIFT, e->high, e->low);
Why does this dump "raw" RMP entries while the above stuff filters them
through a bunch of helper macros?
There are two cases which we need to consider:

1) the faulting page is a guest private (aka assigned)
2) the faulting page is a hypervisor (aka shared)

We will be primarily seeing #1. In this case, we know its a assigned
page, and we can decode the fields.

The #2 will happen in rare conditions,
What rare conditions?
if it happens, one of the undocumented bit in the RMP entry can
provide us some useful information hence we dump the raw values.
You're saying that there are things that can cause RMP faults that
aren't documented?  That's rather nasty for your users, don't you think?

I'd be fine if you want to define a mask of unknown bits and spit out to
the users that some unknown bits are set.
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help