2013-10-23 19:13:02 +04:00
/*
* Debug helper to dump the current kernel pagetables of the system
* so that we can see what the various memory ranges are set to .
*
* Derived from x86 implementation :
* ( C ) Copyright 2008 Intel Corporation
*
* Author : Arjan van de Ven < arjan @ linux . intel . com >
*
* This program is free software ; you can redistribute it and / or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation ; version 2
* of the License .
*/
# include <linux/debugfs.h>
# include <linux/fs.h>
# include <linux/mm.h>
# include <linux/seq_file.h>
2017-03-25 02:51:32 +03:00
# include <asm/domain.h>
2013-10-23 19:13:02 +04:00
# include <asm/fixmap.h>
2017-01-29 19:31:32 +03:00
# include <asm/memory.h>
2013-10-23 19:13:02 +04:00
# include <asm/pgtable.h>
2017-12-12 03:41:09 +03:00
# include <asm/ptdump.h>
2013-10-23 19:13:02 +04:00
static struct addr_marker address_markers [ ] = {
{ MODULES_VADDR , " Modules " } ,
{ PAGE_OFFSET , " Kernel Mapping " } ,
{ 0 , " vmalloc() Area " } ,
{ VMALLOC_END , " vmalloc() End " } ,
{ FIXADDR_START , " Fixmap Area " } ,
2017-01-29 19:31:32 +03:00
{ VECTORS_BASE , " Vectors " } ,
{ VECTORS_BASE + PAGE_SIZE * 2 , " Vectors End " } ,
2013-10-23 19:13:02 +04:00
{ - 1 , NULL } ,
} ;
2017-12-12 03:42:25 +03:00
# define pt_dump_seq_printf(m, fmt, args...) \
( { \
if ( m ) \
seq_printf ( m , fmt , # # args ) ; \
} )
# define pt_dump_seq_puts(m, fmt) \
( { \
if ( m ) \
seq_printf ( m , fmt ) ; \
} )
2013-10-23 19:13:02 +04:00
struct pg_state {
struct seq_file * seq ;
const struct addr_marker * marker ;
unsigned long start_address ;
unsigned level ;
u64 current_prot ;
2017-12-12 03:43:57 +03:00
bool check_wx ;
unsigned long wx_pages ;
2017-03-25 02:51:32 +03:00
const char * current_domain ;
2013-10-23 19:13:02 +04:00
} ;
struct prot_bits {
u64 mask ;
u64 val ;
const char * set ;
const char * clear ;
2017-12-12 03:43:57 +03:00
bool ro_bit ;
bool nx_bit ;
2013-10-23 19:13:02 +04:00
} ;
static const struct prot_bits pte_bits [ ] = {
{
. mask = L_PTE_USER ,
. val = L_PTE_USER ,
. set = " USR " ,
. clear = " " ,
} , {
. mask = L_PTE_RDONLY ,
. val = L_PTE_RDONLY ,
. set = " ro " ,
. clear = " RW " ,
2017-12-12 03:43:57 +03:00
. ro_bit = true ,
2013-10-23 19:13:02 +04:00
} , {
. mask = L_PTE_XN ,
. val = L_PTE_XN ,
. set = " NX " ,
. clear = " x " ,
2017-12-12 03:43:57 +03:00
. nx_bit = true ,
2013-10-23 19:13:02 +04:00
} , {
. mask = L_PTE_SHARED ,
. val = L_PTE_SHARED ,
. set = " SHD " ,
. clear = " " ,
} , {
. mask = L_PTE_MT_MASK ,
. val = L_PTE_MT_UNCACHED ,
. set = " SO/UNCACHED " ,
} , {
. mask = L_PTE_MT_MASK ,
. val = L_PTE_MT_BUFFERABLE ,
. set = " MEM/BUFFERABLE/WC " ,
} , {
. mask = L_PTE_MT_MASK ,
. val = L_PTE_MT_WRITETHROUGH ,
. set = " MEM/CACHED/WT " ,
} , {
. mask = L_PTE_MT_MASK ,
. val = L_PTE_MT_WRITEBACK ,
. set = " MEM/CACHED/WBRA " ,
# ifndef CONFIG_ARM_LPAE
} , {
. mask = L_PTE_MT_MASK ,
. val = L_PTE_MT_MINICACHE ,
. set = " MEM/MINICACHE " ,
# endif
} , {
. mask = L_PTE_MT_MASK ,
. val = L_PTE_MT_WRITEALLOC ,
. set = " MEM/CACHED/WBWA " ,
} , {
. mask = L_PTE_MT_MASK ,
. val = L_PTE_MT_DEV_SHARED ,
. set = " DEV/SHARED " ,
# ifndef CONFIG_ARM_LPAE
} , {
. mask = L_PTE_MT_MASK ,
. val = L_PTE_MT_DEV_NONSHARED ,
. set = " DEV/NONSHARED " ,
# endif
} , {
. mask = L_PTE_MT_MASK ,
. val = L_PTE_MT_DEV_WC ,
. set = " DEV/WC " ,
} , {
. mask = L_PTE_MT_MASK ,
. val = L_PTE_MT_DEV_CACHED ,
. set = " DEV/CACHED " ,
} ,
} ;
static const struct prot_bits section_bits [ ] = {
2014-04-01 01:20:34 +04:00
# ifdef CONFIG_ARM_LPAE
{
. mask = PMD_SECT_USER ,
. val = PMD_SECT_USER ,
. set = " USR " ,
} , {
2017-11-14 02:55:26 +03:00
. mask = L_PMD_SECT_RDONLY | PMD_SECT_AP2 ,
. val = L_PMD_SECT_RDONLY | PMD_SECT_AP2 ,
2014-04-01 01:20:34 +04:00
. set = " ro " ,
. clear = " RW " ,
2017-12-12 03:43:57 +03:00
. ro_bit = true ,
2014-04-01 01:20:34 +04:00
# elif __LINUX_ARM_ARCH__ >= 6
2013-10-23 19:13:02 +04:00
{
2014-04-01 01:20:34 +04:00
. mask = PMD_SECT_APX | PMD_SECT_AP_READ | PMD_SECT_AP_WRITE ,
. val = PMD_SECT_APX | PMD_SECT_AP_WRITE ,
2013-10-23 19:13:02 +04:00
. set = " ro " ,
2017-12-12 03:43:57 +03:00
. ro_bit = true ,
2013-10-23 19:13:02 +04:00
} , {
2014-04-01 01:20:34 +04:00
. mask = PMD_SECT_APX | PMD_SECT_AP_READ | PMD_SECT_AP_WRITE ,
2013-10-23 19:13:02 +04:00
. val = PMD_SECT_AP_WRITE ,
. set = " RW " ,
} , {
2014-04-01 01:20:34 +04:00
. mask = PMD_SECT_APX | PMD_SECT_AP_READ | PMD_SECT_AP_WRITE ,
2013-10-23 19:13:02 +04:00
. val = PMD_SECT_AP_READ ,
. set = " USR ro " ,
} , {
2014-04-01 01:20:34 +04:00
. mask = PMD_SECT_APX | PMD_SECT_AP_READ | PMD_SECT_AP_WRITE ,
2013-10-23 19:13:02 +04:00
. val = PMD_SECT_AP_READ | PMD_SECT_AP_WRITE ,
. set = " USR RW " ,
2014-04-01 01:20:34 +04:00
# else /* ARMv4/ARMv5 */
/* These are approximate */
2013-10-23 19:13:02 +04:00
{
2014-04-01 01:20:34 +04:00
. mask = PMD_SECT_AP_READ | PMD_SECT_AP_WRITE ,
. val = 0 ,
. set = " ro " ,
2017-12-12 03:43:57 +03:00
. ro_bit = true ,
2013-10-23 19:13:02 +04:00
} , {
2014-04-01 01:20:34 +04:00
. mask = PMD_SECT_AP_READ | PMD_SECT_AP_WRITE ,
. val = PMD_SECT_AP_WRITE ,
. set = " RW " ,
} , {
. mask = PMD_SECT_AP_READ | PMD_SECT_AP_WRITE ,
. val = PMD_SECT_AP_READ ,
. set = " USR ro " ,
} , {
. mask = PMD_SECT_AP_READ | PMD_SECT_AP_WRITE ,
. val = PMD_SECT_AP_READ | PMD_SECT_AP_WRITE ,
. set = " USR RW " ,
2013-10-23 19:13:02 +04:00
# endif
} , {
. mask = PMD_SECT_XN ,
. val = PMD_SECT_XN ,
. set = " NX " ,
. clear = " x " ,
2017-12-12 03:43:57 +03:00
. nx_bit = true ,
2013-10-23 19:13:02 +04:00
} , {
. mask = PMD_SECT_S ,
. val = PMD_SECT_S ,
. set = " SHD " ,
. clear = " " ,
} ,
} ;
struct pg_level {
const struct prot_bits * bits ;
size_t num ;
u64 mask ;
2017-12-12 03:43:57 +03:00
const struct prot_bits * ro_bit ;
const struct prot_bits * nx_bit ;
2013-10-23 19:13:02 +04:00
} ;
static struct pg_level pg_level [ ] = {
{
} , { /* pgd */
} , { /* pud */
} , { /* pmd */
. bits = section_bits ,
. num = ARRAY_SIZE ( section_bits ) ,
} , { /* pte */
. bits = pte_bits ,
. num = ARRAY_SIZE ( pte_bits ) ,
} ,
} ;
static void dump_prot ( struct pg_state * st , const struct prot_bits * bits , size_t num )
{
unsigned i ;
for ( i = 0 ; i < num ; i + + , bits + + ) {
const char * s ;
if ( ( st - > current_prot & bits - > mask ) = = bits - > val )
s = bits - > set ;
else
s = bits - > clear ;
if ( s )
2017-12-12 03:42:25 +03:00
pt_dump_seq_printf ( st - > seq , " %s " , s ) ;
2013-10-23 19:13:02 +04:00
}
}
2017-12-12 03:43:57 +03:00
static void note_prot_wx ( struct pg_state * st , unsigned long addr )
{
if ( ! st - > check_wx )
return ;
if ( ( st - > current_prot & pg_level [ st - > level ] . ro_bit - > mask ) = =
pg_level [ st - > level ] . ro_bit - > val )
return ;
if ( ( st - > current_prot & pg_level [ st - > level ] . nx_bit - > mask ) = =
pg_level [ st - > level ] . nx_bit - > val )
return ;
WARN_ONCE ( 1 , " arm/mm: Found insecure W+X mapping at address %pS \n " ,
( void * ) st - > start_address ) ;
st - > wx_pages + = ( addr - st - > start_address ) / PAGE_SIZE ;
}
2017-03-25 02:51:32 +03:00
static void note_page ( struct pg_state * st , unsigned long addr ,
unsigned int level , u64 val , const char * domain )
2013-10-23 19:13:02 +04:00
{
static const char units [ ] = " KMGTPE " ;
u64 prot = val & pg_level [ level ] . mask ;
if ( ! st - > level ) {
st - > level = level ;
st - > current_prot = prot ;
2017-03-25 02:51:32 +03:00
st - > current_domain = domain ;
2017-12-12 03:42:25 +03:00
pt_dump_seq_printf ( st - > seq , " ---[ %s ]--- \n " , st - > marker - > name ) ;
2013-10-23 19:13:02 +04:00
} else if ( prot ! = st - > current_prot | | level ! = st - > level | |
2017-03-25 02:51:32 +03:00
domain ! = st - > current_domain | |
2013-10-23 19:13:02 +04:00
addr > = st - > marker [ 1 ] . start_address ) {
const char * unit = units ;
unsigned long delta ;
if ( st - > current_prot ) {
2017-12-12 03:43:57 +03:00
note_prot_wx ( st , addr ) ;
2017-12-12 03:42:25 +03:00
pt_dump_seq_printf ( st - > seq , " 0x%08lx-0x%08lx " ,
2013-10-23 19:13:02 +04:00
st - > start_address , addr ) ;
delta = ( addr - st - > start_address ) > > 10 ;
while ( ! ( delta & 1023 ) & & unit [ 1 ] ) {
delta > > = 10 ;
unit + + ;
}
2017-12-12 03:42:25 +03:00
pt_dump_seq_printf ( st - > seq , " %9lu%c " , delta , * unit ) ;
2017-03-25 02:51:32 +03:00
if ( st - > current_domain )
2017-12-12 03:42:25 +03:00
pt_dump_seq_printf ( st - > seq , " %s " ,
st - > current_domain ) ;
2013-10-23 19:13:02 +04:00
if ( pg_level [ st - > level ] . bits )
dump_prot ( st , pg_level [ st - > level ] . bits , pg_level [ st - > level ] . num ) ;
2017-12-12 03:42:25 +03:00
pt_dump_seq_printf ( st - > seq , " \n " ) ;
2013-10-23 19:13:02 +04:00
}
if ( addr > = st - > marker [ 1 ] . start_address ) {
st - > marker + + ;
2017-12-12 03:42:25 +03:00
pt_dump_seq_printf ( st - > seq , " ---[ %s ]--- \n " ,
st - > marker - > name ) ;
2013-10-23 19:13:02 +04:00
}
st - > start_address = addr ;
st - > current_prot = prot ;
2017-03-25 02:51:32 +03:00
st - > current_domain = domain ;
2013-10-23 19:13:02 +04:00
st - > level = level ;
}
}
2017-03-25 02:51:32 +03:00
static void walk_pte ( struct pg_state * st , pmd_t * pmd , unsigned long start ,
const char * domain )
2013-10-23 19:13:02 +04:00
{
pte_t * pte = pte_offset_kernel ( pmd , 0 ) ;
unsigned long addr ;
unsigned i ;
for ( i = 0 ; i < PTRS_PER_PTE ; i + + , pte + + ) {
addr = start + i * PAGE_SIZE ;
2017-03-25 02:51:32 +03:00
note_page ( st , addr , 4 , pte_val ( * pte ) , domain ) ;
2013-10-23 19:13:02 +04:00
}
}
2017-03-25 02:51:32 +03:00
static const char * get_domain_name ( pmd_t * pmd )
{
# ifndef CONFIG_ARM_LPAE
switch ( pmd_val ( * pmd ) & PMD_DOMAIN_MASK ) {
case PMD_DOMAIN ( DOMAIN_KERNEL ) :
return " KERNEL " ;
case PMD_DOMAIN ( DOMAIN_USER ) :
return " USER " ;
case PMD_DOMAIN ( DOMAIN_IO ) :
return " IO " ;
case PMD_DOMAIN ( DOMAIN_VECTORS ) :
return " VECTORS " ;
default :
return " unknown " ;
}
# endif
return NULL ;
}
2013-10-23 19:13:02 +04:00
static void walk_pmd ( struct pg_state * st , pud_t * pud , unsigned long start )
{
pmd_t * pmd = pmd_offset ( pud , 0 ) ;
unsigned long addr ;
unsigned i ;
2017-03-25 02:51:32 +03:00
const char * domain ;
2013-10-23 19:13:02 +04:00
for ( i = 0 ; i < PTRS_PER_PMD ; i + + , pmd + + ) {
addr = start + i * PMD_SIZE ;
2017-03-25 02:51:32 +03:00
domain = get_domain_name ( pmd ) ;
2013-10-23 19:13:02 +04:00
if ( pmd_none ( * pmd ) | | pmd_large ( * pmd ) | | ! pmd_present ( * pmd ) )
2017-03-25 02:51:32 +03:00
note_page ( st , addr , 3 , pmd_val ( * pmd ) , domain ) ;
2013-10-23 19:13:02 +04:00
else
2017-03-25 02:51:32 +03:00
walk_pte ( st , pmd , addr , domain ) ;
2014-02-14 23:19:03 +04:00
2017-03-25 02:51:32 +03:00
if ( SECTION_SIZE < PMD_SIZE & & pmd_large ( pmd [ 1 ] ) ) {
addr + = SECTION_SIZE ;
pmd + + ;
domain = get_domain_name ( pmd ) ;
note_page ( st , addr , 3 , pmd_val ( * pmd ) , domain ) ;
}
2013-10-23 19:13:02 +04:00
}
}
static void walk_pud ( struct pg_state * st , pgd_t * pgd , unsigned long start )
{
pud_t * pud = pud_offset ( pgd , 0 ) ;
unsigned long addr ;
unsigned i ;
for ( i = 0 ; i < PTRS_PER_PUD ; i + + , pud + + ) {
addr = start + i * PUD_SIZE ;
if ( ! pud_none ( * pud ) ) {
walk_pmd ( st , pud , addr ) ;
} else {
2017-03-25 02:51:32 +03:00
note_page ( st , addr , 2 , pud_val ( * pud ) , NULL ) ;
2013-10-23 19:13:02 +04:00
}
}
}
2017-12-12 03:41:09 +03:00
static void walk_pgd ( struct pg_state * st , struct mm_struct * mm ,
unsigned long start )
2013-10-23 19:13:02 +04:00
{
2017-12-12 03:41:09 +03:00
pgd_t * pgd = pgd_offset ( mm , 0UL ) ;
2014-12-17 19:57:38 +03:00
unsigned i ;
2017-12-12 03:41:09 +03:00
unsigned long addr ;
2013-10-23 19:13:02 +04:00
2014-12-17 19:57:38 +03:00
for ( i = 0 ; i < PTRS_PER_PGD ; i + + , pgd + + ) {
2017-12-12 03:41:09 +03:00
addr = start + i * PGDIR_SIZE ;
2013-10-23 19:13:02 +04:00
if ( ! pgd_none ( * pgd ) ) {
2017-12-12 03:41:09 +03:00
walk_pud ( st , pgd , addr ) ;
2013-10-23 19:13:02 +04:00
} else {
2017-12-12 03:41:09 +03:00
note_page ( st , addr , 1 , pgd_val ( * pgd ) , NULL ) ;
2013-10-23 19:13:02 +04:00
}
}
}
2017-12-12 03:41:09 +03:00
void ptdump_walk_pgd ( struct seq_file * m , struct ptdump_info * info )
2013-10-23 19:13:02 +04:00
{
2017-12-12 03:41:09 +03:00
struct pg_state st = {
. seq = m ,
. marker = info - > markers ,
2017-12-12 03:43:57 +03:00
. check_wx = false ,
2017-12-12 03:41:09 +03:00
} ;
2013-10-23 19:13:02 +04:00
2017-12-12 03:41:09 +03:00
walk_pgd ( & st , info - > mm , info - > base_addr ) ;
note_page ( & st , 0 , 0 , 0 , NULL ) ;
2013-10-23 19:13:02 +04:00
}
2017-12-12 03:41:09 +03:00
static void ptdump_initialize ( void )
2013-10-23 19:13:02 +04:00
{
unsigned i , j ;
for ( i = 0 ; i < ARRAY_SIZE ( pg_level ) ; i + + )
if ( pg_level [ i ] . bits )
2017-12-12 03:43:57 +03:00
for ( j = 0 ; j < pg_level [ i ] . num ; j + + ) {
2013-10-23 19:13:02 +04:00
pg_level [ i ] . mask | = pg_level [ i ] . bits [ j ] . mask ;
2017-12-12 03:43:57 +03:00
if ( pg_level [ i ] . bits [ j ] . ro_bit )
pg_level [ i ] . ro_bit = & pg_level [ i ] . bits [ j ] ;
if ( pg_level [ i ] . bits [ j ] . nx_bit )
pg_level [ i ] . nx_bit = & pg_level [ i ] . bits [ j ] ;
}
2013-10-23 19:13:02 +04:00
address_markers [ 2 ] . start_address = VMALLOC_START ;
2017-12-12 03:41:09 +03:00
}
2013-10-23 19:13:02 +04:00
2017-12-12 03:41:09 +03:00
static struct ptdump_info kernel_ptdump_info = {
. mm = & init_mm ,
. markers = address_markers ,
. base_addr = 0 ,
} ;
2017-12-12 03:43:57 +03:00
void ptdump_check_wx ( void )
{
struct pg_state st = {
. seq = NULL ,
. marker = ( struct addr_marker [ ] ) {
{ 0 , NULL } ,
{ - 1 , NULL } ,
} ,
. check_wx = true ,
} ;
walk_pgd ( & st , & init_mm , 0 ) ;
note_page ( & st , 0 , 0 , 0 , NULL ) ;
if ( st . wx_pages )
pr_warn ( " Checked W+X mappings: FAILED, %lu W+X pages found \n " ,
st . wx_pages ) ;
else
pr_info ( " Checked W+X mappings: passed, no W+X pages found \n " ) ;
}
2017-12-12 03:41:09 +03:00
static int ptdump_init ( void )
{
ptdump_initialize ( ) ;
return ptdump_debugfs_register ( & kernel_ptdump_info ,
" kernel_page_tables " ) ;
2013-10-23 19:13:02 +04:00
}
__initcall ( ptdump_init ) ;