> On May 20, 2019, at 1:07 PM, Rick Edgecombe <rick.p.edgecombe@xxxxxxxxx> wrote: > > Switch VM_FLUSH_RESET_PERMS to use a regular TLB flush intead of > vm_unmap_aliases() and fix calculation of the direct map for the > CONFIG_ARCH_HAS_SET_DIRECT_MAP case. > > Meelis Roos reported issues with the new VM_FLUSH_RESET_PERMS flag on a > sparc machine. On investigation some issues were noticed: > Can you split this into a few (3?) patches, each fixing one issue? > 1. The calculation of the direct map address range to flush was wrong. > This could cause problems on x86 if a RO direct map alias ever got loaded > into the TLB. This shouldn't normally happen, but it could cause the > permissions to remain RO on the direct map alias, and then the page > would return from the page allocator to some other component as RO and > cause a crash. > > 2. Calling vm_unmap_alias() on vfree could potentially be a lot of work to > do on a free operation. Simply flushing the TLB instead of the whole > vm_unmap_alias() operation makes the frees faster and pushes the heavy > work to happen on allocation where it would be more expected. > In addition to the extra work, vm_unmap_alias() takes some locks including > a long hold of vmap_purge_lock, which will make all other > VM_FLUSH_RESET_PERMS vfrees wait while the purge operation happens. > > 3. page_address() can have locking on some configurations, so skip calling > this when possible to further speed this up. > > Fixes: 868b104d7379 ("mm/vmalloc: Add flag for freeing of special permsissions") > Reported-by: Meelis Roos <mroos@xxxxxxxx> > Cc: Meelis Roos <mroos@xxxxxxxx> > Cc: Peter Zijlstra <peterz@xxxxxxxxxxxxx> > Cc: "David S. Miller" <davem@xxxxxxxxxxxxx> > Cc: Dave Hansen <dave.hansen@xxxxxxxxx> > Cc: Borislav Petkov <bp@xxxxxxxxx> > Cc: Andy Lutomirski <luto@xxxxxxxxxx> > Cc: Ingo Molnar <mingo@xxxxxxxxxx> > Cc: Nadav Amit <namit@xxxxxxxxxx> > Signed-off-by: Rick Edgecombe <rick.p.edgecombe@xxxxxxxxx> > --- > > Changes since v1: > - Update commit message with more detail > - Fix flush end range on !CONFIG_ARCH_HAS_SET_DIRECT_MAP case > > mm/vmalloc.c | 23 +++++++++++++---------- > 1 file changed, 13 insertions(+), 10 deletions(-) > > diff --git a/mm/vmalloc.c b/mm/vmalloc.c > index c42872ed82ac..8d03427626dc 100644 > --- a/mm/vmalloc.c > +++ b/mm/vmalloc.c > @@ -2122,9 +2122,10 @@ static inline void set_area_direct_map(const struct vm_struct *area, > /* Handle removing and resetting vm mappings related to the vm_struct. */ > static void vm_remove_mappings(struct vm_struct *area, int deallocate_pages) > { > + const bool has_set_direct = IS_ENABLED(CONFIG_ARCH_HAS_SET_DIRECT_MAP); > + const bool flush_reset = area->flags & VM_FLUSH_RESET_PERMS; > unsigned long addr = (unsigned long)area->addr; > - unsigned long start = ULONG_MAX, end = 0; > - int flush_reset = area->flags & VM_FLUSH_RESET_PERMS; > + unsigned long start = addr, end = addr + area->size; > int i; > > /* > @@ -2133,7 +2134,7 @@ static void vm_remove_mappings(struct vm_struct *area, int deallocate_pages) > * This is concerned with resetting the direct map any an vm alias with > * execute permissions, without leaving a RW+X window. > */ > - if (flush_reset && !IS_ENABLED(CONFIG_ARCH_HAS_SET_DIRECT_MAP)) { > + if (flush_reset && !has_set_direct) { > set_memory_nx(addr, area->nr_pages); > set_memory_rw(addr, area->nr_pages); > } > @@ -2146,22 +2147,24 @@ static void vm_remove_mappings(struct vm_struct *area, int deallocate_pages) > > /* > * If not deallocating pages, just do the flush of the VM area and > - * return. > + * return. If the arch doesn't have set_direct_map_(), also skip the > + * below work. > */ > - if (!deallocate_pages) { > - vm_unmap_aliases(); > + if (!deallocate_pages || !has_set_direct) { > + flush_tlb_kernel_range(start, end); > return; > } > > /* > * If execution gets here, flush the vm mapping and reset the direct > * map. Find the start and end range of the direct mappings to make sure > - * the vm_unmap_aliases() flush includes the direct map. > + * the flush_tlb_kernel_range() includes the direct map. > */ > for (i = 0; i < area->nr_pages; i++) { > - if (page_address(area->pages[i])) { > + addr = (unsigned long)page_address(area->pages[i]); > + if (addr) { > start = min(addr, start); > - end = max(addr, end); > + end = max(addr + PAGE_SIZE, end); > } > } > > @@ -2171,7 +2174,7 @@ static void vm_remove_mappings(struct vm_struct *area, int deallocate_pages) > * reset the direct map permissions to the default. > */ > set_area_direct_map(area, set_direct_map_invalid_noflush); > - _vm_unmap_aliases(start, end, 1); > + flush_tlb_kernel_range(start, end); > set_area_direct_map(area, set_direct_map_default_noflush); > } > > -- > 2.20.1 >