[하루한줄] CVE-2021-25415 : 삼성 모바일 제품의 RKP memory remapping 취약점

URL

Attacking Samsung RKP

Target

  • SMR JUN-2021 Release 1 이전 RKP

Explain

모바일에서의 sercurity hypervisor는 런타임에 커널 무결성을 보장해 커널 취약점이 발생하더라도 취약점을 이용한 권한 상승이나 코드 실행을 방지합니다. 삼성은 RKP(Real-time Kernel Protection)으로 security hypervisor를 구현했으며 RKP의 아키텍처 개요와 주요 보안 기능은 링크에서 확인할 수 있습니다.

ARMv8의 exception model은 네 가지 레벨에서 프로세스가 동작합니다.

EL0(유저 APP) < EL1(커널) < EL2(하이퍼바이저) < EL3(TrustZone Monitor)

삼성 RKP는 EL1이 손상되어도 EL2로의 메모리 권한 변경을 방지하기 위해 유효성 검사가 있지만, 이 유효성 검사가 충분하지 않아 EL2 메모리를 쓰기 가능한 메모리로 매핑할 수 있는 취약점이 발견되었습니다.

취약점은 RKP가 rkp_s2_page_change_permission 또는 rkp_s2_range_change_permission 함수에서 단일 페이지 혹은 주소 범위 메모리의 권한을 변경할 때 발생합니다.

int64_t rkp_s2_page_change_permission(void* p_addr, uint64_t access, uint32_t exec, uint32_t allow) {
  // ...
  if (is_phys_map_s2unmap(p_addr)) {  // page must not be marked S2UNMAP in the physmap
    rkp_policy_violation("Error page was s2 unmapped before %p", p_addr);
    return -1;
  }
  if (map_s2_page(p_addr, p_addr, 0x1000, attrs) < 0) {
    rkp_policy_violation("map_s2_page failed, p_addr : %p, attrs : %d", p_addr, attrs);
    return -1;
  }
  tlbivaae1is(((p_addr + 0x80000000) | 0xFFFFFFC000000000) >> 12);
  return rkp_set_pgt_bitmap(p_addr, access);
}

int64_t rkp_s2_range_change_permission(uint64_t start_addr, uint64_t end_addr, uint64_t access,  uint32_t execuint32_t allow) {
  // ...
  p_addr_start = start_addr;
  if (s2_map(start_addr, end_addr - start_addr, attrs, &p_addr_start) < 0) {
    uh_log('L', "rkp_paging.c", 222, "s2_map returned false, p_addr_start : %p, size : %p", p_start_addr, size);
    return -1;
  }
  if (start_addr == end_addr)
    return 0;
  addr = start_addr;
  do {
    res = rkp_set_pgt_bitmap(addr, access);
    if (res < 0) {
      uh_log('L', "rkp_paging.c", 229, "set_pgt_bitmap fail, %p", addr);
      return res;
    }
    tlbivaae1is(((addr + 0x80000000) | 0xFFFFFFC000000000) >> 12);
    addr += 0x1000;
  } while (addr < end_addr);
  return 0;
}

rkp_s2_page_change_permission는 physmap의 S2UNMAP이 설정되지 않아야 한다는 조건을 확인한 다음 map_s2_page를 호출해 페이지 단위 매핑을 수행합니다. 그러나rkp_s2_range_change_permission는 이를 확인하지 않고 s2_map을 호출해 주소 범위 단위로 매핑을 수행합니다.

매핑 해제도 매핑과 비슷하게 수행하며 아래는 주소 범위의 매핑을 해제하는 s2_unmap 함수입니다.

int64_t s2_unmap(uint64_t orig_addr, uint64_t orig_size) {
  // ...

  addr = orig_addr & 0xFFFFFFFFFFFFF000;
  size = (orig_addr & 0xFFF) + orig_size;
  if (!size)
    return 0;
  while (size > 0x3FFFFFFF && (addr & 0x3FFFFFFF) == 0) {
    if (unmap_s2_page(addr, 0x40000000)) {
      uh_log('L', "s2.c", 1175, "unable to unmap 1gb s2 page: %p", addr);
      return -1;
    }
    size -= 0x40000000;
    addr += 0x40000000;
    if (!size)
      return 0;
  }
  while (size > 0x1FFFFF && (addr & 0x1FFFFF) == 0) {
    if (unmap_s2_page(addr, 0x200000)) {
      uh_log('L', "s2.c", 1183, "unable to unmap 2mb s2 page: %p", addr);
      return -1;
    }
    size -= 0x200000;
    addr += 0x200000;
    if (!size)
      return 0;
  }
  while (size > 0xFFF && (addr & 0xFFF) == 0) {
    if (unmap_s2_page(addr, 0x1000)) {
      uh_log('L', "s2.c", 1191, "unable to unmap 4kb s2 page: %p", addr);
      return -1;
    }
    size -= 0x1000;
    addr += 0x1000;
    if (!size)
      return 0;
  }
  return 0;
}

s2_unmaps2_map과 유사하게 unmap_s2_page를 여러번 호출해 주소 범위의 페이지를 매핑 해제합니다. 이 과정에서 페이지를 physmap에서 S2UNMAP으로 설정하는 rkp_phys_map_set, rkp_phys_map_set_region , sparsemap_set_value_addr에 대한 호출이 없어 이후 하이퍼바이저 메모리를 커널 권한으로 쓰기 가능한 메모리에 다시 매핑할 수 있습니다.

해당 취약점 외에도 CVE-2021-25411(실행 가능한 커널 페이지 생성), CVE-2021-25416(read-only 권한 커널 write) 등의 취약점의 정보 또한 공개되었습니다.