kernel: backport fix for a page pool related race condition
Signed-off-by: Felix Fietkau <nbd@nbd.name>
This commit is contained in:
		| @@ -0,0 +1,85 @@ | ||||
| From: Liang Chen <liangchen.linux@gmail.com> | ||||
| Date: Thu, 13 Apr 2023 17:03:53 +0800 | ||||
| Subject: [PATCH] skbuff: Fix a race between coalescing and releasing SKBs | ||||
|  | ||||
| Commit 1effe8ca4e34 ("skbuff: fix coalescing for page_pool fragment | ||||
| recycling") allowed coalescing to proceed with non page pool page and page | ||||
| pool page when @from is cloned, i.e. | ||||
|  | ||||
| to->pp_recycle    --> false | ||||
| from->pp_recycle  --> true | ||||
| skb_cloned(from)  --> true | ||||
|  | ||||
| However, it actually requires skb_cloned(@from) to hold true until | ||||
| coalescing finishes in this situation. If the other cloned SKB is | ||||
| released while the merging is in process, from_shinfo->nr_frags will be | ||||
| set to 0 toward the end of the function, causing the increment of frag | ||||
| page _refcount to be unexpectedly skipped resulting in inconsistent | ||||
| reference counts. Later when SKB(@to) is released, it frees the page | ||||
| directly even though the page pool page is still in use, leading to | ||||
| use-after-free or double-free errors. So it should be prohibited. | ||||
|  | ||||
| The double-free error message below prompted us to investigate: | ||||
| BUG: Bad page state in process swapper/1  pfn:0e0d1 | ||||
| page:00000000c6548b28 refcount:-1 mapcount:0 mapping:0000000000000000 | ||||
| index:0x2 pfn:0xe0d1 | ||||
| flags: 0xfffffc0000000(node=0|zone=1|lastcpupid=0x1fffff) | ||||
| raw: 000fffffc0000000 0000000000000000 ffffffff00000101 0000000000000000 | ||||
| raw: 0000000000000002 0000000000000000 ffffffffffffffff 0000000000000000 | ||||
| page dumped because: nonzero _refcount | ||||
|  | ||||
| CPU: 1 PID: 0 Comm: swapper/1 Tainted: G            E      6.2.0+ | ||||
| Call Trace: | ||||
|  <IRQ> | ||||
| dump_stack_lvl+0x32/0x50 | ||||
| bad_page+0x69/0xf0 | ||||
| free_pcp_prepare+0x260/0x2f0 | ||||
| free_unref_page+0x20/0x1c0 | ||||
| skb_release_data+0x10b/0x1a0 | ||||
| napi_consume_skb+0x56/0x150 | ||||
| net_rx_action+0xf0/0x350 | ||||
| ? __napi_schedule+0x79/0x90 | ||||
| __do_softirq+0xc8/0x2b1 | ||||
| __irq_exit_rcu+0xb9/0xf0 | ||||
| common_interrupt+0x82/0xa0 | ||||
| </IRQ> | ||||
| <TASK> | ||||
| asm_common_interrupt+0x22/0x40 | ||||
| RIP: 0010:default_idle+0xb/0x20 | ||||
|  | ||||
| Fixes: 53e0961da1c7 ("page_pool: add frag page recycling support in page pool") | ||||
| Signed-off-by: Liang Chen <liangchen.linux@gmail.com> | ||||
| Reviewed-by: Eric Dumazet <edumazet@google.com> | ||||
| Link: https://lore.kernel.org/r/20230413090353.14448-1-liangchen.linux@gmail.com | ||||
| Signed-off-by: Jakub Kicinski <kuba@kernel.org> | ||||
| --- | ||||
|  | ||||
| --- a/net/core/skbuff.c | ||||
| +++ b/net/core/skbuff.c | ||||
| @@ -5208,18 +5208,18 @@ bool skb_try_coalesce(struct sk_buff *to | ||||
|  	if (skb_cloned(to)) | ||||
|  		return false; | ||||
|   | ||||
| -	/* In general, avoid mixing slab allocated and page_pool allocated | ||||
| -	 * pages within the same SKB. However when @to is not pp_recycle and | ||||
| -	 * @from is cloned, we can transition frag pages from page_pool to | ||||
| -	 * reference counted. | ||||
| -	 * | ||||
| -	 * On the other hand, don't allow coalescing two pp_recycle SKBs if | ||||
| -	 * @from is cloned, in case the SKB is using page_pool fragment | ||||
| +	/* In general, avoid mixing page_pool and non-page_pool allocated | ||||
| +	 * pages within the same SKB. Additionally avoid dealing with clones | ||||
| +	 * with page_pool pages, in case the SKB is using page_pool fragment | ||||
|  	 * references (PP_FLAG_PAGE_FRAG). Since we only take full page | ||||
|  	 * references for cloned SKBs at the moment that would result in | ||||
|  	 * inconsistent reference counts. | ||||
| +	 * In theory we could take full references if @from is cloned and | ||||
| +	 * !@to->pp_recycle but its tricky (due to potential race with | ||||
| +	 * the clone disappearing) and rare, so not worth dealing with. | ||||
|  	 */ | ||||
| -	if (to->pp_recycle != (from->pp_recycle && !skb_cloned(from))) | ||||
| +	if (to->pp_recycle != from->pp_recycle || | ||||
| +	    (from->pp_recycle && skb_cloned(from))) | ||||
|  		return false; | ||||
|   | ||||
|  	if (len <= skb_tailroom(to)) { | ||||
| @@ -0,0 +1,85 @@ | ||||
| From: Liang Chen <liangchen.linux@gmail.com> | ||||
| Date: Thu, 13 Apr 2023 17:03:53 +0800 | ||||
| Subject: [PATCH] skbuff: Fix a race between coalescing and releasing SKBs | ||||
|  | ||||
| Commit 1effe8ca4e34 ("skbuff: fix coalescing for page_pool fragment | ||||
| recycling") allowed coalescing to proceed with non page pool page and page | ||||
| pool page when @from is cloned, i.e. | ||||
|  | ||||
| to->pp_recycle    --> false | ||||
| from->pp_recycle  --> true | ||||
| skb_cloned(from)  --> true | ||||
|  | ||||
| However, it actually requires skb_cloned(@from) to hold true until | ||||
| coalescing finishes in this situation. If the other cloned SKB is | ||||
| released while the merging is in process, from_shinfo->nr_frags will be | ||||
| set to 0 toward the end of the function, causing the increment of frag | ||||
| page _refcount to be unexpectedly skipped resulting in inconsistent | ||||
| reference counts. Later when SKB(@to) is released, it frees the page | ||||
| directly even though the page pool page is still in use, leading to | ||||
| use-after-free or double-free errors. So it should be prohibited. | ||||
|  | ||||
| The double-free error message below prompted us to investigate: | ||||
| BUG: Bad page state in process swapper/1  pfn:0e0d1 | ||||
| page:00000000c6548b28 refcount:-1 mapcount:0 mapping:0000000000000000 | ||||
| index:0x2 pfn:0xe0d1 | ||||
| flags: 0xfffffc0000000(node=0|zone=1|lastcpupid=0x1fffff) | ||||
| raw: 000fffffc0000000 0000000000000000 ffffffff00000101 0000000000000000 | ||||
| raw: 0000000000000002 0000000000000000 ffffffffffffffff 0000000000000000 | ||||
| page dumped because: nonzero _refcount | ||||
|  | ||||
| CPU: 1 PID: 0 Comm: swapper/1 Tainted: G            E      6.2.0+ | ||||
| Call Trace: | ||||
|  <IRQ> | ||||
| dump_stack_lvl+0x32/0x50 | ||||
| bad_page+0x69/0xf0 | ||||
| free_pcp_prepare+0x260/0x2f0 | ||||
| free_unref_page+0x20/0x1c0 | ||||
| skb_release_data+0x10b/0x1a0 | ||||
| napi_consume_skb+0x56/0x150 | ||||
| net_rx_action+0xf0/0x350 | ||||
| ? __napi_schedule+0x79/0x90 | ||||
| __do_softirq+0xc8/0x2b1 | ||||
| __irq_exit_rcu+0xb9/0xf0 | ||||
| common_interrupt+0x82/0xa0 | ||||
| </IRQ> | ||||
| <TASK> | ||||
| asm_common_interrupt+0x22/0x40 | ||||
| RIP: 0010:default_idle+0xb/0x20 | ||||
|  | ||||
| Fixes: 53e0961da1c7 ("page_pool: add frag page recycling support in page pool") | ||||
| Signed-off-by: Liang Chen <liangchen.linux@gmail.com> | ||||
| Reviewed-by: Eric Dumazet <edumazet@google.com> | ||||
| Link: https://lore.kernel.org/r/20230413090353.14448-1-liangchen.linux@gmail.com | ||||
| Signed-off-by: Jakub Kicinski <kuba@kernel.org> | ||||
| --- | ||||
|  | ||||
| --- a/net/core/skbuff.c | ||||
| +++ b/net/core/skbuff.c | ||||
| @@ -5397,18 +5397,18 @@ bool skb_try_coalesce(struct sk_buff *to | ||||
|  	if (skb_cloned(to)) | ||||
|  		return false; | ||||
|   | ||||
| -	/* In general, avoid mixing slab allocated and page_pool allocated | ||||
| -	 * pages within the same SKB. However when @to is not pp_recycle and | ||||
| -	 * @from is cloned, we can transition frag pages from page_pool to | ||||
| -	 * reference counted. | ||||
| -	 * | ||||
| -	 * On the other hand, don't allow coalescing two pp_recycle SKBs if | ||||
| -	 * @from is cloned, in case the SKB is using page_pool fragment | ||||
| +	/* In general, avoid mixing page_pool and non-page_pool allocated | ||||
| +	 * pages within the same SKB. Additionally avoid dealing with clones | ||||
| +	 * with page_pool pages, in case the SKB is using page_pool fragment | ||||
|  	 * references (PP_FLAG_PAGE_FRAG). Since we only take full page | ||||
|  	 * references for cloned SKBs at the moment that would result in | ||||
|  	 * inconsistent reference counts. | ||||
| +	 * In theory we could take full references if @from is cloned and | ||||
| +	 * !@to->pp_recycle but its tricky (due to potential race with | ||||
| +	 * the clone disappearing) and rare, so not worth dealing with. | ||||
|  	 */ | ||||
| -	if (to->pp_recycle != (from->pp_recycle && !skb_cloned(from))) | ||||
| +	if (to->pp_recycle != from->pp_recycle || | ||||
| +	    (from->pp_recycle && skb_cloned(from))) | ||||
|  		return false; | ||||
|   | ||||
|  	if (len <= skb_tailroom(to)) { | ||||
		Reference in New Issue
	
	Block a user
	 Felix Fietkau
					Felix Fietkau