dump.c 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414
  1. /*
  2. * Copyright (c) 2014, The Linux Foundation. All rights reserved.
  3. * Debug helper to dump the current kernel pagetables of the system
  4. * so that we can see what the various memory ranges are set to.
  5. *
  6. * Derived from x86 and arm implementation:
  7. * (C) Copyright 2008 Intel Corporation
  8. *
  9. * Author: Arjan van de Ven <arjan@linux.intel.com>
  10. *
  11. * This program is free software; you can redistribute it and/or
  12. * modify it under the terms of the GNU General Public License
  13. * as published by the Free Software Foundation; version 2
  14. * of the License.
  15. */
  16. #include <linux/debugfs.h>
  17. #include <linux/errno.h>
  18. #include <linux/fs.h>
  19. #include <linux/io.h>
  20. #include <linux/init.h>
  21. #include <linux/mm.h>
  22. #include <linux/sched.h>
  23. #include <linux/seq_file.h>
  24. #include <asm/fixmap.h>
  25. #include <asm/kasan.h>
  26. #include <asm/memory.h>
  27. #include <asm/pgtable.h>
  28. #include <asm/pgtable-hwdef.h>
  29. #include <asm/ptdump.h>
  30. static const struct addr_marker address_markers[] = {
  31. #ifdef CONFIG_KASAN
  32. { KASAN_SHADOW_START, "Kasan shadow start" },
  33. { KASAN_SHADOW_END, "Kasan shadow end" },
  34. #endif
  35. { MODULES_VADDR, "Modules start" },
  36. { MODULES_END, "Modules end" },
  37. { VMALLOC_START, "vmalloc() Area" },
  38. { VMALLOC_END, "vmalloc() End" },
  39. { FIXADDR_START, "Fixmap start" },
  40. { FIXADDR_TOP, "Fixmap end" },
  41. { PCI_IO_START, "PCI I/O start" },
  42. { PCI_IO_END, "PCI I/O end" },
  43. #ifdef CONFIG_SPARSEMEM_VMEMMAP
  44. { VMEMMAP_START, "vmemmap start" },
  45. { VMEMMAP_START + VMEMMAP_SIZE, "vmemmap end" },
  46. #endif
  47. { PAGE_OFFSET, "Linear Mapping" },
  48. { -1, NULL },
  49. };
  50. #define pt_dump_seq_printf(m, fmt, args...) \
  51. ({ \
  52. if (m) \
  53. seq_printf(m, fmt, ##args); \
  54. })
  55. #define pt_dump_seq_puts(m, fmt) \
  56. ({ \
  57. if (m) \
  58. seq_printf(m, fmt); \
  59. })
  60. /*
  61. * The page dumper groups page table entries of the same type into a single
  62. * description. It uses pg_state to track the range information while
  63. * iterating over the pte entries. When the continuity is broken it then
  64. * dumps out a description of the range.
  65. */
  66. struct pg_state {
  67. struct seq_file *seq;
  68. const struct addr_marker *marker;
  69. unsigned long start_address;
  70. unsigned level;
  71. u64 current_prot;
  72. bool check_wx;
  73. unsigned long wx_pages;
  74. unsigned long uxn_pages;
  75. };
  76. struct prot_bits {
  77. u64 mask;
  78. u64 val;
  79. const char *set;
  80. const char *clear;
  81. };
  82. static const struct prot_bits pte_bits[] = {
  83. {
  84. .mask = PTE_VALID,
  85. .val = PTE_VALID,
  86. .set = " ",
  87. .clear = "F",
  88. }, {
  89. .mask = PTE_USER,
  90. .val = PTE_USER,
  91. .set = "USR",
  92. .clear = " ",
  93. }, {
  94. .mask = PTE_RDONLY,
  95. .val = PTE_RDONLY,
  96. .set = "ro",
  97. .clear = "RW",
  98. }, {
  99. .mask = PTE_PXN,
  100. .val = PTE_PXN,
  101. .set = "NX",
  102. .clear = "x ",
  103. }, {
  104. .mask = PTE_SHARED,
  105. .val = PTE_SHARED,
  106. .set = "SHD",
  107. .clear = " ",
  108. }, {
  109. .mask = PTE_AF,
  110. .val = PTE_AF,
  111. .set = "AF",
  112. .clear = " ",
  113. }, {
  114. .mask = PTE_NG,
  115. .val = PTE_NG,
  116. .set = "NG",
  117. .clear = " ",
  118. }, {
  119. .mask = PTE_CONT,
  120. .val = PTE_CONT,
  121. .set = "CON",
  122. .clear = " ",
  123. }, {
  124. .mask = PTE_TABLE_BIT,
  125. .val = PTE_TABLE_BIT,
  126. .set = " ",
  127. .clear = "BLK",
  128. }, {
  129. .mask = PTE_UXN,
  130. .val = PTE_UXN,
  131. .set = "UXN",
  132. }, {
  133. .mask = PTE_ATTRINDX_MASK,
  134. .val = PTE_ATTRINDX(MT_DEVICE_nGnRnE),
  135. .set = "DEVICE/nGnRnE",
  136. }, {
  137. .mask = PTE_ATTRINDX_MASK,
  138. .val = PTE_ATTRINDX(MT_DEVICE_nGnRE),
  139. .set = "DEVICE/nGnRE",
  140. }, {
  141. .mask = PTE_ATTRINDX_MASK,
  142. .val = PTE_ATTRINDX(MT_DEVICE_GRE),
  143. .set = "DEVICE/GRE",
  144. }, {
  145. .mask = PTE_ATTRINDX_MASK,
  146. .val = PTE_ATTRINDX(MT_NORMAL_NC),
  147. .set = "MEM/NORMAL-NC",
  148. }, {
  149. .mask = PTE_ATTRINDX_MASK,
  150. .val = PTE_ATTRINDX(MT_NORMAL),
  151. .set = "MEM/NORMAL",
  152. }
  153. };
  154. struct pg_level {
  155. const struct prot_bits *bits;
  156. const char *name;
  157. size_t num;
  158. u64 mask;
  159. };
  160. static struct pg_level pg_level[] = {
  161. {
  162. }, { /* pgd */
  163. .name = "PGD",
  164. .bits = pte_bits,
  165. .num = ARRAY_SIZE(pte_bits),
  166. }, { /* pud */
  167. .name = (CONFIG_PGTABLE_LEVELS > 3) ? "PUD" : "PGD",
  168. .bits = pte_bits,
  169. .num = ARRAY_SIZE(pte_bits),
  170. }, { /* pmd */
  171. .name = (CONFIG_PGTABLE_LEVELS > 2) ? "PMD" : "PGD",
  172. .bits = pte_bits,
  173. .num = ARRAY_SIZE(pte_bits),
  174. }, { /* pte */
  175. .name = "PTE",
  176. .bits = pte_bits,
  177. .num = ARRAY_SIZE(pte_bits),
  178. },
  179. };
  180. static void dump_prot(struct pg_state *st, const struct prot_bits *bits,
  181. size_t num)
  182. {
  183. unsigned i;
  184. for (i = 0; i < num; i++, bits++) {
  185. const char *s;
  186. if ((st->current_prot & bits->mask) == bits->val)
  187. s = bits->set;
  188. else
  189. s = bits->clear;
  190. if (s)
  191. pt_dump_seq_printf(st->seq, " %s", s);
  192. }
  193. }
  194. static void note_prot_uxn(struct pg_state *st, unsigned long addr)
  195. {
  196. if (!st->check_wx)
  197. return;
  198. if ((st->current_prot & PTE_UXN) == PTE_UXN)
  199. return;
  200. WARN_ONCE(1, "arm64/mm: Found non-UXN mapping at address %p/%pS\n",
  201. (void *)st->start_address, (void *)st->start_address);
  202. st->uxn_pages += (addr - st->start_address) / PAGE_SIZE;
  203. }
  204. static void note_prot_wx(struct pg_state *st, unsigned long addr)
  205. {
  206. if (!st->check_wx)
  207. return;
  208. if ((st->current_prot & PTE_RDONLY) == PTE_RDONLY)
  209. return;
  210. if ((st->current_prot & PTE_PXN) == PTE_PXN)
  211. return;
  212. WARN_ONCE(1, "arm64/mm: Found insecure W+X mapping at address %p/%pS\n",
  213. (void *)st->start_address, (void *)st->start_address);
  214. st->wx_pages += (addr - st->start_address) / PAGE_SIZE;
  215. }
  216. static void note_page(struct pg_state *st, unsigned long addr, unsigned level,
  217. u64 val)
  218. {
  219. static const char units[] = "KMGTPE";
  220. u64 prot = val & pg_level[level].mask;
  221. if (!st->level) {
  222. st->level = level;
  223. st->current_prot = prot;
  224. st->start_address = addr;
  225. pt_dump_seq_printf(st->seq, "---[ %s ]---\n", st->marker->name);
  226. } else if (prot != st->current_prot || level != st->level ||
  227. addr >= st->marker[1].start_address) {
  228. const char *unit = units;
  229. unsigned long delta;
  230. if (st->current_prot) {
  231. note_prot_uxn(st, addr);
  232. note_prot_wx(st, addr);
  233. pt_dump_seq_printf(st->seq, "0x%016lx-0x%016lx ",
  234. st->start_address, addr);
  235. delta = (addr - st->start_address) >> 10;
  236. while (!(delta & 1023) && unit[1]) {
  237. delta >>= 10;
  238. unit++;
  239. }
  240. pt_dump_seq_printf(st->seq, "%9lu%c %s", delta, *unit,
  241. pg_level[st->level].name);
  242. if (pg_level[st->level].bits)
  243. dump_prot(st, pg_level[st->level].bits,
  244. pg_level[st->level].num);
  245. pt_dump_seq_puts(st->seq, "\n");
  246. }
  247. if (addr >= st->marker[1].start_address) {
  248. st->marker++;
  249. pt_dump_seq_printf(st->seq, "---[ %s ]---\n", st->marker->name);
  250. }
  251. st->start_address = addr;
  252. st->current_prot = prot;
  253. st->level = level;
  254. }
  255. if (addr >= st->marker[1].start_address) {
  256. st->marker++;
  257. pt_dump_seq_printf(st->seq, "---[ %s ]---\n", st->marker->name);
  258. }
  259. }
  260. static void walk_pte(struct pg_state *st, pmd_t *pmdp, unsigned long start)
  261. {
  262. pte_t *ptep = pte_offset_kernel(pmdp, 0UL);
  263. unsigned long addr;
  264. unsigned i;
  265. for (i = 0; i < PTRS_PER_PTE; i++, ptep++) {
  266. addr = start + i * PAGE_SIZE;
  267. note_page(st, addr, 4, READ_ONCE(pte_val(*ptep)));
  268. }
  269. }
  270. static void walk_pmd(struct pg_state *st, pud_t *pudp, unsigned long start)
  271. {
  272. pmd_t *pmdp = pmd_offset(pudp, 0UL);
  273. unsigned long addr;
  274. unsigned i;
  275. for (i = 0; i < PTRS_PER_PMD; i++, pmdp++) {
  276. pmd_t pmd = READ_ONCE(*pmdp);
  277. addr = start + i * PMD_SIZE;
  278. if (pmd_none(pmd) || pmd_sect(pmd)) {
  279. note_page(st, addr, 3, pmd_val(pmd));
  280. } else {
  281. BUG_ON(pmd_bad(pmd));
  282. walk_pte(st, pmdp, addr);
  283. }
  284. }
  285. }
  286. static void walk_pud(struct pg_state *st, pgd_t *pgdp, unsigned long start)
  287. {
  288. pud_t *pudp = pud_offset(pgdp, 0UL);
  289. unsigned long addr;
  290. unsigned i;
  291. for (i = 0; i < PTRS_PER_PUD; i++, pudp++) {
  292. pud_t pud = READ_ONCE(*pudp);
  293. addr = start + i * PUD_SIZE;
  294. if (pud_none(pud) || pud_sect(pud)) {
  295. note_page(st, addr, 2, pud_val(pud));
  296. } else {
  297. BUG_ON(pud_bad(pud));
  298. walk_pmd(st, pudp, addr);
  299. }
  300. }
  301. }
  302. static void walk_pgd(struct pg_state *st, struct mm_struct *mm,
  303. unsigned long start)
  304. {
  305. pgd_t *pgdp = pgd_offset(mm, 0UL);
  306. unsigned i;
  307. unsigned long addr;
  308. for (i = 0; i < PTRS_PER_PGD; i++, pgdp++) {
  309. pgd_t pgd = READ_ONCE(*pgdp);
  310. addr = start + i * PGDIR_SIZE;
  311. if (pgd_none(pgd)) {
  312. note_page(st, addr, 1, pgd_val(pgd));
  313. } else {
  314. BUG_ON(pgd_bad(pgd));
  315. walk_pud(st, pgdp, addr);
  316. }
  317. }
  318. }
  319. void ptdump_walk_pgd(struct seq_file *m, struct ptdump_info *info)
  320. {
  321. struct pg_state st = {
  322. .seq = m,
  323. .marker = info->markers,
  324. };
  325. walk_pgd(&st, info->mm, info->base_addr);
  326. note_page(&st, 0, 0, 0);
  327. }
  328. static void ptdump_initialize(void)
  329. {
  330. unsigned i, j;
  331. for (i = 0; i < ARRAY_SIZE(pg_level); i++)
  332. if (pg_level[i].bits)
  333. for (j = 0; j < pg_level[i].num; j++)
  334. pg_level[i].mask |= pg_level[i].bits[j].mask;
  335. }
  336. static struct ptdump_info kernel_ptdump_info = {
  337. .mm = &init_mm,
  338. .markers = address_markers,
  339. .base_addr = VA_START,
  340. };
  341. void ptdump_check_wx(void)
  342. {
  343. struct pg_state st = {
  344. .seq = NULL,
  345. .marker = (struct addr_marker[]) {
  346. { 0, NULL},
  347. { -1, NULL},
  348. },
  349. .check_wx = true,
  350. };
  351. walk_pgd(&st, &init_mm, VA_START);
  352. note_page(&st, 0, 0, 0);
  353. if (st.wx_pages || st.uxn_pages)
  354. pr_warn("Checked W+X mappings: FAILED, %lu W+X pages found, %lu non-UXN pages found\n",
  355. st.wx_pages, st.uxn_pages);
  356. else
  357. pr_info("Checked W+X mappings: passed, no W+X pages found\n");
  358. }
  359. static int ptdump_init(void)
  360. {
  361. ptdump_initialize();
  362. return ptdump_debugfs_register(&kernel_ptdump_info,
  363. "kernel_page_tables");
  364. }
  365. device_initcall(ptdump_init);