Preparatory patch for userfaultfd read-write protection (RWP). RWP extends userfaultfd protection from plain write-protection (WP) to full read-write protection: accesses to an RWP-protected range -- reads as well as writes -- trap through userfaultfd. Reserve VM_UFFD_RWP, add the userfaultfd_rwp() and userfaultfd_protected() helpers, and wire up the smaps "ur" entry and the trace-flag table the rest of the series will use. The flag is gated on CONFIG_USERFAULTFD_RWP, which is introduced together with the UAPI in a later patch; until then VM_UFFD_RWP aliases VM_NONE and every downstream check folds to dead code. Nothing sets or queries the flag yet.
Unified · Split
| @@ -608,6 +608,7 @@ encoded manner. The codes are the following: · 607 unmodified lines | ||
| 608 | 608 | um userfaultfd missing tracking |
| 609 | 609 | uw userfaultfd wr-protect tracking |
| 610 | 610 | ui userfaultfd minor fault |
| 611 | + ur userfaultfd read-write-protect tracking | |
| 611 | 612 | ss shadow/guarded control stack page |
| 612 | 613 | sl sealed |
| 613 | 614 | lf lock on fault pages |
| @@ -1237,6 +1237,9 @@ static void show_smap_vma_flags(struct seq_file *m, struct vm_area_struct *vma) · 1236 unmodified lines | ||
| 1237 | 1237 | #ifdef CONFIG_HAVE_ARCH_USERFAULTFD_MINOR |
| 1238 | 1238 | [ilog2(VM_UFFD_MINOR)] = "ui", |
| 1239 | 1239 | #endif /* CONFIG_HAVE_ARCH_USERFAULTFD_MINOR */ |
| 1240 | +#ifdef CONFIG_USERFAULTFD_RWP | |
| 1241 | + [ilog2(VM_UFFD_RWP)] = "ur", | |
| 1242 | +#endif | |
| 1240 | 1243 | #ifdef CONFIG_ARCH_HAS_USER_SHADOW_STACK |
| 1241 | 1244 | [ilog2(VM_SHADOW_STACK)] = "ss", |
| 1242 | 1245 | #endif |
| @@ -354,6 +354,7 @@ enum { · 353 unmodified lines | ||
| 354 | 354 | #endif |
| 355 | 355 | DECLARE_VMA_BIT(UFFD_MINOR, 41), |
| 356 | 356 | DECLARE_VMA_BIT(SEALED, 42), |
| 357 | + DECLARE_VMA_BIT(UFFD_RWP, 43), | |
| 357 | 358 | /* Flags that reuse flags above. */ |
| 358 | 359 | DECLARE_VMA_BIT_ALIAS(PKEY_BIT0, HIGH_ARCH_0), |
| 359 | 360 | DECLARE_VMA_BIT_ALIAS(PKEY_BIT1, HIGH_ARCH_1), |
| @@ -497,12 +498,17 @@ enum { · 137 unmodified lines | ||
| 497 | 498 | #else |
| 498 | 499 | #define VM_UFFD_MINOR VM_NONE |
| 499 | 500 | #endif |
| 501 | +#ifdef CONFIG_USERFAULTFD_RWP | |
| 502 | +#define VM_UFFD_RWP INIT_VM_FLAG(UFFD_RWP) | |
| 503 | +#else | |
| 504 | +#define VM_UFFD_RWP VM_NONE | |
| 505 | +#endif | |
| 500 | 506 | |
| 501 | 507 | /* |
| 502 | − * vma_flags_t masks for the userfaultfd VMA flags. VMA_UFFD_MINOR is gated on | |
| 503 | − * the same config as VM_UFFD_MINOR -- which implies 64BIT, where the bit fits | |
| 504 | − * -- so an out-of-range bit is never fed to mk_vma_flags() on a build whose | |
| 505 | − * bitmap cannot hold it. | |
| 508 | + * vma_flags_t masks for the userfaultfd VMA flags. The two high-bit modes are | |
| 509 | + * gated on the same configs as their VM_* flags above -- both of which imply | |
| 510 | + * 64BIT -- so an out-of-range bit is never fed to mk_vma_flags() on a build | |
| 511 | + * whose bitmap cannot hold it. | |
| 506 | 512 | */ |
| 507 | 513 | #define VMA_UFFD_MISSING mk_vma_flags(VMA_UFFD_MISSING_BIT) |
| 508 | 514 | #define VMA_UFFD_WP mk_vma_flags(VMA_UFFD_WP_BIT) |
| @@ -511,6 +517,11 @@ enum { · 2 unmodified lines | ||
| 511 | 517 | #else |
| 512 | 518 | #define VMA_UFFD_MINOR EMPTY_VMA_FLAGS |
| 513 | 519 | #endif |
| 520 | +#ifdef CONFIG_USERFAULTFD_RWP | |
| 521 | +#define VMA_UFFD_RWP mk_vma_flags(VMA_UFFD_RWP_BIT) | |
| 522 | +#else | |
| 523 | +#define VMA_UFFD_RWP EMPTY_VMA_FLAGS | |
| 524 | +#endif | |
| 514 | 525 | |
| 515 | 526 | #ifdef CONFIG_64BIT |
| 516 | 527 | #define VM_ALLOW_ANY_UNCACHED INIT_VM_FLAG(ALLOW_ANY_UNCACHED) |
| @@ -649,22 +660,24 @@ enum { · 132 unmodified lines | ||
| 649 | 660 | * reconsistuted upon page fault, so necessitate page table copying upon fork. |
| 650 | 661 | * |
| 651 | 662 | * Note that these flags should be compared with the DESTINATION VMA not the |
| 652 | − * source, as VM_UFFD_WP may not be propagated to destination, while all other | |
| 653 | − * flags will be. | |
| 663 | + * source: VM_UFFD_WP and VM_UFFD_RWP may be cleared on the destination | |
| 664 | + * (dup_userfaultfd() -> userfaultfd_reset_ctx() when the parent context did | |
| 665 | + * not negotiate UFFD_FEATURE_EVENT_FORK), while all other flags propagate. | |
| 654 | 666 | * |
| 655 | 667 | * VM_PFNMAP / VM_MIXEDMAP - These contain kernel-mapped data which cannot be |
| 656 | 668 | * reasonably reconstructed on page fault. |
| 657 | 669 | * |
| 658 | 670 | * VM_UFFD_WP - Encodes metadata about an installed uffd |
| 659 | − * write protect handler, which cannot be | |
| 660 | − * reconstructed on page fault. | |
| 661 | − * | |
| 662 | − * We always copy pgtables when dst_vma has uffd-wp | |
| 663 | − * enabled even if it's file-backed | |
| 664 | − * (e.g. shmem). Because when uffd-wp is enabled, | |
| 665 | − * pgtable contains uffd-wp protection information, | |
| 666 | − * that's something we can't retrieve from page cache, | |
| 667 | − * and skip copying will lose those info. | |
| 671 | + * VM_UFFD_RWP write- or read-write-protect handler, which | |
| 672 | + * cannot be reconstructed on page fault. | |
| 673 | + * | |
| 674 | + * We always copy pgtables when dst_vma has the | |
| 675 | + * uffd PTE bit in use even if it's file-backed | |
| 676 | + * (e.g. shmem). Because when the uffd bit is | |
| 677 | + * in use, the pgtable contains the protection | |
| 678 | + * information, that's something we can't | |
| 679 | + * retrieve from page cache, and skip copying | |
| 680 | + * will lose those info. | |
| 668 | 681 | * |
| 669 | 682 | * VM_MAYBE_GUARD - Could contain page guard region markers which |
| 670 | 683 | * by design are a property of the page tables |
| @@ -21,10 +21,11 @@ · 20 unmodified lines | ||
| 21 | 21 | #include <linux/hugetlb_inline.h> |
| 22 | 22 | |
| 23 | 23 | /* The set of all possible UFFD-related VM flags. */ |
| 24 | −#define __VM_UFFD_FLAGS (VM_UFFD_MISSING | VM_UFFD_WP | VM_UFFD_MINOR) | |
| 24 | +#define __VM_UFFD_FLAGS (VM_UFFD_MISSING | VM_UFFD_MINOR | \ | |
| 25 | + VM_UFFD_WP | VM_UFFD_RWP) | |
| 25 | 26 | |
| 26 | 27 | #define __VMA_UFFD_FLAGS mk_vma_flags_from_masks(VMA_UFFD_MISSING, VMA_UFFD_WP, \ |
| 27 | − VMA_UFFD_MINOR) | |
| 28 | + VMA_UFFD_MINOR, VMA_UFFD_RWP) | |
| 28 | 29 | |
| 29 | 30 | /* |
| 30 | 31 | * CAREFUL: Check include/uapi/asm-generic/fcntl.h when defining |
| @@ -168,7 +169,8 @@ static inline bool is_mergeable_vm_userfaultfd_ctx(struct vm_area_struct *vma, · 137 unmodified lines | ||
| 168 | 169 | /* |
| 169 | 170 | * Never enable huge pmd sharing on some uffd registered vmas: |
| 170 | 171 | * |
| 171 | − * - VM_UFFD_WP VMAs, because write protect information is per pgtable entry. | |
| 172 | + * - VM_UFFD_WP and VM_UFFD_RWP VMAs, because the write protect / access | |
| 173 | + * tracking information is per pgtable entry. | |
| 172 | 174 | * |
| 173 | 175 | * - VM_UFFD_MINOR VMAs, because otherwise we would never get minor faults for |
| 174 | 176 | * VMAs which share huge pmds. (If you have two mappings to the same |
| @@ -179,20 +181,25 @@ static inline bool is_mergeable_vm_userfaultfd_ctx(struct vm_area_struct *vma, · 4 unmodified lines | ||
| 179 | 181 | static inline bool uffd_disable_huge_pmd_share(struct vm_area_struct *vma) |
| 180 | 182 | { |
| 181 | 183 | return vma_test_any_mask(vma, |
| 182 | − mk_vma_flags_from_masks(VMA_UFFD_WP, VMA_UFFD_MINOR)); | |
| 184 | + mk_vma_flags_from_masks(VMA_UFFD_WP, VMA_UFFD_RWP, | |
| 185 | + VMA_UFFD_MINOR)); | |
| 183 | 186 | } |
| 184 | 187 | |
| 185 | 188 | /* |
| 186 | − * Don't do fault around for either WP or MINOR registered uffd range. For | |
| 189 | + * Don't do fault around for WP, RWP or MINOR registered uffd range. For | |
| 187 | 190 | * MINOR registered range, fault around will be a total disaster and ptes can |
| 188 | 191 | * be installed without notifications; for WP it should mostly be fine as long |
| 189 | 192 | * as the fault around checks for pte_none() before the installation, however |
| 190 | − * to be super safe we just forbid it. | |
| 193 | + * to be super safe we just forbid it; for RWP, pre-faulted neighbours would | |
| 194 | + * be indistinguishable from accessed pages in PAGEMAP_SCAN (PAGE_IS_ACCESSED) | |
| 195 | + * and pollute the tracked working set, so each page must be populated by its | |
| 196 | + * own fault. | |
| 191 | 197 | */ |
| 192 | 198 | static inline bool uffd_disable_fault_around(struct vm_area_struct *vma) |
| 193 | 199 | { |
| 194 | 200 | return vma_test_any_mask(vma, |
| 195 | − mk_vma_flags_from_masks(VMA_UFFD_WP, VMA_UFFD_MINOR)); | |
| 201 | + mk_vma_flags_from_masks(VMA_UFFD_WP, VMA_UFFD_RWP, | |
| 202 | + VMA_UFFD_MINOR)); | |
| 196 | 203 | } |
| 197 | 204 | |
| 198 | 205 | static inline bool userfaultfd_missing(struct vm_area_struct *vma) |
| @@ -210,6 +217,16 @@ static inline bool userfaultfd_minor(struct vm_area_struct *vma) · 11 unmodified lines | ||
| 210 | 217 | return vma_test_any_mask(vma, VMA_UFFD_MINOR); |
| 211 | 218 | } |
| 212 | 219 | |
| 220 | +static inline bool userfaultfd_rwp(struct vm_area_struct *vma) | |
| 221 | +{ | |
| 222 | + return vma_test_single_mask(vma, VMA_UFFD_RWP); | |
| 223 | +} | |
| 224 | + | |
| 225 | +static inline bool userfaultfd_protected(struct vm_area_struct *vma) | |
| 226 | +{ | |
| 227 | + return userfaultfd_wp(vma) || userfaultfd_rwp(vma); | |
| 228 | +} | |
| 229 | + | |
| 213 | 230 | static inline bool userfaultfd_pte_wp(struct vm_area_struct *vma, |
| 214 | 231 | pte_t pte) |
| 215 | 232 | { |
| @@ -330,6 +347,16 @@ static inline bool userfaultfd_minor(struct vm_area_struct *vma) · 114 unmodified lines | ||
| 330 | 347 | return false; |
| 331 | 348 | } |
| 332 | 349 | |
| 350 | +static inline bool userfaultfd_rwp(struct vm_area_struct *vma) | |
| 351 | +{ | |
| 352 | + return false; | |
| 353 | +} | |
| 354 | + | |
| 355 | +static inline bool userfaultfd_protected(struct vm_area_struct *vma) | |
| 356 | +{ | |
| 357 | + return false; | |
| 358 | +} | |
| 359 | + | |
| 333 | 360 | static inline bool userfaultfd_pte_wp(struct vm_area_struct *vma, |
| 334 | 361 | pte_t pte) |
| 335 | 362 | { |
| @@ -423,8 +450,8 @@ static inline bool userfaultfd_wp_use_markers(struct vm_area_struct *vma) · 87 unmodified lines | ||
| 423 | 450 | } |
| 424 | 451 | |
| 425 | 452 | /* |
| 426 | − * Returns true if this is a swap pte and was uffd-wp wr-protected in either | |
| 427 | − * forms (pte marker or a normal swap pte), false otherwise. | |
| 453 | + * Returns true if this swap pte carries uffd-tracked state in either | |
| 454 | + * form (pte marker or a normal swap pte), false otherwise. | |
| 428 | 455 | */ |
| 429 | 456 | static inline bool pte_swp_uffd_any(pte_t pte) |
| 430 | 457 | { |
| @@ -186,6 +186,12 @@ IF_HAVE_PG_ARCH_3(arch_3) · 185 unmodified lines | ||
| 186 | 186 | # define IF_HAVE_UFFD_MINOR(flag, name) |
| 187 | 187 | #endif |
| 188 | 188 | |
| 189 | +#ifdef CONFIG_USERFAULTFD_RWP | |
| 190 | +# define IF_HAVE_UFFD_RWP(flag, name) {flag, name}, | |
| 191 | +#else | |
| 192 | +# define IF_HAVE_UFFD_RWP(flag, name) | |
| 193 | +#endif | |
| 194 | + | |
| 189 | 195 | #if defined(CONFIG_64BIT) || defined(CONFIG_PPC32) |
| 190 | 196 | # define IF_HAVE_VM_DROPPABLE(flag, name) {flag, name}, |
| 191 | 197 | #else |
| @@ -207,6 +213,7 @@ IF_HAVE_UFFD_MINOR(VM_UFFD_MINOR, "uffd_minor" ) \ · 15 unmodified lines | ||
| 207 | 213 | {VM_PFNMAP, "pfnmap" }, \ |
| 208 | 214 | {VM_MAYBE_GUARD, "maybe_guard" }, \ |
| 209 | 215 | {VM_UFFD_WP, "uffd_wp" }, \ |
| 216 | +IF_HAVE_UFFD_RWP(VM_UFFD_RWP, "uffd_rwp" ) \ | |
| 210 | 217 | {VM_LOCKED, "locked" }, \ |
| 211 | 218 | {VM_IO, "io" }, \ |
| 212 | 219 | {VM_SEQ_READ, "seqread" }, \ |