Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1755247AbcJTIrX (ORCPT ); Thu, 20 Oct 2016 04:47:23 -0400 Received: from mx2.suse.de ([195.135.220.15]:46473 "EHLO mx2.suse.de" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S934234AbcJTIpc (ORCPT ); Thu, 20 Oct 2016 04:45:32 -0400 X-Amavis-Alert: BAD HEADER SECTION, Duplicate header field: "References" From: Jiri Slaby To: stable@vger.kernel.org Cc: linux-kernel@vger.kernel.org, Linus Torvalds , Andy Lutomirski , Kees Cook , Oleg Nesterov , Willy Tarreau , Nick Piggin , Greg Thelen , Jiri Slaby Subject: [PATCH 3.12 5/5] mm: remove gup_flags FOLL_WRITE games from __get_user_pages() Date: Thu, 20 Oct 2016 10:45:20 +0200 Message-Id: <1bfc47d5b2e135b5bc9ea64c953a6bfe855bc6b9.1476953088.git.jslaby@suse.cz> X-Mailer: git-send-email 2.10.1 In-Reply-To: <8e335ed458e79a74833fb846a5607c814c762bf8.1476953088.git.jslaby@suse.cz> References: <8e335ed458e79a74833fb846a5607c814c762bf8.1476953088.git.jslaby@suse.cz> In-Reply-To: References: Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 3565 Lines: 96 From: Linus Torvalds 3.12-stable review patch. If anyone has any objections, please let me know. =============== commit 19be0eaffa3ac7d8eb6784ad9bdbc7d67ed8e619 upstream. This is an ancient bug that was actually attempted to be fixed once (badly) by me eleven years ago in commit 4ceb5db9757a ("Fix get_user_pages() race for write access") but that was then undone due to problems on s390 by commit f33ea7f404e5 ("fix get_user_pages bug"). In the meantime, the s390 situation has long been fixed, and we can now fix it by checking the pte_dirty() bit properly (and do it better). The s390 dirty bit was implemented in abf09bed3cce ("s390/mm: implement software dirty bits") which made it into v3.9. Earlier kernels will have to look at the page state itself. Also, the VM has become more scalable, and what used a purely theoretical race back then has become easier to trigger. To fix it, we introduce a new internal FOLL_COW flag to mark the "yes, we already did a COW" rather than play racy games with FOLL_WRITE that is very fundamental, and then use the pte dirty flag to validate that the FOLL_COW flag is still valid. Reported-and-tested-by: Phil "not Paul" Oester Acked-by: Hugh Dickins Reviewed-by: Michal Hocko Cc: Andy Lutomirski Cc: Kees Cook Cc: Oleg Nesterov Cc: Willy Tarreau Cc: Nick Piggin Cc: Greg Thelen Signed-off-by: Linus Torvalds Signed-off-by: Jiri Slaby --- include/linux/mm.h | 1 + mm/memory.c | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/include/linux/mm.h b/include/linux/mm.h index 79aa518c16a3..5676a670429e 100644 --- a/include/linux/mm.h +++ b/include/linux/mm.h @@ -1749,6 +1749,7 @@ static inline struct page *follow_page(struct vm_area_struct *vma, #define FOLL_HWPOISON 0x100 /* check page is hwpoisoned */ #define FOLL_NUMA 0x200 /* force NUMA hinting page fault */ #define FOLL_MIGRATION 0x400 /* wait for page to replace migration entry */ +#define FOLL_COW 0x4000 /* internal GUP flag */ typedef int (*pte_fn_t)(pte_t *pte, pgtable_t token, unsigned long addr, void *data); diff --git a/mm/memory.c b/mm/memory.c index 61926356c09a..a0c9c6cb59d1 100644 --- a/mm/memory.c +++ b/mm/memory.c @@ -1441,6 +1441,16 @@ int zap_vma_ptes(struct vm_area_struct *vma, unsigned long address, } EXPORT_SYMBOL_GPL(zap_vma_ptes); +/* + * FOLL_FORCE can write to even unwritable pte's, but only + * after we've gone through a COW cycle and they are dirty. + */ +static inline bool can_follow_write_pte(pte_t pte, unsigned int flags) +{ + return pte_write(pte) || + ((flags & FOLL_FORCE) && (flags & FOLL_COW) && pte_dirty(pte)); +} + /** * follow_page_mask - look up a page descriptor from a user-virtual address * @vma: vm_area_struct mapping @address @@ -1550,7 +1560,7 @@ split_fallthrough: } if ((flags & FOLL_NUMA) && pte_numa(pte)) goto no_page; - if ((flags & FOLL_WRITE) && !pte_write(pte)) + if ((flags & FOLL_WRITE) && !can_follow_write_pte(pte, flags)) goto unlock; page = vm_normal_page(vma, address, pte); @@ -1858,7 +1868,7 @@ long __get_user_pages(struct task_struct *tsk, struct mm_struct *mm, */ if ((ret & VM_FAULT_WRITE) && !(vma->vm_flags & VM_WRITE)) - foll_flags &= ~FOLL_WRITE; + foll_flags |= FOLL_COW; cond_resched(); } -- 2.10.1