Google Android - 'rkp_set_init_page_ro' RKP Memory Corruption

EDB-ID:

41232

CVE:





Platform:

Android

Date:

2017-02-02


Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=984

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.

During the initialization of RKP, a special command can be issued by EL1 kernel in order to mark the RKP read-only page as such in the stage 2 translation table. This command, "rkp_set_init_page_ro" (command code 0x51) has the following approximate high-level logic:

__int64 rkp_set_init_page_ro(struct args* args_buffer)
{
  unsigned long page_pa = rkp_get_pa(args_buffer->arg0);
  if ( page_pa < rkp_get_pa(text) || page_pa >= rkp_get_pa(etext) )
  {
    if ( !rkp_s2_page_change_permission(page_pa, 128LL, 0, 0) )// RO, XN
      return rkp_debug_log("Cred: Unable to set permission for init cred", 0LL, 0LL, 0LL);
  }
  else
  {
    rkp_debug_log("Good init CRED is within RO range", 0LL, 0LL, 0LL);
  }
  rkp_debug_log("init cred page", 0LL, 0LL, 0LL);
  return rkp_set_pgt_bitmap(page_pa, 0);
}

As we can see above, the function receives an address in the kernel VAS, and converts it to a physical address by adding a constant offset to it (the virt_to_phys offset for the kernel VAS). Then, the function proceeds to mark the resulting physical address as read-only and non-executable in the stage 2 translation table. Finally, the function proceeds to unset the bit in the RKP page-table bitmap corresponding to the given address. This is meant to indicate to EL1 that the address is protected by a stage 2 mapping.

However, the function fails to validate the bounds of the given virtual address (or the resulting physical address). This means that an attacker can supply any arbitrary address and the function will accept it as valid input. Similarly, the implementation of "rkp_set_pgt_bitmap" performs no such validations:

signed __int64 __fastcall rkp_set_pgt_bitmap(__int64 phys_addr, unsigned char set_or_unset)
{
  unsigned long phys_off = phys_addr - 0x80000000LL;
  unsigned long bitmap_index = (phys_off >> 18) & 0x3FFFFFFFFFFFLL;
  if ( !rkp_pgt_bitmap )
    return 0LL;
  unsigned long bit_offset = (phys_off >> 12) & 0x3F;
  if ( set_or_unset & 0x80 )
  {
    spin_lock(&rkp_bitmap_spinlock);
    *(rkp_pgt_bitmap + 8 * bitmap_index) |= 1LL << bit_offset;
    spin_unlock(&rkp_bitmap_spinlock);
    result = 1LL;
  }
  else
  {
    spin_lock(&rkp_bitmap_spinlock);
    *(rkp_pgt_bitmap + 8 * bitmap_index) &= ~(1LL << bit_offset);
    spin_unlock(&rkp_bitmap_spinlock);
    result = 1LL;
  }
  return result;
}

The RKP page-table bitmap is only 0x20000 bytes large (each bit denotes a 4KB page, resulting in a supported range of at-most 0x100000000 bytes). The base physical address for the bitmap is the physical base address for the kernel range - 0x80000000.

This means that if an attacker supplies any virtual address that is converted to a physical address not in the range of 0x80000000-0x180000000, the resulting "bitmap_index" will not be within the bitmap's bounds, causing the function to modify a bit out-of-bounds.

An attacker can use this in order to specifically craft an input virtual address so that the resulting calculated "bitmap_index" will have any arbitrary value, thus resulting in a modification at an arbitrary offset from the base of the page-table bitmap, within the context of RKP.

As the bitmap resides directly before RKP's code, an attacker can trivially use this primitive in order to modify the code or data pages belonging to RKP, thus gaining privilege escalation from EL1 to the context of RKP.

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 memory corruption in "rkp_set_init_page_ro".

This PoC modifies an instruction within RKP's address space by repeatedly calling "rkp_set_init_page_ro" with faulty input addresses.


Proof of Concept:
https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/41232.zip