← All reports

[2] Untrusted texture-upload offset reinterpreted as a client pointer

HighWebCore GraphicsContextGLOOB

A number crossed the IPC boundary and came back out as a pointer

487e5a0

High. WebContent process가 정한 64비트 값을 GPU process가 raw pointer로 만들고, attacker가 지정한 길이만큼 바이트를 읽어 sampleable texture에 채웁니다. 전제 조건은 WebContent에서 이미 code execution을 확보한 상태라는 점입니다. renderer 쪽 WebGL2RenderingContext는 unpack buffer가 bind되지 않은 상태에서 이 offset overload를 거부하므로, 해당 호출은 raw IPC로 직접 발행해야 합니다.

웹 페이지가 발행한 WebGL 명령은 그 페이지가 속한 process에서 실행되지 않습니다. IPC로 직렬화되어 GPU process로 넘어간 뒤, 그곳에서 driver를 마주보는 구현 위에 다시 재생되고, 되돌아오는 것은 결과값뿐입니다. 이 재생을 담당하는 코드가 GraphicsContextGLANGLE인데, ANGLE 위에 얹힌 WebKit wrapper입니다. ANGLE 자체는 Cocoa에서 GL ES 호출을 Metal로 변환하는 계층입니다. 한편 OpenGL ES 3에서 texture upload의 pixel 인자는 두 가지 의미를 가지며, 어느 쪽인지는 오로지 binding state로 결정됩니다. GL_PIXEL_UNPACK_BUFFER에 buffer가 bind되어 있으면 그 buffer 안의 offset이고, 아무것도 bind되어 있지 않으면 driver가 역참조하는 client memory 주소가 됩니다.

관전 포인트: 장악된 WebContent process는 GPU process 안의 임의 주소를 지목할 수 있고, 그 주소에서 원하는 길이만큼 바이트를 texture로 복사하도록 driver를 유도할 수 있습니다. 결과적으로 GPU process의 heap 메모리가 읽어낼 수 있는 pixel로 바뀌게 됩니다.

이번 변경은 GraphicsContextGLANGLE의 offset 기반 tex(Sub)ImagecompressedTex(Sub)Image overload를 대상으로 합니다. 각 overload는 IPC를 통해 WebContent에서 넘어온 GCGLintptr offset을 받습니다. 패치 이전에는 이 값을 reinterpret_cast<GLvoid*>(offset)으로 변환해 pixels / data 인자로 ANGLE에 그대로 전달했습니다. 패치는 cast 이전에 binding state 전제 조건을 WebKit 자체 코드에서 검사하도록 했습니다. 그 결과 offset이 pointer로 만들어지는 경우는 GL state가 buffer 상대값임을 보장할 때로 한정됩니다. 아울러 pixel unpack buffer를 bind하지 않은 상태에서 compressedTexImage2D(TEXTURE_2D, 0, COMPRESSED_RGB8_ETC2, 4, 4, 0, 8, 0x41414140)을 호출하는 regression test가 추가되었습니다.

신뢰할 수 없는 정수를 raw pointer로 재해석하면서, 그 재해석을 안전하게 만들어 주던 binding state 전제 조건은 vendored dependency 안에서만 검사되던 패턴.

  WebContent process        |  GPU process
  --------------------------|-------------------------------------------
  compressedTexImage2D(     |  RemoteGraphicsContextGL decodes the message
    ..., imageSize = 8,     |        |
    offset = 0x41414140)    |        v
                            |  GraphicsContextGLANGLE
                            |    Before: reinterpret_cast<GLvoid*>(offset)
                            |            no PBO bound -> ANGLE treats the
                            |            value as a client pointer -> Metal
                            |            reads imageSize bytes from it
                            |    After:  pointer formed only when a pixel
                            |            unpack buffer is bound

이 코드가 있는 위치. GPU process 구성에서 WebGL 호출은 WebContent process가 공급하는 IPC::StreamServerConnection으로부터 RemoteGraphicsContextGL(Source/WebKit/GPUProcess/graphics/)이 디코딩합니다. 이후 GPU process 내부의 GraphicsContextGLANGLE 위에서 다시 재생되며, Cocoa 플랫폼에서는 ANGLE이 이 호출들을 Metal로 낮춰 내려보냅니다.

Pixel unpack buffer. OpenGL ES 3부터는 texture upload의 데이터 출처로 buffer object를 사용할 수 있게 되었습니다. GL_PIXEL_UNPACK_BUFFER에 buffer를 bind하면, upload의 데이터 인자가 갖는 의미가 "client memory를 가리키는 pointer"에서 "bind된 buffer 안의 바이트 offset"으로 바뀝니다. 두 의미가 하나의 파라미터 자리를 공유하는 셈입니다. 그래서 이 값의 타입은 호출 그 자체가 아니라 호출 시점의 GL state에 의해 결정됩니다.

Validation 책임 소재. ANGLE은 자체 validation 계층을 함께 제공하며, WebKit은 GL 수준 전제 조건을 wrapper에서 중복 검사하기보다 오래도록 여기에 기대 왔습니다. 이런 구조에서는 vendored dependency의 validation 범위가 곧 WebKit security boundary의 일부가 됩니다.

이번 버그는 lifetime 문제가 아니라, 신뢰할 수 없는 입력에 대한 전제 조건 검사가 빠진 경우에 해당합니다. "pixel unpack buffer가 bind되어 있으므로 이 정수는 buffer 상대값이다"라는 invariant는 WebKit 쪽에서 한 번도 강제된 적이 없었고, ANGLE에서 물려받은 것이었습니다. commit message에 따르면 upstream ANGLE roll(chromium/angle 849f7128)이 compressed texture upload에 대한 해당 validation을 제거했습니다. 검사가 사라진 뒤로는, cast 이전에 binding state를 살펴보는 코드가 GPU process 안에 남아 있지 않았습니다.

패치 이전 경로는 makeContextCurrent() 뒤에 GL_CompressedTexImage2D(..., imageSize, reinterpret_cast<GLvoid*>(offset))가 이어지는 형태였습니다. buffer가 bind되지 않은 상태라면 ANGLE은 이 값을 client pointer로 취급합니다. commit message에 따르면 Metal backend는 GPU process 주소 공간의 해당 주소에서 imageSize 바이트를 읽어 texture로 옮깁니다. 도달 경로는 일반적인 script가 아니라 IPC 경계에 놓여 있습니다. WebContent 쪽 WebGL2RenderingContextm_boundPixelUnpackBuffer를 추적하고, 이 값이 null이면 offset overload를 거부하도록 되어 있기 때문입니다. 따라서 실제로 이를 성립시키려면, WebContent code execution을 이미 확보하고 메시지를 직접 발행할 수 있는 attacker가 필요합니다.

read를 구성하는 두 축을 attacker가 모두 제어합니다. 주소는 offset으로 정하고, 길이는 imageSize로 정합니다. 압축되지 않은 overload에서는 widthxheightxbpp 곱이 길이가 됩니다. 추가된 테스트는 알아보기 쉬운 sentinel 주소를 써서 정확히 이 형태를 재현합니다. 한편 복사 대상이 texture라는 점이 중요합니다. 복사된 바이트는 이후 sampling이 가능하고, 통상적인 readback 경로로 다시 꺼내올 수 있습니다. 그래서 out-of-bounds read가 crash에 그치지 않고 information disclosure로 이어집니다.

정수로 경계를 넘어온 값이 권한이 높은 쪽에서 pointer로 사용됩니다. WebContent에서 GPU process로 이어지는 경계 중 가장 폭이 넓은 지점이 약해지는 셈입니다. 이번 fix는 검사 유지를 vendored dependency에 맡기는 대신, WebKit 자체 코드에서 invariant를 복원합니다.