mkdirty.c 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  1. // SPDX-License-Identifier: GPL-2.0-only
  2. /*
  3. * Test handling of code that might set PTE/PMD dirty in read-only VMAs.
  4. * Setting a PTE/PMD dirty must not accidentally set the PTE/PMD writable.
  5. *
  6. * Copyright 2023, Red Hat, Inc.
  7. *
  8. * Author(s): David Hildenbrand <david@redhat.com>
  9. */
  10. #include <fcntl.h>
  11. #include <signal.h>
  12. #include <unistd.h>
  13. #include <string.h>
  14. #include <errno.h>
  15. #include <stdlib.h>
  16. #include <stdbool.h>
  17. #include <stdint.h>
  18. #include <sys/mman.h>
  19. #include <setjmp.h>
  20. #include <sys/syscall.h>
  21. #include <sys/ioctl.h>
  22. #include <linux/userfaultfd.h>
  23. #include <linux/mempolicy.h>
  24. #include "../kselftest.h"
  25. #include "vm_util.h"
  26. static size_t pagesize;
  27. static size_t thpsize;
  28. static int mem_fd;
  29. static int pagemap_fd;
  30. static sigjmp_buf env;
  31. static void signal_handler(int sig)
  32. {
  33. if (sig == SIGSEGV)
  34. siglongjmp(env, 1);
  35. siglongjmp(env, 2);
  36. }
  37. static void do_test_write_sigsegv(char *mem)
  38. {
  39. char orig = *mem;
  40. int ret;
  41. if (signal(SIGSEGV, signal_handler) == SIG_ERR) {
  42. ksft_test_result_fail("signal() failed\n");
  43. return;
  44. }
  45. ret = sigsetjmp(env, 1);
  46. if (!ret)
  47. *mem = orig + 1;
  48. if (signal(SIGSEGV, SIG_DFL) == SIG_ERR)
  49. ksft_test_result_fail("signal() failed\n");
  50. ksft_test_result(ret == 1 && *mem == orig,
  51. "SIGSEGV generated, page not modified\n");
  52. }
  53. static char *mmap_thp_range(int prot, char **_mmap_mem, size_t *_mmap_size)
  54. {
  55. const size_t mmap_size = 2 * thpsize;
  56. char *mem, *mmap_mem;
  57. mmap_mem = mmap(NULL, mmap_size, prot, MAP_PRIVATE|MAP_ANON,
  58. -1, 0);
  59. if (mmap_mem == MAP_FAILED) {
  60. ksft_test_result_fail("mmap() failed\n");
  61. return MAP_FAILED;
  62. }
  63. mem = (char *)(((uintptr_t)mmap_mem + thpsize) & ~(thpsize - 1));
  64. if (madvise(mem, thpsize, MADV_HUGEPAGE)) {
  65. ksft_test_result_skip("MADV_HUGEPAGE failed\n");
  66. munmap(mmap_mem, mmap_size);
  67. return MAP_FAILED;
  68. }
  69. *_mmap_mem = mmap_mem;
  70. *_mmap_size = mmap_size;
  71. return mem;
  72. }
  73. static void test_ptrace_write(void)
  74. {
  75. char data = 1;
  76. char *mem;
  77. int ret;
  78. ksft_print_msg("[INFO] PTRACE write access\n");
  79. mem = mmap(NULL, pagesize, PROT_READ, MAP_PRIVATE|MAP_ANON, -1, 0);
  80. if (mem == MAP_FAILED) {
  81. ksft_test_result_fail("mmap() failed\n");
  82. return;
  83. }
  84. /* Fault in the shared zeropage. */
  85. if (*mem != 0) {
  86. ksft_test_result_fail("Memory not zero\n");
  87. goto munmap;
  88. }
  89. /*
  90. * Unshare the page (populating a fresh anon page that might be set
  91. * dirty in the PTE) in the read-only VMA using ptrace (FOLL_FORCE).
  92. */
  93. lseek(mem_fd, (uintptr_t) mem, SEEK_SET);
  94. ret = write(mem_fd, &data, 1);
  95. if (ret != 1 || *mem != data) {
  96. ksft_test_result_fail("write() failed\n");
  97. goto munmap;
  98. }
  99. do_test_write_sigsegv(mem);
  100. munmap:
  101. munmap(mem, pagesize);
  102. }
  103. static void test_ptrace_write_thp(void)
  104. {
  105. char *mem, *mmap_mem;
  106. size_t mmap_size;
  107. char data = 1;
  108. int ret;
  109. ksft_print_msg("[INFO] PTRACE write access to THP\n");
  110. mem = mmap_thp_range(PROT_READ, &mmap_mem, &mmap_size);
  111. if (mem == MAP_FAILED)
  112. return;
  113. /*
  114. * Write to the first subpage in the read-only VMA using
  115. * ptrace(FOLL_FORCE), eventually placing a fresh THP that is marked
  116. * dirty in the PMD.
  117. */
  118. lseek(mem_fd, (uintptr_t) mem, SEEK_SET);
  119. ret = write(mem_fd, &data, 1);
  120. if (ret != 1 || *mem != data) {
  121. ksft_test_result_fail("write() failed\n");
  122. goto munmap;
  123. }
  124. /* MM populated a THP if we got the last subpage populated as well. */
  125. if (!pagemap_is_populated(pagemap_fd, mem + thpsize - pagesize)) {
  126. ksft_test_result_skip("Did not get a THP populated\n");
  127. goto munmap;
  128. }
  129. do_test_write_sigsegv(mem);
  130. munmap:
  131. munmap(mmap_mem, mmap_size);
  132. }
  133. static void test_page_migration(void)
  134. {
  135. char *mem;
  136. ksft_print_msg("[INFO] Page migration\n");
  137. mem = mmap(NULL, pagesize, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANON,
  138. -1, 0);
  139. if (mem == MAP_FAILED) {
  140. ksft_test_result_fail("mmap() failed\n");
  141. return;
  142. }
  143. /* Populate a fresh page and dirty it. */
  144. memset(mem, 1, pagesize);
  145. if (mprotect(mem, pagesize, PROT_READ)) {
  146. ksft_test_result_fail("mprotect() failed\n");
  147. goto munmap;
  148. }
  149. /* Trigger page migration. Might not be available or fail. */
  150. if (syscall(__NR_mbind, mem, pagesize, MPOL_LOCAL, NULL, 0x7fful,
  151. MPOL_MF_MOVE)) {
  152. ksft_test_result_skip("mbind() failed\n");
  153. goto munmap;
  154. }
  155. do_test_write_sigsegv(mem);
  156. munmap:
  157. munmap(mem, pagesize);
  158. }
  159. static void test_page_migration_thp(void)
  160. {
  161. char *mem, *mmap_mem;
  162. size_t mmap_size;
  163. ksft_print_msg("[INFO] Page migration of THP\n");
  164. mem = mmap_thp_range(PROT_READ|PROT_WRITE, &mmap_mem, &mmap_size);
  165. if (mem == MAP_FAILED)
  166. return;
  167. /*
  168. * Write to the first page, which might populate a fresh anon THP
  169. * and dirty it.
  170. */
  171. memset(mem, 1, pagesize);
  172. if (mprotect(mem, thpsize, PROT_READ)) {
  173. ksft_test_result_fail("mprotect() failed\n");
  174. goto munmap;
  175. }
  176. /* MM populated a THP if we got the last subpage populated as well. */
  177. if (!pagemap_is_populated(pagemap_fd, mem + thpsize - pagesize)) {
  178. ksft_test_result_skip("Did not get a THP populated\n");
  179. goto munmap;
  180. }
  181. /* Trigger page migration. Might not be available or fail. */
  182. if (syscall(__NR_mbind, mem, thpsize, MPOL_LOCAL, NULL, 0x7fful,
  183. MPOL_MF_MOVE)) {
  184. ksft_test_result_skip("mbind() failed\n");
  185. goto munmap;
  186. }
  187. do_test_write_sigsegv(mem);
  188. munmap:
  189. munmap(mmap_mem, mmap_size);
  190. }
  191. static void test_pte_mapped_thp(void)
  192. {
  193. char *mem, *mmap_mem;
  194. size_t mmap_size;
  195. ksft_print_msg("[INFO] PTE-mapping a THP\n");
  196. mem = mmap_thp_range(PROT_READ|PROT_WRITE, &mmap_mem, &mmap_size);
  197. if (mem == MAP_FAILED)
  198. return;
  199. /*
  200. * Write to the first page, which might populate a fresh anon THP
  201. * and dirty it.
  202. */
  203. memset(mem, 1, pagesize);
  204. if (mprotect(mem, thpsize, PROT_READ)) {
  205. ksft_test_result_fail("mprotect() failed\n");
  206. goto munmap;
  207. }
  208. /* MM populated a THP if we got the last subpage populated as well. */
  209. if (!pagemap_is_populated(pagemap_fd, mem + thpsize - pagesize)) {
  210. ksft_test_result_skip("Did not get a THP populated\n");
  211. goto munmap;
  212. }
  213. /* Trigger PTE-mapping the THP by mprotect'ing the last subpage. */
  214. if (mprotect(mem + thpsize - pagesize, pagesize,
  215. PROT_READ|PROT_WRITE)) {
  216. ksft_test_result_fail("mprotect() failed\n");
  217. goto munmap;
  218. }
  219. do_test_write_sigsegv(mem);
  220. munmap:
  221. munmap(mmap_mem, mmap_size);
  222. }
  223. #ifdef __NR_userfaultfd
  224. static void test_uffdio_copy(void)
  225. {
  226. struct uffdio_register uffdio_register;
  227. struct uffdio_copy uffdio_copy;
  228. struct uffdio_api uffdio_api;
  229. char *dst, *src;
  230. int uffd;
  231. ksft_print_msg("[INFO] UFFDIO_COPY\n");
  232. src = malloc(pagesize);
  233. memset(src, 1, pagesize);
  234. dst = mmap(NULL, pagesize, PROT_READ, MAP_PRIVATE|MAP_ANON, -1, 0);
  235. if (dst == MAP_FAILED) {
  236. ksft_test_result_fail("mmap() failed\n");
  237. return;
  238. }
  239. uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
  240. if (uffd < 0) {
  241. ksft_test_result_skip("__NR_userfaultfd failed\n");
  242. goto munmap;
  243. }
  244. uffdio_api.api = UFFD_API;
  245. uffdio_api.features = 0;
  246. if (ioctl(uffd, UFFDIO_API, &uffdio_api) < 0) {
  247. ksft_test_result_fail("UFFDIO_API failed\n");
  248. goto close_uffd;
  249. }
  250. uffdio_register.range.start = (unsigned long) dst;
  251. uffdio_register.range.len = pagesize;
  252. uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
  253. if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register)) {
  254. ksft_test_result_fail("UFFDIO_REGISTER failed\n");
  255. goto close_uffd;
  256. }
  257. /* Place a page in a read-only VMA, which might set the PTE dirty. */
  258. uffdio_copy.dst = (unsigned long) dst;
  259. uffdio_copy.src = (unsigned long) src;
  260. uffdio_copy.len = pagesize;
  261. uffdio_copy.mode = 0;
  262. if (ioctl(uffd, UFFDIO_COPY, &uffdio_copy)) {
  263. ksft_test_result_fail("UFFDIO_COPY failed\n");
  264. goto close_uffd;
  265. }
  266. do_test_write_sigsegv(dst);
  267. close_uffd:
  268. close(uffd);
  269. munmap:
  270. munmap(dst, pagesize);
  271. free(src);
  272. }
  273. #endif /* __NR_userfaultfd */
  274. int main(void)
  275. {
  276. int err, tests = 2;
  277. pagesize = getpagesize();
  278. thpsize = read_pmd_pagesize();
  279. if (thpsize) {
  280. ksft_print_msg("[INFO] detected THP size: %zu KiB\n",
  281. thpsize / 1024);
  282. tests += 3;
  283. }
  284. #ifdef __NR_userfaultfd
  285. tests += 1;
  286. #endif /* __NR_userfaultfd */
  287. ksft_print_header();
  288. ksft_set_plan(tests);
  289. mem_fd = open("/proc/self/mem", O_RDWR);
  290. if (mem_fd < 0)
  291. ksft_exit_fail_msg("opening /proc/self/mem failed\n");
  292. pagemap_fd = open("/proc/self/pagemap", O_RDONLY);
  293. if (pagemap_fd < 0)
  294. ksft_exit_fail_msg("opening /proc/self/pagemap failed\n");
  295. /*
  296. * On some ptrace(FOLL_FORCE) write access via /proc/self/mem in
  297. * read-only VMAs, the kernel may set the PTE/PMD dirty.
  298. */
  299. test_ptrace_write();
  300. if (thpsize)
  301. test_ptrace_write_thp();
  302. /*
  303. * On page migration, the kernel may set the PTE/PMD dirty when
  304. * remapping the page.
  305. */
  306. test_page_migration();
  307. if (thpsize)
  308. test_page_migration_thp();
  309. /* PTE-mapping a THP might propagate the dirty PMD bit to the PTEs. */
  310. if (thpsize)
  311. test_pte_mapped_thp();
  312. /* Placing a fresh page via userfaultfd may set the PTE dirty. */
  313. #ifdef __NR_userfaultfd
  314. test_uffdio_copy();
  315. #endif /* __NR_userfaultfd */
  316. err = ksft_get_fail_cnt();
  317. if (err)
  318. ksft_exit_fail_msg("%d out of %d tests failed\n",
  319. err, ksft_test_num());
  320. ksft_exit_pass();
  321. }