RenderFrameHost
Chrome Browser
는 site isolation
기능을 사용해서 한 탭마다 하나의 Process
가 맵핑되게 한다. 하나의 Chrome Process
는 Browser Process
와 Renderer Process
로 나눌 수 있다.
Renderer Process
에는 여러 프레임들이 로딩될 수 있다. Chrome Process
는 HTML, CSS, JavaScript등의 구문을 분석하여 Main Frame
에 표시한다.
이 Renderer
의 Main Frame
을 Browser Process
가 추적할 수 있게 도와주는 것이 바로 RenderFrameHost
객체이다.
하나의 Frame
만 로딩된다면 덜 복잡하겠지만, iframe과 같이 하나의 Renderer Process
밑에 여러 하위 Frame들이 로딩될 수 있다.
만약 iframe
으로 불러온 하위 Frame이 Main Frame
에서 불러온 것이라면 단순히 Frame 객체를 생성하지만, 만약 다른 곳에서 불러온 것이라면 site isolation
에 의해 새로운 Renderer Process
가 생성된다.
Mojo
Chromium
에는 두 가지 IPC가 존재한다. 레거시 IPC
와 Mojo IPC
가 있는데 요즘은 대부분 Mojo IPC
를 이용해 Renderer Process
와 Browser Process
가 통신을 수행한다. Mojo Interface
는 Frame별로 바인딩된다. 즉, iframe이 생성되면 Renderer Process
단에서 새로운 Mojo 바인딩
를 요청하고 Browser Process
에서 새로운 Mojo Interface 객체
를 할당해준다.
Mojo를 이용한다면 웹 페이지에 직접적인 접근 없이 샌드박스에서 허용하지 않은 작업에 대해서 쉽게 접근할 수 있다. 그러나 Mojo Interface 객체
가 RenderFrameHost의 WebContentsImpl 객체나 RenderFrameProcess 객채
에 직접 접근해야 할 때가 있다. 이는 Mojo Interface 생성자
에서 Interface를 인스턴스화한 RenderFrameHost
에 대한 raw pointer
를 제공하는 것이다. 이렇게 하면 SensorProviderProxyImpl 생성자
에서 멤버 변수로 들고 갈 수 있다. 해당 동작은 SensorProviderProxyImpl
에서 확인할 수 있다.
1 |
|
여기서 한 가지 의문을 가져야 한다. SensorProviderProxyImpl
객체에서 RenderFrameHost
에 대한 raw pointer
를 사용하고 있는데, 과연 Mojo Interface
가 RenderFrameHost
객체보다 수명이 더 길지 않다는 것을 보증할 수 있을까? 이 질문에 대한 답은 아래 코드를 통해 확인할 수 있다.
1 |
|
만약, sensor_provider_proxy
pointer가 초기화되지 않았다면, make_unique를 통해 초기화되므로 unique_ptr
의 특성을 따라 SensorProviderProxyImpl
객체는 RenderFrameHost
객체와 lifetime
이 서로 동일하기 때문에 같이 소멸되는 것을 보장받는다.
이러한 방법 말고도 Mojo
를 이용해서 인스턴스화 하는 방법도 존재한다. Mojo::MakeSelfOwnedReceiver
에 따르면 receiver는 stand-alone 객체로 존재하며 이 객체는 impl을 가지고 있으며 바인드된 interface endpoint가 오류를 검출하면 자동으로 정리된다고 한다.
1 |
|
즉, Mojo Interface
객체는 Mojo Connection
과 lifetime
이 동일하다. 이는, UI Thread가 RenderFrameHost
객체를 파괴하고 Mojo Connection(self-owned)
이 살아있다면, endpoint에서 오류가 검출되기 전까지 Mojo Message를 처리하는 상황이 올 수도 있다. 한마디로 Mojo Interface
가 Free된 RenderFrameHost
에 관한 Message를 처리하게 되면 UaF
가 일어나는 것이다.
Chromium에는 이러한 상황을 해결하기 위한 몇 가지 방법이 존재한다.
- WebContentsObserver: 선언한
Mojo Interface
가WebContentsObserver
에서 상속되었을 경우, 특정callback events
들이 제공되는데, 해당 callback 중에는RenderFrameHost
객체가 삭제될 때마다 트리거되는RenderFrameDeleted
가 있다. 해당 코드에서는render_frame_host
를nullptr
로 초기화한다.InstalledAppProviderImpl
에서 확인할 수 있다.
1 |
|
- FrameServicebase: WebContentsObserver와 비슷하며
RenderFrameHost
가 삭제되는 즉시, 메모리가 해제되는 것을 보장한다.
1 |
|
Issue 1068395
Link
다음 crbug issue를 분석하여 Chromium에서 일어나는 UaF 버그에 대해 더 자세히 알아보도록 하겠다. (참고로 밑에 소스코드들은 stable version의 Chromium과 다르다.)
1 |
|
먼저 GetBrowserContext()
와 this(RenderFrameHost object)
를 인자로 갖고 SmsFetcher::Get
함수를 호출하고 SmsFetcher
객체를 가진 pointer
를 반환한다. 그리고 해당 pointer
와 RenderFrameHost(이하 RFH)
를 인자로 갖는 SmsService::Create
함수를 호출한다.
1 |
|
코드 주석에 나와있듯이 SmsService는 self-owned
이다. 만약 Mojo Interface
에서 에러가 검출되거나 RFH가 삭제되면 RFH는 새로운 문서로 이동한다. 위에서 설명한 Mojo::MakeSelfOwnedReceiver
을 사용하진 않았지만, SmsService는 FrameServiceBase
를 상속하므로 비슷한 효과를 낼 수 있다. 우선 이 코드에는 UaF 버그가 존재하지 않는다. 이유는 FrameServiceBase
의 특성을 잘 생각해보면 알 수 있다. 다시 처음으로 돌아가서 SmsService::Get
함수와 SmsService::Create
함수를 살펴보자.
1 |
|
먼저 BrowserContext
에 SmsFetcher
객체가 저장되어 있는지 확인한다. SmsFetcher
와 BrowserContext
가 둘 다 살아있고 SMS message를 수신할 수 있는 경우 해당 유저 데이터를 반환한다. 그러나 해당 조건문을 통과하지 못할 경우, 새로운 SmsFetcherImpl
객체를 생성한다. SmsProvider::Create
함수도 같이 살펴보자.
1 |
|
여기에는 두 가지 SmsProvider
type이 존재한다.
- SmsProviderGmsVerification: 상관 X
- SmsProviderGmsUserConsent:
RFH raw pointer
를 생성자의 인자로 사용한다. 우리가 주목해야 할 type이다.
1 |
|
SmsProviderGmsUserConsent
클래스 내부에 RFH raw pointer
가 저장되어있다. 또한 SmsProviderGmsUserConsent::Retrieve
함수를 호출할 때마다 RFH
에 변수에 접근하게 된다. 그리고 제일 중요한 것은 WebContents::FromRenderFrameHost
함수에 접근할 때 RFH
가 alive 한지 freed된 상태인지 검증하는 코드가 존재하지 않아 UaF
로 이어질 수 있다.
여기서 재밌는 점은 새로운 SmsService
가 계속 만들어지면 SmsFetcherImpl
는 계속해서 생성되지 않지만, RFH
객체에 대한 참조를 제공한다. 즉, 새로운 SmsProvider Mojo Interface
에 대해 동일한 RFH raw pointer
가 재사용되는 것이기 때문에 UaF
에 매우 취약하다. 따라서 SmsService Interface
에 바인드하는 새로운 RFH
객체가 존재할 경우, dangling RFH pointer
가 SmsService
객채에 저장된다. 시나리오는 다음과 같다.
iframe(A)
을 만들고SmsReceiver
에 바인드한다. 바인드가 성공적으로 되면SmsFetecherImpl
객체와 iframe(A)에 대한RFH raw pointer
를 갖고 있는SmsProviderGmsUserConsent
객체가 생성된다.iframe(B)
를 만들고SmsReceiver
에 바인드한다. 이때SmsFetcherImpl
객채에 대한 참조가 생성된다.iframe(A)
를 삭제한다.iframe(A)
의RFH
객체가 삭제되지만SmsProviderGmsUserConsent
에는 아직RFH raw pointer
에 대한참조
가 존재한다.iframe(B)
에 대한SmsReceiver
를 호출한다. 여기서SmsproviderGmsUserConsent::Retrieve
함수 내부에 WebContents* web_contents = WebContents::FromRenderFrameHost(render_frame_host_); 가 트리거 되면서UaF
가 발생하게 된다.
해커 입장에는 다행스럽게도 raw pointer
를 가진 객체 덕분에 FrameServiceBase
를 아무 쓸모없게 만들어버렸다.
A more detailed analysis
우리는 이 UaF
를 쓸모있게(?) 트리거하기 위해 WebContents::FromRenderFrameHost(render_frame_host)
의 동작을 자세하게 분석해 볼 필요가 있다. 먼저 SmsProviderGmsUserConsent::Retrieve
함수를 살펴보자.
1 |
|
WebContents::FromRenderFrameHost
함수 인자에 RFH raw pointer
을 넣어 호출하고, null pointer인지 검사한 후, 특정 Java code를 수행한다. 다음은 WebContents::FromRenderFrameHost
함수의 동작 과정이다.
1 |
|
총 2곳에서 RFH raw pointer
을 이용한 function call
을 수행하는데 해당 함수들의 정의는 다음과 같다.
1 |
|
두 함수는 모두 virtual
로 선언되어 있다. 즉, 컴파일러가 가상 함수들을 처리하기 위해 virtual table
을 생성하는데, 우리가 Freed RFH raw pointer
을 악의적인 virtual table
를 가리키게 할 수 있다면 임의의 함수를 호출할 수 있게 된다.