ASA-2020-00051 – Linux kernel: vsyscall page refcounting error


Allele Security Alert

ASA-2020-00051

Identifier(s)

ASA-2020-00051, CVE-2020-25221, CID-9fa2dd946743

Title

vsyscall page refcounting error

Vendor(s)

Linux foundation

Product(s)

Linux kernel

Affected version(s)

Linux kernel version 5.7
Linux kernel versions 5.8.x before 5.8.7

Linux kernel versions since the following commit:

mm/gup: track FOLL_PIN pages
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=3faa52c03f440d1b9ddef18c4f189f4790d52d7e

Fixed version(s)

Linux kernel version 5.8.7

Linux kernel versions with the following commit applied:

mm: fix pin vs. gup mismatch with gate pages
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=9fa2dd946743ae6f30dc4830da19147bf100a7f2

Proof of concept

Yes

Description

Linux 5.7 and 5.8 have a bug in the reference counting of the struct page that backs the vsyscall page. The result is a refcount underflow. This can be triggered by any 64-bit process that is permitted to use ptrace() or process_vm_readv().

Technical details

Gate pages were missed when converting from get to pin_user_pages(). This can lead to refcount imbalances. This is reliably and quickly reproducible running the x86 selftests when vsyscall=emulate is enabled (the default). The fix is done by using try_grab_page() with appropriate flags passed.

Today, pin_user_pages() and get_user_pages() are similar interfaces for manipulating page reference counts. However, “pins” use a “bias” value and manipulate the actual reference count by 1024 instead of 1 used by plain “gets”.

That means that pin_user_pages() must be matched with unpin_user_pages() and can’t be mixed with a plain put_user_pages() or put_page().

Enter gate pages, like the vsyscall page. They are pages usually in the kernel image, but which are mapped to userspace. Userspace is allowed access to them, including interfaces using get/pin_user_pages(). The refcount of these kernel pages is manipulated just like a normal user
page on the get/pin side so that the put/unpin side can work the same for normal user pages or gate pages.

get_gate_page() uses try_get_page() which only bumps the refcount by 1, not 1024, even if called in the pin_user_pages() path. If someone pins a gate page, this happens:

pin_user_pages()
get_gate_page()
try_get_page() // bump refcount +1
... some time later
unpin_user_pages()
page_ref_sub_and_test(page, 1024))

this results in a refcount off by 1023. This is reliably and quickly reproducible running the x86 selftests when booted with vsyscall=emulate (the default).

To fix it, simply use try_grab_page() instead of try_get_page(), and pass ‘gup_flags’ in so that FOLL_PIN can be respected.

Credits

Peter Zijlstra

Reference(s)

oss-security – CVE Request: Linux kernel vsyscall page refcounting error
https://www.openwall.com/lists/oss-security/2020/09/08/4

test_vsyscall.c
https://github.com/torvalds/linux/blob/master/tools/testing/selftests/x86/test_vsyscall.c

mm: fix pin vs. gup mismatch with gate pages
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=9fa2dd946743ae6f30dc4830da19147bf100a7f2

mm: fix pin vs. gup mismatch with gate pages
https://github.com/torvalds/linux/commit/9fa2dd946743ae6f30dc4830da19147bf100a7f2

mm/gup: track FOLL_PIN pages
https://github.com/torvalds/linux/commit/3faa52c03f440d1b9ddef18c4f189f4790d52d7e

mm/gup: track FOLL_PIN pages
https://github.com/torvalds/linux/commit/3faa52c03f440d1b9ddef18c4f189f4790d52d7e

Linux 5.8.7
https://cdn.kernel.org/pub/linux/kernel/v5.x/ChangeLog-5.8.7

CVE-2020-25221 - Red Hat Customer Portal
https://access.redhat.com/security/cve/CVE-2020-25221

CVE-2020-25221 in Ubuntu
https://people.canonical.com/~ubuntu-security/cve/CVE-2020-25221.html

CVE-2020-25221 | SUSE
https://www.suse.com/security/cve/CVE-2020-25221

CVE-2020-25221
https://security-tracker.debian.org/tracker/CVE-2020-25221

CVE-2020-25221
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-25221

CVE-2020-25221
https://nvd.nist.gov/vuln/detail/CVE-2020-25221

If there is any error in this alert or you wish a comprehensive analysis, let us know.

Last modified: September 16, 2020

We are not responsible for any data loss, device corruption or any other type of issue due to the use of any information mentioned in our security alerts.