New issue
Advanced search Search tips
Note: Color blocks (like or ) mean that a user may not be available. Tooltip shows the reason.

Issue 981 link

Starred by 2 users

Issue metadata

Status: Fixed
Last visit > 30 days ago
Closed: Jan 2017

Sign in to add a comment

Samsung: RKP EL1 Code Loading Bypass

Reported by, Nov 2 2016

Issue description

As part of Samsung KNOX, Samsung phones include a security hypervisor called RKP (Real-time Kernel Protection), running in EL2. This hypervisor is meant to ensure that the HLOS kernel running in EL1 remains protected from exploits and aims to prevent privilege escalation attacks by "shielding" certain data structures within the hypervisor.

One of the protections implemented by RKP is a security policy meant to ensure that only the "authentic" kernel code pages are executable from EL1. This mitigation is achieved by combining a few memory protection policies together, namely:
  -All pages with the exception of the kernel code are marked PXN
  -All kernel code pages are marked read-only in the stage 2 translation table
  -Kernel data pages are never marked executable
  -Kernel code pages are never marked writable
(for more information, see

In order to explore this mitigation technique, I've written a small tool to dump the stage 1 and stage 2 translation tables for EL0/EL1.  First, the initial stage 2 translation table is embedded in the VMM code, so it can be statically retrieved and analysed. Here is a short snippet from the initial stage 2 translation table (the addresses here are PAs, although RKP implements a one-to-one PA<->IPA translation, barring memory protections):

0x80000000-0x80200000: S2AP=11, XN=0
0x80200000-0x80400000: S2AP=11, XN=0
0x80400000-0x80600000: S2AP=11, XN=0
0x80600000-0x80800000: S2AP=11, XN=0
0x80800000-0x80a00000: S2AP=11, XN=0
0x80a00000-0x80c00000: S2AP=11, XN=0
0x80c00000-0x80e00000: S2AP=11, XN=0
0x80e00000-0x81000000: S2AP=11, XN=0
0x81000000-0x81200000: S2AP=11, XN=0
0x81200000-0x81400000: S2AP=11, XN=0
0x81400000-0x81600000: S2AP=11, XN=0

The physical address range above corresponds with the physical address range in which the kernel code is located. As can be seen above, this entire address range is mapped as RWX in the initial stage 2. However, obviously RKP does not leave this area unprotected, as this might allow an attacker to subvert the kernel's integrity (by writing to the kernel's code pages). When RKP is initialized (i.e., when the HVC command RKP_INIT is called from EL1), the HLOS kernel passes a structure containing the address ranges for the currently loaded kernel. Here is a short snippet from "rkp_init" (init/main.c):

static void rkp_init(void)
    rkp_init_t init;
    init.magic = RKP_INIT_MAGIC;
    init.vmalloc_start = VMALLOC_START;
    init.vmalloc_end = (u64)high_memory;
    init.init_mm_pgd = (u64)__pa(swapper_pg_dir);
    init.id_map_pgd = (u64)__pa(idmap_pg_dir);
    init.rkp_pgt_bitmap = (u64)__pa(rkp_pgt_bitmap);
    init.rkp_map_bitmap = (u64)__pa(rkp_map_bitmap);
    init.rkp_pgt_bitmap_size = RKP_PGT_BITMAP_LEN;
    init.zero_pg_addr = page_to_phys(empty_zero_page);
    init._text = (u64) _text;
    init._etext = (u64) _etext;
    if (!vmm_extra_mem) {
        printk(KERN_ERR"Disable RKP: Failed to allocate extra mem\n");
    init.extra_memory_addr = __pa(vmm_extra_mem);
    init.extra_memory_size = 0x600000;
    init._srodata = (u64) __start_rodata;
    init._erodata =(u64) __end_rodata;
    init.large_memory = rkp_support_large_memory;

    rkp_call(RKP_INIT, (u64)&init, 0, 0, 0, 0);

Upon receiving this command, RKP changes the stage 2 permissions for the address range corresponding to the kernel text (from "init._text" to "init._etext") to read-only and executable, like so:

kernel_text_phys_start = rkp_get_pa(text);
kernel_text_phys_end = rkp_get_pa(etext);
rkp_debug_log("DEFERRED INIT START", 0LL, 0LL, 0LL);
if ( etext & 0x1FFFFF )
    rkp_debug_log("Kernel range is not aligned", 0LL, 0LL, 0LL);
if ( !rkp_s2_range_change_permission(kernel_text_phys_start, kernel_text_phys_end, 128LL, 1, 1) )
    rkp_debug_log("Failed to make Kernel range RO", 0LL, 0LL, 0LL);
rkp_l1pgt_process_table(init_mm_pgd, 1u, 1u);

However, notice that the code above only marks the region from _text to _etext as read-only. This region is *strictly smaller* than the physical address range reserved for the kernel text region (in part in order to account for RKP's KASLR slide, which means the kernel can be placed at several offsets within this region). If we take a look at the stage 1 translation table from TTBR1_EL1, we can see that the kernel code pages are allocated using L2 block descriptors (i.e., a large granularity), like so:

[256] L1 table [PXNTable: 0, APTable: 0]
  [  0] 0x080000000-0x080200000 [PXN: 0, UXN: 1, AP: 0]
  [  1] 0x080200000-0x080400000 [PXN: 0, UXN: 1, AP: 0]
  [  2] 0x080400000-0x080600000 [PXN: 0, UXN: 1, AP: 0]
  [  3] 0x080600000-0x080800000 [PXN: 0, UXN: 1, AP: 0]
  [  4] 0x080800000-0x080a00000 [PXN: 0, UXN: 1, AP: 0]
  [  5] 0x080a00000-0x080c00000 [PXN: 0, UXN: 1, AP: 0]
  [  6] 0x080c00000-0x080e00000 [PXN: 0, UXN: 1, AP: 0]
  [  7] 0x080e00000-0x081000000 [PXN: 0, UXN: 1, AP: 0]
  [  8] 0x081000000-0x081200000 [PXN: 0, UXN: 1, AP: 0]
  [  9] 0x081200000-0x081400000 [PXN: 0, UXN: 1, AP: 0]
  [ 10] 0x081400000-0x081600000 [PXN: 1, UXN: 1, AP: 0]
Moreover, as we can see above, the region 0x080000000-0x081400000 is marked as RWX in the stage 1 translation table, even though the kernel code pages only take up a much smaller area within this region.

Combining these facts, we arrive at the conclusion that any address in the range 0x080000000-"_text" or "_etext"-0x081400000 are marked as RWX both in stage 1 and stage 2, even after RKP is initialized.

This issue can be reproduced by simply writing code to any of these memory regions in EL1 and executing it directly (e.g., writing code to address 0xffffffc000000000 in the kernel's VAS).

This bug is subject to a 90 day disclosure deadline. If 90 days elapse
without a broadly available patch, then the bug report will automatically
become visible to the public.
I've verified this issue on an SM-G935F device, build version "XXS1APG3". The RKP version present on the device is "RKP4.2_CL7572479".
Proof of concept for the RKP EL1 code-loading bypass.

Writes a small executable stub and executes it in EL1.
6.4 KB Download
Adding the stage 2 translation table parser script.
1.6 KB Download
Labels: -Restrict-View-Commit SVE-2016-7897
Status: Fixed (was: New)
Fixed in January SMR, verification pending.
Summary: Samsung: RKP EL1 Code Loading Bypass (was: Android: RKP EL1 Code Loading Bypass)

Sign in to add a comment