Pointer Compression in V8

 


1. 관련글

 

:: Pointer Compression in V8

 


2. V8 힙

 

:: 자바 스크립트로 생성한 객체, 배열, 함수는 V8 힙에 위치함.

:: 조금 더 덧붙이면, 자바 스크립트에서는 배열, 함수도 객체임.

:: V8 힙은 GDB 등의 디버거에서 [heap]으로 표시되는 메모리 영역과 다름.

:: 위에서 V8 힙은 상위 32비트가 0x00001f08로 표시되는 모든 메모리 영역으로, 보통 프로그램의 최하위 메모리 주소에 매핑됨.

:: V8에서 메모리는 모두 격리(isolate)된다고 함. 위에서 보다시피 최하위 메모리 주소에 매핑되며, 바이너리의 텍스트 세그먼트가 바로 다음에 매핑됨.

:: V8 힙의 주소는 실행될 때마다 변경되지만, 상위 32비트(0x00001f08)는 실행 중에 변경되지 않음.

 


3. 포인터 압축

 

:: 따라서, V8 힙 포인터를 저장할 때 하위 32비트만 저장하기로 함.

 

:: 이를 포인터 압축이라고 하며, 포인터 압축을 통해 힙 메모리 사용량을 40%까지 절약하였다고 함.

:: V8 힙 주소의 상위 32비트를 격리 루트(isolate root)라고 함.

:: 격리 루트는 특정 레지스터(R13)에 저장하며, 이 레지스터를 루트 레지스터라고 함.

:: 포인터 압축의 단점은 V8 힙의 사이즈가 최대 4GB(32비트만 주소로 사용)로 제한된다는 것임.

:: 이런 단점 때문에, node.js는 포인터 압축을 사용하지 않음.

:: 포인터 압축과 관련된 코드는 아래의 위치에 구현되어 있음.

  • v8/src/common/prt-compr.h
  • v8/src/common/ptr-compr-inl.h

 


4. 포인터 압축과 V8 익스플로잇

 

:: 자바 스크립트를 통해 격리 루트를 알아내는 건 어려움. 하지만, 격리 루트를 굳이 알아야할 필요는 없음.

:: addrof 또는 fakeobj가 가능하다면, 가짜 JSArray를 만든 후 엘리먼트 포인터를 수정하여 임의의 주소에 데이터를 쓰고, 읽을 수 있음.

:: JSArray의 엘리먼트 포인터에는 32비트로 압축된 포인터가 저장됨.

:: 따라서, V8 힙 내부의 주소에만 데이터를 쓰고, 읽을 수 있음.

:: 이를 극복하기 위한 기본적인 방법은 V8 힙에 ArrayBuffer를 할당하는 것임.

:: ArrayBuffer의 엘리먼트 포인터는 PartitionAlloc를 사용해서 할당되기 때문임.

:: PartitionAlloc은 V8 힙이 아닌 메모리 영역에 할당함.

:: 즉, ArrayBuffer의 엘리먼트 포인터에 압축되지 않은 64비트 포인터를 저장하여, V8 힙 이외의 메모리 영역에 접근할 수 있음.