LCOV - code coverage report
Current view: top level - mm - mprotect.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 84 101 83.2 %
Date: 2014-04-16 Functions: 4 5 80.0 %
Branches: 59 87 67.8 %

           Branch data     Line data    Source code
       1                 :            : /*
       2                 :            :  *  mm/mprotect.c
       3                 :            :  *
       4                 :            :  *  (C) Copyright 1994 Linus Torvalds
       5                 :            :  *  (C) Copyright 2002 Christoph Hellwig
       6                 :            :  *
       7                 :            :  *  Address space accounting code       <alan@lxorguk.ukuu.org.uk>
       8                 :            :  *  (C) Copyright 2002 Red Hat Inc, All Rights Reserved
       9                 :            :  */
      10                 :            : 
      11                 :            : #include <linux/mm.h>
      12                 :            : #include <linux/hugetlb.h>
      13                 :            : #include <linux/shm.h>
      14                 :            : #include <linux/mman.h>
      15                 :            : #include <linux/fs.h>
      16                 :            : #include <linux/highmem.h>
      17                 :            : #include <linux/security.h>
      18                 :            : #include <linux/mempolicy.h>
      19                 :            : #include <linux/personality.h>
      20                 :            : #include <linux/syscalls.h>
      21                 :            : #include <linux/swap.h>
      22                 :            : #include <linux/swapops.h>
      23                 :            : #include <linux/mmu_notifier.h>
      24                 :            : #include <linux/migrate.h>
      25                 :            : #include <linux/perf_event.h>
      26                 :            : #include <linux/ksm.h>
      27                 :            : #include <asm/uaccess.h>
      28                 :            : #include <asm/pgtable.h>
      29                 :            : #include <asm/cacheflush.h>
      30                 :            : #include <asm/tlbflush.h>
      31                 :            : 
      32                 :            : #ifndef pgprot_modify
      33                 :            : static inline pgprot_t pgprot_modify(pgprot_t oldprot, pgprot_t newprot)
      34                 :            : {
      35                 :            :         return newprot;
      36                 :            : }
      37                 :            : #endif
      38                 :            : 
      39                 :          0 : static unsigned long change_pte_range(struct vm_area_struct *vma, pmd_t *pmd,
      40                 :            :                 unsigned long addr, unsigned long end, pgprot_t newprot,
      41                 :            :                 int dirty_accountable, int prot_numa)
      42                 :            : {
      43                 :            :         struct mm_struct *mm = vma->vm_mm;
      44                 :            :         pte_t *pte, oldpte;
      45                 :            :         spinlock_t *ptl;
      46                 :            :         unsigned long pages = 0;
      47                 :            : 
      48                 :     248965 :         pte = pte_offset_map_lock(mm, pmd, addr, &ptl);
      49                 :            :         arch_enter_lazy_mmu_mode();
      50                 :            :         do {
      51                 :     921323 :                 oldpte = *pte;
      52         [ +  + ]:     921323 :                 if (pte_present(oldpte)) {
      53                 :            :                         pte_t ptent;
      54                 :            :                         bool updated = false;
      55                 :            : 
      56         [ +  - ]:     185634 :                         if (!prot_numa) {
      57                 :            :                                 ptent = ptep_modify_prot_start(mm, addr, pte);
      58                 :            :                                 if (pte_numa(ptent))
      59                 :            :                                         ptent = pte_mknonnuma(ptent);
      60                 :            :                                 ptent = pte_modify(ptent, newprot);
      61                 :            :                                 /*
      62                 :            :                                  * Avoid taking write faults for pages we
      63                 :            :                                  * know to be dirty.
      64                 :            :                                  */
      65 [ -  + ][ #  # ]:     185635 :                                 if (dirty_accountable && pte_dirty(ptent))
      66                 :            :                                         ptent = pte_mkwrite(ptent);
      67                 :            :                                 ptep_modify_prot_commit(mm, addr, pte, ptent);
      68                 :            :                                 updated = true;
      69                 :            :                         } else {
      70                 :            :                                 struct page *page;
      71                 :            : 
      72                 :          0 :                                 page = vm_normal_page(vma, addr, oldpte);
      73         [ #  # ]:          0 :                                 if (page && !PageKsm(page)) {
      74                 :            :                                         if (!pte_numa(oldpte)) {
      75                 :            :                                                 ptep_set_numa(mm, addr, pte);
      76                 :            :                                                 updated = true;
      77                 :            :                                         }
      78                 :            :                                 }
      79                 :            :                         }
      80            [ + ]:     185630 :                         if (updated)
      81                 :     185635 :                                 pages++;
      82            [ + ]:     735689 :                 } else if (IS_ENABLED(CONFIG_MIGRATION) && !pte_file(oldpte)) {
      83                 :            :                         swp_entry_t entry = pte_to_swp_entry(oldpte);
      84                 :            : 
      85         [ -  + ]:     735694 :                         if (is_write_migration_entry(entry)) {
      86                 :            :                                 pte_t newpte;
      87                 :            :                                 /*
      88                 :            :                                  * A protection check is difficult so
      89                 :            :                                  * just be safe and disable write
      90                 :            :                                  */
      91                 :            :                                 make_migration_entry_read(&entry);
      92                 :            :                                 newpte = swp_entry_to_pte(entry);
      93                 :            :                                 if (pte_swp_soft_dirty(oldpte))
      94                 :            :                                         newpte = pte_swp_mksoft_dirty(newpte);
      95                 :            :                                 set_pte_at(mm, addr, pte, newpte);
      96                 :            : 
      97                 :          0 :                                 pages++;
      98                 :            :                         }
      99                 :            :                 }
     100         [ +  + ]:     921319 :         } while (pte++, addr += PAGE_SIZE, addr != end);
     101                 :            :         arch_leave_lazy_mmu_mode();
     102                 :     248959 :         pte_unmap_unlock(pte - 1, ptl);
     103                 :            : 
     104                 :     248965 :         return pages;
     105                 :            : }
     106                 :            : 
     107                 :            : static inline unsigned long change_pmd_range(struct vm_area_struct *vma,
     108                 :            :                 pud_t *pud, unsigned long addr, unsigned long end,
     109                 :            :                 pgprot_t newprot, int dirty_accountable, int prot_numa)
     110                 :            : {
     111                 :            :         pmd_t *pmd;
     112                 :            :         unsigned long next;
     113                 :            :         unsigned long pages = 0;
     114                 :            :         unsigned long nr_huge_updates = 0;
     115                 :            : 
     116                 :            :         pmd = pmd_offset(pud, addr);
     117                 :            :         do {
     118                 :            :                 unsigned long this_pages;
     119                 :            : 
     120                 :            :                 next = pmd_addr_end(addr, end);
     121                 :            :                 if (pmd_trans_huge(*pmd)) {
     122                 :            :                         if (next - addr != HPAGE_PMD_SIZE)
     123                 :            :                                 split_huge_page_pmd(vma, addr, pmd);
     124                 :            :                         else {
     125                 :            :                                 int nr_ptes = change_huge_pmd(vma, pmd, addr,
     126                 :            :                                                 newprot, prot_numa);
     127                 :            : 
     128                 :            :                                 if (nr_ptes) {
     129                 :            :                                         if (nr_ptes == HPAGE_PMD_NR) {
     130                 :            :                                                 pages += HPAGE_PMD_NR;
     131                 :            :                                                 nr_huge_updates++;
     132                 :            :                                         }
     133                 :            :                                         continue;
     134                 :            :                                 }
     135                 :            :                         }
     136                 :            :                         /* fall through */
     137                 :            :                 }
     138         [ +  + ]:     251361 :                 if (pmd_none_or_clear_bad(pmd))
     139                 :       2397 :                         continue;
     140                 :     248964 :                 this_pages = change_pte_range(vma, pmd, addr, next, newprot,
     141                 :            :                                  dirty_accountable, prot_numa);
     142                 :            :                 pages += this_pages;
     143                 :            :         } while (pmd++, addr = next, addr != end);
     144                 :            : 
     145                 :            :         if (nr_huge_updates)
     146                 :            :                 count_vm_numa_events(NUMA_HUGE_PTE_UPDATES, nr_huge_updates);
     147                 :            :         return pages;
     148                 :            : }
     149                 :            : 
     150                 :            : static inline unsigned long change_pud_range(struct vm_area_struct *vma,
     151                 :            :                 pgd_t *pgd, unsigned long addr, unsigned long end,
     152                 :            :                 pgprot_t newprot, int dirty_accountable, int prot_numa)
     153                 :            : {
     154                 :            :         pud_t *pud;
     155                 :            :         unsigned long next;
     156                 :            :         unsigned long pages = 0;
     157                 :            : 
     158                 :            :         pud = pud_offset(pgd, addr);
     159                 :            :         do {
     160                 :            :                 next = pud_addr_end(addr, end);
     161                 :            :                 if (pud_none_or_clear_bad(pud))
     162                 :            :                         continue;
     163                 :            :                 pages += change_pmd_range(vma, pud, addr, next, newprot,
     164                 :            :                                  dirty_accountable, prot_numa);
     165                 :            :         } while (pud++, addr = next, addr != end);
     166                 :            : 
     167                 :            :         return pages;
     168                 :            : }
     169                 :            : 
     170                 :          0 : static unsigned long change_protection_range(struct vm_area_struct *vma,
     171                 :            :                 unsigned long addr, unsigned long end, pgprot_t newprot,
     172                 :            :                 int dirty_accountable, int prot_numa)
     173                 :            : {
     174                 :     250758 :         struct mm_struct *mm = vma->vm_mm;
     175                 :            :         pgd_t *pgd;
     176                 :            :         unsigned long next;
     177                 :            :         unsigned long start = addr;
     178                 :            :         unsigned long pages = 0;
     179                 :            : 
     180         [ -  + ]:     250758 :         BUG_ON(addr >= end);
     181                 :     250758 :         pgd = pgd_offset(mm, addr);
     182                 :     250758 :         flush_cache_range(vma, addr, end);
     183                 :            :         set_tlb_flush_pending(mm);
     184                 :            :         do {
     185         [ +  + ]:     502119 :                 next = pgd_addr_end(addr, end);
     186                 :            :                 if (pgd_none_or_clear_bad(pgd))
     187                 :            :                         continue;
     188                 :     251358 :                 pages += change_pud_range(vma, pgd, addr, next, newprot,
     189                 :            :                                  dirty_accountable, prot_numa);
     190         [ +  + ]:     251358 :         } while (pgd++, addr = next, addr != end);
     191                 :            : 
     192                 :            :         /* Only flush the TLB if we actually modified any entries: */
     193         [ +  + ]:     250758 :         if (pages)
     194                 :     150835 :                 flush_tlb_range(vma, start, end);
     195                 :            :         clear_tlb_flush_pending(mm);
     196                 :            : 
     197                 :     250757 :         return pages;
     198                 :            : }
     199                 :            : 
     200                 :          0 : unsigned long change_protection(struct vm_area_struct *vma, unsigned long start,
     201                 :            :                        unsigned long end, pgprot_t newprot,
     202                 :            :                        int dirty_accountable, int prot_numa)
     203                 :            : {
     204                 :            :         struct mm_struct *mm = vma->vm_mm;
     205                 :            :         unsigned long pages;
     206                 :            : 
     207                 :            :         mmu_notifier_invalidate_range_start(mm, start, end);
     208                 :            :         if (is_vm_hugetlb_page(vma))
     209                 :            :                 pages = hugetlb_change_protection(vma, start, end, newprot);
     210                 :            :         else
     211                 :     250758 :                 pages = change_protection_range(vma, start, end, newprot, dirty_accountable, prot_numa);
     212                 :            :         mmu_notifier_invalidate_range_end(mm, start, end);
     213                 :            : 
     214                 :          0 :         return pages;
     215                 :            : }
     216                 :            : 
     217                 :            : int
     218                 :          0 : mprotect_fixup(struct vm_area_struct *vma, struct vm_area_struct **pprev,
     219                 :            :         unsigned long start, unsigned long end, unsigned long newflags)
     220                 :            : {
     221                 :     277993 :         struct mm_struct *mm = vma->vm_mm;
     222                 :     277993 :         unsigned long oldflags = vma->vm_flags;
     223                 :     277993 :         long nrpages = (end - start) >> PAGE_SHIFT;
     224                 :            :         unsigned long charged = 0;
     225                 :            :         pgoff_t pgoff;
     226                 :            :         int error;
     227                 :            :         int dirty_accountable = 0;
     228                 :            : 
     229         [ +  + ]:     277993 :         if (newflags == oldflags) {
     230                 :      27241 :                 *pprev = vma;
     231                 :      27241 :                 return 0;
     232                 :            :         }
     233                 :            : 
     234                 :            :         /*
     235                 :            :          * If we make a private mapping writable we increase our commit;
     236                 :            :          * but (without finer accounting) cannot reduce our commit if we
     237                 :            :          * make it unwritable again. hugetlb mapping were accounted for
     238                 :            :          * even if read-only so there is no need to account for them here
     239                 :            :          */
     240         [ +  + ]:     250752 :         if (newflags & VM_WRITE) {
     241         [ -  + ]:       1771 :                 if (!(oldflags & (VM_ACCOUNT|VM_WRITE|VM_HUGETLB|
     242                 :            :                                                 VM_SHARED|VM_NORESERVE))) {
     243                 :            :                         charged = nrpages;
     244         [ #  # ]:          0 :                         if (security_vm_enough_memory_mm(mm, charged))
     245                 :            :                                 return -ENOMEM;
     246                 :          0 :                         newflags |= VM_ACCOUNT;
     247                 :            :                 }
     248                 :            :         }
     249                 :            : 
     250                 :            :         /*
     251                 :            :          * First try to merge with previous and/or next vma.
     252                 :            :          */
     253                 :     250752 :         pgoff = vma->vm_pgoff + ((start - vma->vm_start) >> PAGE_SHIFT);
     254                 :     250752 :         *pprev = vma_merge(mm, *pprev, start, end, newflags,
     255                 :            :                         vma->anon_vma, vma->vm_file, pgoff, vma_policy(vma),
     256                 :            :                         vma_get_anon_name(vma));
     257         [ +  + ]:     250747 :         if (*pprev) {
     258                 :            :                 vma = *pprev;
     259                 :            :                 goto success;
     260                 :            :         }
     261                 :            : 
     262                 :     249664 :         *pprev = vma;
     263                 :            : 
     264         [ +  + ]:     249664 :         if (start != vma->vm_start) {
     265                 :      96301 :                 error = split_vma(mm, vma, start, 1);
     266         [ +  + ]:      96305 :                 if (error)
     267                 :            :                         goto fail;
     268                 :            :         }
     269                 :            : 
     270         [ +  + ]:     249664 :         if (end != vma->vm_end) {
     271                 :     249399 :                 error = split_vma(mm, vma, end, 0);
     272         [ +  - ]:     249409 :                 if (error)
     273                 :            :                         goto fail;
     274                 :            :         }
     275                 :            : 
     276                 :            : success:
     277                 :            :         /*
     278                 :            :          * vm_flags and vm_page_prot are protected by the mmap_sem
     279                 :            :          * held in write mode.
     280                 :            :          */
     281                 :     250757 :         vma->vm_flags = newflags;
     282                 :     250757 :         vma->vm_page_prot = pgprot_modify(vma->vm_page_prot,
     283                 :            :                                           vm_get_page_prot(newflags));
     284                 :            : 
     285         [ +  + ]:     250759 :         if (vma_wants_writenotify(vma)) {
     286                 :          1 :                 vma->vm_page_prot = vm_get_page_prot(newflags & ~VM_SHARED);
     287                 :            :                 dirty_accountable = 1;
     288                 :            :         }
     289                 :            : 
     290                 :     250758 :         change_protection(vma, start, end, vma->vm_page_prot,
     291                 :            :                           dirty_accountable, 0);
     292                 :            : 
     293                 :     250747 :         vm_stat_account(mm, oldflags, vma->vm_file, -nrpages);
     294                 :     250756 :         vm_stat_account(mm, newflags, vma->vm_file, nrpages);
     295                 :     250757 :         perf_event_mmap(vma);
     296                 :     250759 :         return 0;
     297                 :            : 
     298                 :            : fail:
     299                 :          4 :         vm_unacct_memory(charged);
     300                 :          0 :         return error;
     301                 :            : }
     302                 :            : 
     303                 :          0 : SYSCALL_DEFINE3(mprotect, unsigned long, start, size_t, len,
     304                 :            :                 unsigned long, prot)
     305                 :            : {
     306                 :            :         unsigned long vm_flags, nstart, end, tmp, reqprot;
     307                 :            :         struct vm_area_struct *vma, *prev;
     308                 :            :         int error = -EINVAL;
     309                 :     250753 :         const int grows = prot & (PROT_GROWSDOWN|PROT_GROWSUP);
     310                 :     250753 :         prot &= ~(PROT_GROWSDOWN|PROT_GROWSUP);
     311            [ + ]:     250753 :         if (grows == (PROT_GROWSDOWN|PROT_GROWSUP)) /* can't be both */
     312                 :            :                 return -EINVAL;
     313                 :            : 
     314            [ + ]:     250754 :         if (start & ~PAGE_MASK)
     315                 :            :                 return -EINVAL;
     316         [ +  + ]:     250755 :         if (!len)
     317                 :            :                 return 0;
     318                 :     250753 :         len = PAGE_ALIGN(len);
     319                 :     250753 :         end = start + len;
     320         [ +  - ]:     250753 :         if (end <= start)
     321                 :            :                 return -ENOMEM;
     322         [ +  + ]:     250753 :         if (!arch_validate_prot(prot))
     323                 :            :                 return -EINVAL;
     324                 :            : 
     325                 :            :         reqprot = prot;
     326                 :            :         /*
     327                 :            :          * Does the application expect PROT_READ to imply PROT_EXEC:
     328                 :            :          */
     329 [ +  + ][ -  + ]:     250745 :         if ((prot & PROT_READ) && (current->personality & READ_IMPLIES_EXEC))
     330                 :          0 :                 prot |= PROT_EXEC;
     331                 :            : 
     332                 :            :         vm_flags = calc_vm_prot_bits(prot);
     333                 :            : 
     334                 :     250745 :         down_write(&current->mm->mmap_sem);
     335                 :            : 
     336                 :     250755 :         vma = find_vma(current->mm, start);
     337                 :            :         error = -ENOMEM;
     338         [ +  - ]:     250760 :         if (!vma)
     339                 :            :                 goto out;
     340                 :     250760 :         prev = vma->vm_prev;
     341         [ -  + ]:     250760 :         if (unlikely(grows & PROT_GROWSDOWN)) {
     342         [ #  # ]:          0 :                 if (vma->vm_start >= end)
     343                 :            :                         goto out;
     344                 :            :                 start = vma->vm_start;
     345                 :            :                 error = -EINVAL;
     346         [ #  # ]:          0 :                 if (!(vma->vm_flags & VM_GROWSDOWN))
     347                 :            :                         goto out;
     348                 :            :         } else {
     349         [ +  + ]:     250760 :                 if (vma->vm_start > start)
     350                 :            :                         goto out;
     351         [ +  - ]:     250755 :                 if (unlikely(grows & PROT_GROWSUP)) {
     352                 :            :                         end = vma->vm_end;
     353                 :            :                         error = -EINVAL;
     354                 :            :                         if (!(vma->vm_flags & VM_GROWSUP))
     355                 :            :                                 goto out;
     356                 :            :                 }
     357                 :            :         }
     358         [ +  + ]:     250756 :         if (start > vma->vm_start)
     359                 :     250756 :                 prev = vma;
     360                 :            : 
     361                 :            :         for (nstart = start ; ; ) {
     362                 :            :                 unsigned long newflags;
     363                 :            : 
     364                 :            :                 /* Here we know that vma->vm_start <= nstart < vma->vm_end. */
     365                 :            : 
     366                 :            :                 newflags = vm_flags;
     367                 :     250756 :                 newflags |= (vma->vm_flags & ~(VM_READ | VM_WRITE | VM_EXEC));
     368                 :            : 
     369                 :            :                 /* newflags >> 4 shift VM_MAY% in place of VM_% */
     370         [ +  + ]:     250756 :                 if ((newflags & ~(newflags >> 4)) & (VM_READ | VM_WRITE | VM_EXEC)) {
     371                 :            :                         error = -EACCES;
     372                 :            :                         goto out;
     373                 :            :                 }
     374                 :            : 
     375                 :     250754 :                 error = security_file_mprotect(vma, reqprot, prot);
     376            [ + ]:     250754 :                 if (error)
     377                 :            :                         goto out;
     378                 :            : 
     379                 :     250755 :                 tmp = vma->vm_end;
     380         [ +  + ]:     250755 :                 if (tmp > end)
     381                 :            :                         tmp = end;
     382                 :     250755 :                 error = mprotect_fixup(vma, &prev, nstart, tmp, newflags);
     383         [ +  - ]:     250759 :                 if (error)
     384                 :            :                         goto out;
     385                 :            :                 nstart = tmp;
     386                 :            : 
     387         [ -  + ]:     250759 :                 if (nstart < prev->vm_end)
     388                 :            :                         nstart = prev->vm_end;
     389         [ -  + ]:     250759 :                 if (nstart >= end)
     390                 :            :                         goto out;
     391                 :            : 
     392                 :          0 :                 vma = prev->vm_next;
     393 [ #  # ][ #  # ]:          0 :                 if (!vma || vma->vm_start != nstart) {
     394                 :            :                         error = -ENOMEM;
     395                 :            :                         goto out;
     396                 :            :                 }
     397                 :            :         }
     398                 :            : out:
     399                 :     250764 :         up_write(&current->mm->mmap_sem);
     400                 :            :         return error;
     401                 :            : }

Generated by: LCOV version 1.9