Skip to content

Commit 1494e0c

Browse files
Steven Pricetorvalds
authored andcommitted
x86: mm: ptdump: calculate effective permissions correctly
Patch series "Fix W+X debug feature on x86" Jan alerted me[1] that the W+X detection debug feature was broken in x86 by my change[2] to switch x86 to use the generic ptdump infrastructure. Fundamentally the approach of trying to move the calculation of effective permissions into note_page() was broken because note_page() is only called for 'leaf' entries and the effective permissions are passed down via the internal nodes of the page tree. The solution I've taken here is to create a new (optional) callback which is called for all nodes of the page tree and therefore can calculate the effective permissions. Secondly on some configurations (32 bit with PAE) "unsigned long" is not large enough to store the table entries. The fix here is simple - let's just use a u64. [1] https://lore.kernel.org/lkml/[email protected]/ [2] 2ae2713 ("x86: mm: convert dump_pagetables to use walk_page_range") This patch (of 2): By switching the x86 page table dump code to use the generic code the effective permissions are no longer calculated correctly because the note_page() function is only called for *leaf* entries. To calculate the actual effective permissions it is necessary to observe the full hierarchy of the page tree. Introduce a new callback for ptdump which is called for every entry and can therefore update the prot_levels array correctly. note_page() can then simply access the appropriate element in the array. [[email protected]: make the assignment conditional on val != 0] Link: http://lkml.kernel.org/r/[email protected] Fixes: 2ae2713 ("x86: mm: convert dump_pagetables to use walk_page_range") Reported-by: Jan Beulich <[email protected]> Signed-off-by: Steven Price <[email protected]> Signed-off-by: Andrew Morton <[email protected]> Cc: Qian Cai <[email protected]> Cc: Andy Lutomirski <[email protected]> Cc: Borislav Petkov <[email protected]> Cc: Dave Hansen <[email protected]> Cc: Ingo Molnar <[email protected]> Cc: Peter Zijlstra <[email protected]> Cc: Thomas Gleixner <[email protected]> Cc: <[email protected]> Link: http://lkml.kernel.org/r/[email protected] Link: http://lkml.kernel.org/r/[email protected] Signed-off-by: Linus Torvalds <[email protected]>
1 parent 50d53d7 commit 1494e0c

File tree

3 files changed

+37
-14
lines changed

3 files changed

+37
-14
lines changed

arch/x86/mm/dump_pagetables.c

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -249,10 +249,22 @@ static void note_wx(struct pg_state *st, unsigned long addr)
249249
(void *)st->start_address);
250250
}
251251

252-
static inline pgprotval_t effective_prot(pgprotval_t prot1, pgprotval_t prot2)
252+
static void effective_prot(struct ptdump_state *pt_st, int level, u64 val)
253253
{
254-
return (prot1 & prot2 & (_PAGE_USER | _PAGE_RW)) |
255-
((prot1 | prot2) & _PAGE_NX);
254+
struct pg_state *st = container_of(pt_st, struct pg_state, ptdump);
255+
pgprotval_t prot = val & PTE_FLAGS_MASK;
256+
pgprotval_t effective;
257+
258+
if (level > 0) {
259+
pgprotval_t higher_prot = st->prot_levels[level - 1];
260+
261+
effective = (higher_prot & prot & (_PAGE_USER | _PAGE_RW)) |
262+
((higher_prot | prot) & _PAGE_NX);
263+
} else {
264+
effective = prot;
265+
}
266+
267+
st->prot_levels[level] = effective;
256268
}
257269

258270
/*
@@ -270,16 +282,10 @@ static void note_page(struct ptdump_state *pt_st, unsigned long addr, int level,
270282
struct seq_file *m = st->seq;
271283

272284
new_prot = val & PTE_FLAGS_MASK;
273-
274-
if (level > 0) {
275-
new_eff = effective_prot(st->prot_levels[level - 1],
276-
new_prot);
277-
} else {
278-
new_eff = new_prot;
279-
}
280-
281-
if (level >= 0)
282-
st->prot_levels[level] = new_eff;
285+
if (!val)
286+
new_eff = 0;
287+
else
288+
new_eff = st->prot_levels[level];
283289

284290
/*
285291
* If we have a "break" in the series, we need to flush the state that
@@ -374,6 +380,7 @@ static void ptdump_walk_pgd_level_core(struct seq_file *m,
374380
struct pg_state st = {
375381
.ptdump = {
376382
.note_page = note_page,
383+
.effective_prot = effective_prot,
377384
.range = ptdump_ranges
378385
},
379386
.level = -1,

include/linux/ptdump.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ struct ptdump_state {
1414
/* level is 0:PGD to 4:PTE, or -1 if unknown */
1515
void (*note_page)(struct ptdump_state *st, unsigned long addr,
1616
int level, unsigned long val);
17+
void (*effective_prot)(struct ptdump_state *st, int level, u64 val);
1718
const struct ptdump_range *range;
1819
};
1920

mm/ptdump.c

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ static int ptdump_pgd_entry(pgd_t *pgd, unsigned long addr,
3636
return note_kasan_page_table(walk, addr);
3737
#endif
3838

39+
if (st->effective_prot)
40+
st->effective_prot(st, 0, pgd_val(val));
41+
3942
if (pgd_leaf(val))
4043
st->note_page(st, addr, 0, pgd_val(val));
4144

@@ -53,6 +56,9 @@ static int ptdump_p4d_entry(p4d_t *p4d, unsigned long addr,
5356
return note_kasan_page_table(walk, addr);
5457
#endif
5558

59+
if (st->effective_prot)
60+
st->effective_prot(st, 1, p4d_val(val));
61+
5662
if (p4d_leaf(val))
5763
st->note_page(st, addr, 1, p4d_val(val));
5864

@@ -70,6 +76,9 @@ static int ptdump_pud_entry(pud_t *pud, unsigned long addr,
7076
return note_kasan_page_table(walk, addr);
7177
#endif
7278

79+
if (st->effective_prot)
80+
st->effective_prot(st, 2, pud_val(val));
81+
7382
if (pud_leaf(val))
7483
st->note_page(st, addr, 2, pud_val(val));
7584

@@ -87,6 +96,8 @@ static int ptdump_pmd_entry(pmd_t *pmd, unsigned long addr,
8796
return note_kasan_page_table(walk, addr);
8897
#endif
8998

99+
if (st->effective_prot)
100+
st->effective_prot(st, 3, pmd_val(val));
90101
if (pmd_leaf(val))
91102
st->note_page(st, addr, 3, pmd_val(val));
92103

@@ -97,8 +108,12 @@ static int ptdump_pte_entry(pte_t *pte, unsigned long addr,
97108
unsigned long next, struct mm_walk *walk)
98109
{
99110
struct ptdump_state *st = walk->private;
111+
pte_t val = READ_ONCE(*pte);
112+
113+
if (st->effective_prot)
114+
st->effective_prot(st, 4, pte_val(val));
100115

101-
st->note_page(st, addr, 4, pte_val(READ_ONCE(*pte)));
116+
st->note_page(st, addr, 4, pte_val(val));
102117

103118
return 0;
104119
}

0 commit comments

Comments
 (0)