CVE-2021-47635
Publication date:
26/02/2025
In the Linux kernel, the following vulnerability has been resolved:<br />
<br />
ubifs: Fix to add refcount once page is set private<br />
<br />
MM defined the rule [1] very clearly that once page was set with PG_private<br />
flag, we should increment the refcount in that page, also main flows like<br />
pageout(), migrate_page() will assume there is one additional page<br />
reference count if page_has_private() returns true. Otherwise, we may<br />
get a BUG in page migration:<br />
<br />
page:0000000080d05b9d refcount:-1 mapcount:0 mapping:000000005f4d82a8<br />
index:0xe2 pfn:0x14c12<br />
aops:ubifs_file_address_operations [ubifs] ino:8f1 dentry name:"f30e"<br />
flags: 0x1fffff80002405(locked|uptodate|owner_priv_1|private|node=0|<br />
zone=1|lastcpupid=0x1fffff)<br />
page dumped because: VM_BUG_ON_PAGE(page_count(page) != 0)<br />
------------[ cut here ]------------<br />
kernel BUG at include/linux/page_ref.h:184!<br />
invalid opcode: 0000 [#1] SMP<br />
CPU: 3 PID: 38 Comm: kcompactd0 Not tainted 5.15.0-rc5<br />
RIP: 0010:migrate_page_move_mapping+0xac3/0xe70<br />
Call Trace:<br />
ubifs_migrate_page+0x22/0xc0 [ubifs]<br />
move_to_new_page+0xb4/0x600<br />
migrate_pages+0x1523/0x1cc0<br />
compact_zone+0x8c5/0x14b0<br />
kcompactd+0x2bc/0x560<br />
kthread+0x18c/0x1e0<br />
ret_from_fork+0x1f/0x30<br />
<br />
Before the time, we should make clean a concept, what does refcount means<br />
in page gotten from grab_cache_page_write_begin(). There are 2 situations:<br />
Situation 1: refcount is 3, page is created by __page_cache_alloc.<br />
TYPE_A - the write process is using this page<br />
TYPE_B - page is assigned to one certain mapping by calling<br />
__add_to_page_cache_locked()<br />
TYPE_C - page is added into pagevec list corresponding current cpu by<br />
calling lru_cache_add()<br />
Situation 2: refcount is 2, page is gotten from the mapping&#39;s tree<br />
TYPE_B - page has been assigned to one certain mapping<br />
TYPE_A - the write process is using this page (by calling<br />
page_cache_get_speculative())<br />
Filesystem releases one refcount by calling put_page() in xxx_write_end(),<br />
the released refcount corresponds to TYPE_A (write task is using it). If<br />
there are any processes using a page, page migration process will skip the<br />
page by judging whether expected_page_refs() equals to page refcount.<br />
<br />
The BUG is caused by following process:<br />
PA(cpu 0) kcompactd(cpu 1)<br />
compact_zone<br />
ubifs_write_begin<br />
page_a = grab_cache_page_write_begin<br />
add_to_page_cache_lru<br />
lru_cache_add<br />
pagevec_add // put page into cpu 0&#39;s pagevec<br />
(refcnf = 3, for page creation process)<br />
ubifs_write_end<br />
SetPagePrivate(page_a) // doesn&#39;t increase page count !<br />
unlock_page(page_a)<br />
put_page(page_a) // refcnt = 2<br />
[...]<br />
<br />
PB(cpu 0)<br />
filemap_read<br />
filemap_get_pages<br />
add_to_page_cache_lru<br />
lru_cache_add<br />
__pagevec_lru_add // traverse all pages in cpu 0&#39;s pagevec<br />
__pagevec_lru_add_fn<br />
SetPageLRU(page_a)<br />
isolate_migratepages<br />
isolate_migratepages_block<br />
get_page_unless_zero(page_a)<br />
// refcnt = 3<br />
list_add(page_a, from_list)<br />
migrate_pages(from_list)<br />
__unmap_and_move<br />
move_to_new_page<br />
ubifs_migrate_page(page_a)<br />
migrate_page_move_mapping<br />
expected_page_refs get 3<br />
(migration[1] + mapping[1] + private[1])<br />
release_pages<br />
put_page_testzero(page_a) // refcnt = 3<br />
page_ref_freeze // refcnt = 0<br />
page_ref_dec_and_test(0 - 1 = -1)<br />
page_ref_unfreeze<br />
VM_BUG_ON_PAGE(-1 != 0, page)<br />
<br />
UBIFS doesn&#39;t increase the page refcount after setting private flag, which<br />
leads to page migration task believes the page is not used by any other<br />
processes, so the page is migrated. This causes concurrent accessing on<br />
page refcount between put_page() called by other process(eg. read process<br />
calls lru_cache_add) and page_ref_unfreeze() called by mi<br />
---truncated---
Severity CVSS v4.0: Pending analysis
Last modification:
23/09/2025