linux-mm.kvack.org archive mirror
 help / color / mirror / Atom feed
* [PATCH] mm/gup: handle NULL pages in unpin_user_pages()
@ 2024-11-19  4:49 John Hubbard
  2024-11-19 14:33 ` David Hildenbrand
  0 siblings, 1 reply; 3+ messages in thread
From: John Hubbard @ 2024-11-19  4:49 UTC (permalink / raw)
  To: Andrew Morton
  Cc: LKML, linux-mm, John Hubbard, David Hildenbrand, Oscar Salvador,
	Vivek Kasireddy, Dave Airlie, Gerd Hoffmann, Matthew Wilcox,
	Christoph Hellwig, Jason Gunthorpe, Peter Xu, Arnd Bergmann,
	Daniel Vetter, Dongwon Kim, Hugh Dickins, Junxiao Chang, stable

The recent addition of "pofs" (pages or folios) handling to gup has a
flaw: it assumes that unpin_user_pages() handles NULL pages in the
pages** array. That's not the case, as I discovered when I ran on a new
configuration on my test machine.

Fix this by skipping NULL pages in unpin_user_pages(), just like
unpin_folios() already does.

Details: when booting on x86 with "numa=fake=2 movablecore=4G" on Linux
6.12, and running this:

    tools/testing/selftests/mm/gup_longterm

...I get the following crash:

BUG: kernel NULL pointer dereference, address: 0000000000000008
RIP: 0010:sanity_check_pinned_pages+0x3a/0x2d0
...
Call Trace:
 <TASK>
 ? __die_body+0x66/0xb0
 ? page_fault_oops+0x30c/0x3b0
 ? do_user_addr_fault+0x6c3/0x720
 ? irqentry_enter+0x34/0x60
 ? exc_page_fault+0x68/0x100
 ? asm_exc_page_fault+0x22/0x30
 ? sanity_check_pinned_pages+0x3a/0x2d0
 unpin_user_pages+0x24/0xe0
 check_and_migrate_movable_pages_or_folios+0x455/0x4b0
 __gup_longterm_locked+0x3bf/0x820
 ? mmap_read_lock_killable+0x12/0x50
 ? __pfx_mmap_read_lock_killable+0x10/0x10
 pin_user_pages+0x66/0xa0
 gup_test_ioctl+0x358/0xb20
 __se_sys_ioctl+0x6b/0xc0
 do_syscall_64+0x7b/0x150
 entry_SYSCALL_64_after_hwframe+0x76/0x7e

Fixes: 94efde1d1539 ("mm/gup: avoid an unnecessary allocation call for FOLL_LONGTERM cases")
Cc: David Hildenbrand <david@redhat.com>
Cc: Oscar Salvador <osalvador@suse.de>
Cc: Vivek Kasireddy <vivek.kasireddy@intel.com>
Cc: Dave Airlie <airlied@redhat.com>
Cc: Gerd Hoffmann <kraxel@redhat.com>
Cc: Matthew Wilcox <willy@infradead.org>
Cc: Christoph Hellwig <hch@infradead.org>
Cc: Jason Gunthorpe <jgg@nvidia.com>
Cc: Peter Xu <peterx@redhat.com>
Cc: Arnd Bergmann <arnd@arndb.de>
Cc: Daniel Vetter <daniel.vetter@ffwll.ch>
Cc: Dongwon Kim <dongwon.kim@intel.com>
Cc: Hugh Dickins <hughd@google.com>
Cc: Junxiao Chang <junxiao.chang@intel.com>
Cc: <stable@vger.kernel.org>
Signed-off-by: John Hubbard <jhubbard@nvidia.com>
---

Hi,

I got a nasty shock when I tried out a new test machine setup last
night--I wish I'd noticed the problem earlier! But anyway, this should
make it all better...

I've asked Greg K-H to hold off on including commit 94efde1d1539
("mm/gup: avoid an unnecessary allocation call for FOLL_LONGTERM cases")
in linux-stable (6.11.y), but if this fix-to-the-fix looks good, then
maybe both fixes can ultimately end up in stable.

thanks,
John Hubbard

 mm/gup.c | 17 +++++++++++++++--
 1 file changed, 15 insertions(+), 2 deletions(-)

diff --git a/mm/gup.c b/mm/gup.c
index ad0c8922dac3..6e417502728a 100644
--- a/mm/gup.c
+++ b/mm/gup.c
@@ -52,7 +52,12 @@ static inline void sanity_check_pinned_pages(struct page **pages,
 	 */
 	for (; npages; npages--, pages++) {
 		struct page *page = *pages;
-		struct folio *folio = page_folio(page);
+		struct folio *folio;
+
+		if (!page)
+			continue;
+
+		folio = page_folio(page);
 
 		if (is_zero_page(page) ||
 		    !folio_test_anon(folio))
@@ -248,9 +253,14 @@ static inline struct folio *gup_folio_range_next(struct page *start,
 static inline struct folio *gup_folio_next(struct page **list,
 		unsigned long npages, unsigned long i, unsigned int *ntails)
 {
-	struct folio *folio = page_folio(list[i]);
+	struct folio *folio;
 	unsigned int nr;
 
+	if (!list[i])
+		return NULL;
+
+	folio = page_folio(list[i]);
+
 	for (nr = i + 1; nr < npages; nr++) {
 		if (page_folio(list[nr]) != folio)
 			break;
@@ -410,6 +420,9 @@ void unpin_user_pages(struct page **pages, unsigned long npages)
 	sanity_check_pinned_pages(pages, npages);
 	for (i = 0; i < npages; i += nr) {
 		folio = gup_folio_next(pages, npages, i, &nr);
+		if (!folio)
+			continue;
+
 		gup_put_folio(folio, nr, FOLL_PIN);
 	}
 }
-- 
2.47.0



^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [PATCH] mm/gup: handle NULL pages in unpin_user_pages()
  2024-11-19  4:49 [PATCH] mm/gup: handle NULL pages in unpin_user_pages() John Hubbard
@ 2024-11-19 14:33 ` David Hildenbrand
  2024-11-20  3:28   ` John Hubbard
  0 siblings, 1 reply; 3+ messages in thread
From: David Hildenbrand @ 2024-11-19 14:33 UTC (permalink / raw)
  To: John Hubbard, Andrew Morton
  Cc: LKML, linux-mm, Oscar Salvador, Vivek Kasireddy, Dave Airlie,
	Gerd Hoffmann, Matthew Wilcox, Christoph Hellwig,
	Jason Gunthorpe, Peter Xu, Arnd Bergmann, Daniel Vetter,
	Dongwon Kim, Hugh Dickins, Junxiao Chang, stable

On 19.11.24 05:49, John Hubbard wrote:
> The recent addition of "pofs" (pages or folios) handling to gup has a
> flaw: it assumes that unpin_user_pages() handles NULL pages in the
> pages** array. That's not the case, as I discovered when I ran on a new
> configuration on my test machine.
> 
> Fix this by skipping NULL pages in unpin_user_pages(), just like
> unpin_folios() already does.
> 
> Details: when booting on x86 with "numa=fake=2 movablecore=4G" on Linux
> 6.12, and running this:
> 
>      tools/testing/selftests/mm/gup_longterm
> 
> ...I get the following crash:
> 
> BUG: kernel NULL pointer dereference, address: 0000000000000008
> RIP: 0010:sanity_check_pinned_pages+0x3a/0x2d0
> ...
> Call Trace:
>   <TASK>
>   ? __die_body+0x66/0xb0
>   ? page_fault_oops+0x30c/0x3b0
>   ? do_user_addr_fault+0x6c3/0x720
>   ? irqentry_enter+0x34/0x60
>   ? exc_page_fault+0x68/0x100
>   ? asm_exc_page_fault+0x22/0x30
>   ? sanity_check_pinned_pages+0x3a/0x2d0
>   unpin_user_pages+0x24/0xe0
>   check_and_migrate_movable_pages_or_folios+0x455/0x4b0
>   __gup_longterm_locked+0x3bf/0x820
>   ? mmap_read_lock_killable+0x12/0x50
>   ? __pfx_mmap_read_lock_killable+0x10/0x10
>   pin_user_pages+0x66/0xa0
>   gup_test_ioctl+0x358/0xb20
>   __se_sys_ioctl+0x6b/0xc0
>   do_syscall_64+0x7b/0x150
>   entry_SYSCALL_64_after_hwframe+0x76/0x7e
> 
> Fixes: 94efde1d1539 ("mm/gup: avoid an unnecessary allocation call for FOLL_LONGTERM cases")
> Cc: David Hildenbrand <david@redhat.com>
> Cc: Oscar Salvador <osalvador@suse.de>
> Cc: Vivek Kasireddy <vivek.kasireddy@intel.com>
> Cc: Dave Airlie <airlied@redhat.com>
> Cc: Gerd Hoffmann <kraxel@redhat.com>
> Cc: Matthew Wilcox <willy@infradead.org>
> Cc: Christoph Hellwig <hch@infradead.org>
> Cc: Jason Gunthorpe <jgg@nvidia.com>
> Cc: Peter Xu <peterx@redhat.com>
> Cc: Arnd Bergmann <arnd@arndb.de>
> Cc: Daniel Vetter <daniel.vetter@ffwll.ch>
> Cc: Dongwon Kim <dongwon.kim@intel.com>
> Cc: Hugh Dickins <hughd@google.com>
> Cc: Junxiao Chang <junxiao.chang@intel.com>
> Cc: <stable@vger.kernel.org>
> Signed-off-by: John Hubbard <jhubbard@nvidia.com>
> ---
> 
> Hi,
> 
> I got a nasty shock when I tried out a new test machine setup last
> night--I wish I'd noticed the problem earlier! But anyway, this should
> make it all better...
> 
> I've asked Greg K-H to hold off on including commit 94efde1d1539
> ("mm/gup: avoid an unnecessary allocation call for FOLL_LONGTERM cases")
> in linux-stable (6.11.y), but if this fix-to-the-fix looks good, then
> maybe both fixes can ultimately end up in stable.
> 

Ouch!

> thanks,
> John Hubbard
> 
>   mm/gup.c | 17 +++++++++++++++--
>   1 file changed, 15 insertions(+), 2 deletions(-)
> 
> diff --git a/mm/gup.c b/mm/gup.c
> index ad0c8922dac3..6e417502728a 100644
> --- a/mm/gup.c
> +++ b/mm/gup.c
> @@ -52,7 +52,12 @@ static inline void sanity_check_pinned_pages(struct page **pages,
>   	 */
>   	for (; npages; npages--, pages++) {
>   		struct page *page = *pages;
> -		struct folio *folio = page_folio(page);
> +		struct folio *folio;
> +
> +		if (!page)
> +			continue;
> +
> +		folio = page_folio(page);
>   
>   		if (is_zero_page(page) ||
>   		    !folio_test_anon(folio))
> @@ -248,9 +253,14 @@ static inline struct folio *gup_folio_range_next(struct page *start,
>   static inline struct folio *gup_folio_next(struct page **list,
>   		unsigned long npages, unsigned long i, unsigned int *ntails)
>   {
> -	struct folio *folio = page_folio(list[i]);
> +	struct folio *folio;
>   	unsigned int nr;
>   
> +	if (!list[i])
> +		return NULL;
> +

I don't particularly enjoy returning NULL here, if we don't teach the 
other users of that function about that possibility. There are two other 
users.

Also: we are not setting "ntails" to 1. I think the callers uses that as 
"nr" to advance npages. So the caller has to make sure to set "nr = 1" 
in case it sees "NULL".

Alternatively ...

> +	folio = page_folio(list[i]);
> +
>   	for (nr = i + 1; nr < npages; nr++) {
>   		if (page_folio(list[nr]) != folio)
>   			break;
> @@ -410,6 +420,9 @@ void unpin_user_pages(struct page **pages, unsigned long npages)
>   	sanity_check_pinned_pages(pages, npages);
>   	for (i = 0; i < npages; i += nr) {

... handle it here

if (!pages[i]) {
	nr = 1;
	continue;
}

No strong opinion. But I think we should either update all callers to 
deal with returning NULL from this function, and set "nr = 1".

-- 
Cheers,

David / dhildenb



^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [PATCH] mm/gup: handle NULL pages in unpin_user_pages()
  2024-11-19 14:33 ` David Hildenbrand
@ 2024-11-20  3:28   ` John Hubbard
  0 siblings, 0 replies; 3+ messages in thread
From: John Hubbard @ 2024-11-20  3:28 UTC (permalink / raw)
  To: David Hildenbrand, Andrew Morton
  Cc: LKML, linux-mm, Oscar Salvador, Vivek Kasireddy, Dave Airlie,
	Gerd Hoffmann, Matthew Wilcox, Christoph Hellwig,
	Jason Gunthorpe, Peter Xu, Arnd Bergmann, Daniel Vetter,
	Dongwon Kim, Hugh Dickins, Junxiao Chang, stable

On 11/19/24 6:33 AM, David Hildenbrand wrote:
> On 19.11.24 05:49, John Hubbard wrote:
>> The recent addition of "pofs" (pages or folios) handling to gup has a
>> flaw: it assumes that unpin_user_pages() handles NULL pages in the
>> pages** array. That's not the case, as I discovered when I ran on a new
>> configuration on my test machine.
>>
>> Fix this by skipping NULL pages in unpin_user_pages(), just like
>> unpin_folios() already does.
>>
>> Details: when booting on x86 with "numa=fake=2 movablecore=4G" on Linux
>> 6.12, and running this:
>>
>>      tools/testing/selftests/mm/gup_longterm
>>
>> ...I get the following crash:
>>
>> BUG: kernel NULL pointer dereference, address: 0000000000000008
>> RIP: 0010:sanity_check_pinned_pages+0x3a/0x2d0
>> ...
>> Call Trace:
>>   <TASK>
>>   ? __die_body+0x66/0xb0
>>   ? page_fault_oops+0x30c/0x3b0
>>   ? do_user_addr_fault+0x6c3/0x720
>>   ? irqentry_enter+0x34/0x60
>>   ? exc_page_fault+0x68/0x100
>>   ? asm_exc_page_fault+0x22/0x30
>>   ? sanity_check_pinned_pages+0x3a/0x2d0
>>   unpin_user_pages+0x24/0xe0
>>   check_and_migrate_movable_pages_or_folios+0x455/0x4b0
>>   __gup_longterm_locked+0x3bf/0x820
>>   ? mmap_read_lock_killable+0x12/0x50
>>   ? __pfx_mmap_read_lock_killable+0x10/0x10
>>   pin_user_pages+0x66/0xa0
>>   gup_test_ioctl+0x358/0xb20
>>   __se_sys_ioctl+0x6b/0xc0
>>   do_syscall_64+0x7b/0x150
>>   entry_SYSCALL_64_after_hwframe+0x76/0x7e
>>
>> Fixes: 94efde1d1539 ("mm/gup: avoid an unnecessary allocation call for FOLL_LONGTERM cases")
>> Cc: David Hildenbrand <david@redhat.com>
>> Cc: Oscar Salvador <osalvador@suse.de>
>> Cc: Vivek Kasireddy <vivek.kasireddy@intel.com>
>> Cc: Dave Airlie <airlied@redhat.com>
>> Cc: Gerd Hoffmann <kraxel@redhat.com>
>> Cc: Matthew Wilcox <willy@infradead.org>
>> Cc: Christoph Hellwig <hch@infradead.org>
>> Cc: Jason Gunthorpe <jgg@nvidia.com>
>> Cc: Peter Xu <peterx@redhat.com>
>> Cc: Arnd Bergmann <arnd@arndb.de>
>> Cc: Daniel Vetter <daniel.vetter@ffwll.ch>
>> Cc: Dongwon Kim <dongwon.kim@intel.com>
>> Cc: Hugh Dickins <hughd@google.com>
>> Cc: Junxiao Chang <junxiao.chang@intel.com>
>> Cc: <stable@vger.kernel.org>
>> Signed-off-by: John Hubbard <jhubbard@nvidia.com>
>> ---
>>
>> Hi,
>>
>> I got a nasty shock when I tried out a new test machine setup last
>> night--I wish I'd noticed the problem earlier! But anyway, this should
>> make it all better...
>>
>> I've asked Greg K-H to hold off on including commit 94efde1d1539
>> ("mm/gup: avoid an unnecessary allocation call for FOLL_LONGTERM cases")
>> in linux-stable (6.11.y), but if this fix-to-the-fix looks good, then
>> maybe both fixes can ultimately end up in stable.
>>
> 
> Ouch!
> 
>> thanks,
>> John Hubbard
>>
>>   mm/gup.c | 17 +++++++++++++++--
>>   1 file changed, 15 insertions(+), 2 deletions(-)
>>
>> diff --git a/mm/gup.c b/mm/gup.c
>> index ad0c8922dac3..6e417502728a 100644
>> --- a/mm/gup.c
>> +++ b/mm/gup.c
>> @@ -52,7 +52,12 @@ static inline void sanity_check_pinned_pages(struct page **pages,
>>        */
>>       for (; npages; npages--, pages++) {
>>           struct page *page = *pages;
>> -        struct folio *folio = page_folio(page);
>> +        struct folio *folio;
>> +
>> +        if (!page)
>> +            continue;
>> +
>> +        folio = page_folio(page);
>>           if (is_zero_page(page) ||
>>               !folio_test_anon(folio))
>> @@ -248,9 +253,14 @@ static inline struct folio *gup_folio_range_next(struct page *start,
>>   static inline struct folio *gup_folio_next(struct page **list,
>>           unsigned long npages, unsigned long i, unsigned int *ntails)
>>   {
>> -    struct folio *folio = page_folio(list[i]);
>> +    struct folio *folio;
>>       unsigned int nr;
>> +    if (!list[i])
>> +        return NULL;
>> +
> 
> I don't particularly enjoy returning NULL here, if we don't teach the other users of that function about that possibility. There are two other users.
> 
> Also: we are not setting "ntails" to 1. I think the callers uses that as "nr" to advance npages. So the caller has to make sure to set "nr = 1" in case it sees "NULL".
> 
> Alternatively ...
> 
>> +    folio = page_folio(list[i]);
>> +
>>       for (nr = i + 1; nr < npages; nr++) {
>>           if (page_folio(list[nr]) != folio)
>>               break;
>> @@ -410,6 +420,9 @@ void unpin_user_pages(struct page **pages, unsigned long npages)
>>       sanity_check_pinned_pages(pages, npages);
>>       for (i = 0; i < npages; i += nr) {
> 
> ... handle it here
> 
> if (!pages[i]) {
>      nr = 1;
>      continue;
> }
> 
> No strong opinion. But I think we should either update all callers to deal with returning NULL from this function, and set "nr = 1".
> 

Yes, that makes sense. I'll send a v2 shortly with one or the other
approach implemented. I appreciate the review feedback as always!

thanks,
-- 
John Hubbard



^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2024-11-20  3:29 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2024-11-19  4:49 [PATCH] mm/gup: handle NULL pages in unpin_user_pages() John Hubbard
2024-11-19 14:33 ` David Hildenbrand
2024-11-20  3:28   ` John Hubbard

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox