[4] WebGL: `readPixels` PBO offset reinterpreted as a host pointer
A WebGL buffer offset that host-side code read as a base address.
Medium입니다. 정수 하나가 두 가지 addressing 방식을 동시에 담당했습니다. 하나는 GPU-buffer byte offset이었고, 공유되는 host-memory helper는 이를 base address로 처리했습니다. 접근 가능한 주소 범위는 페이지가 실제로 할당받을 수 있는 PBO 크기로 제한되는 것으로 보이며, 그래서 현실적인 결과는 조작된 write보다는 항상 동일하게 재현되는 graphics-process crash에 가깝습니다.
Graphics API는 파라미터 하나를 두 개의 address space에 걸쳐 중복 사용하는 경우가 있습니다. glReadPixels가 받는 void* destination이 대표적인 예로, pixel pack buffer가 바인딩되지 않은 상태에서는 실제 host pointer로 동작하지만, 바인딩된 상태에서는 GPU-side storage 내부의 byte offset으로 동작합니다. WebKit의 ANGLE 기반 GraphicsContextGLANGLE은 모든 WebGL context가 GL 명령을 전달하는 대상 객체로, GPU-process WebGL이 활성화된 경우 GPU process에서 실행됩니다. 이 객체는 두 형태의 readPixels를 모두 하나의 공유 helper로 라우팅했는데, 이 helper는 std::span<uint8_t>를 받는 구조로 소유권 검증도, 유효성 검사도 없는 pointer-plus-length 쌍이었습니다. 원래 기대되는 동작은, WebGL API 경계를 buffer offset으로 넘어온 값이 ANGLE이 bounds-check하는 GL-side 주소 연산 안에 머물러야 하고, WebKit 자체 코드 안에서 host pointer로 변환되어서는 안 된다는 것입니다.
관전 포인트: 페이지가 WebGL2의 readPixels를 호출하면서 pixel pack buffer에 특정 offset을 지정하면, 그 숫자 값이 곧 host-side pixel post-processing이 graphics process에서 write 대상으로 삼는 base address가 됩니다.
PBO로 pixel을 read할 때는 client buffer 경로를 사용하지 않아야 합니다. offset을 지정해 PBO로 read할 경우 legacy wipeAlphaChannelFromPixels가 실행되는 문제가 있었습니다.
Source/WebCore/platform/graphics/angle/GraphicsContextGLANGLE.cpp
LayoutTests/webgl/readpixels-pbo-offset-validation.html
Patch Details
GraphicsContextGLANGLE::readPixelsBufferObject()의 재작성은 크게 세 갈래로 나눌 수 있습니다. 먼저 함수의 precondition을 로컬에서 확립하는 두 개의 새로운 entry guard가 추가되었습니다. 다음으로 기존에 삭제된 helper가 담당하던 multisample 처리가 인라인되었습니다. 마지막으로 span 기반의 client-buffer 호출이 direct robust GL read로 교체되었습니다.
두 guard는 각각 !m_isForWebGL2 검사와 GL_GetIntegerv(GL_PIXEL_PACK_BUFFER_BINDING, ...) 검사이며, 조건을 만족하지 못하면 addError(GCGLErrorCode::InvalidOperation)로 거부합니다. 결과적으로 이 함수는 WebGL2 context이면서 실제로 pixel pack buffer가 바인딩된 경우가 아니면 동작을 거부합니다.
multisample 처리는 read 이전에 수행되는 resolveMultisamplingIfNecessary(rect)와 GL_BindFramebuffer(READ_FRAMEBUFFER, m_fbo), 그리고 read 이후 m_multisampleFBO로 되돌리는 rebind로 구성됩니다. 두 동작 모두 attrs.antialias && m_state.boundReadFBO == m_multisampleFBO 조건에 의해 게이팅됩니다.
핵심 변경은 std::span<uint8_t> data(reinterpret_cast<uint8_t*>(offset), bufferSize) 구성 코드를 삭제한 것입니다. 이 span은 PBO byte offset을 host pointer로 캐스팅한 값을 data pointer로 사용하고 있었습니다. 함께 삭제된 것은 GL_GetBufferParameterivRobustANGLE(GL_PIXEL_PACK_BUFFER, GL_BUFFER_SIZE, ...) 쿼리와 WTF_ALLOW_UNSAFE_BUFFER_USAGE_BEGIN/END 블록, 그리고 그에 딸린 FIXME입니다. Read는 이제 GL_ReadPixelsRobustANGLE(..., bufferSize = std::numeric_limits<GLsizei>::max(), nullptr, nullptr, nullptr, reinterpret_cast<void*>(offset)) 형태로 직접 호출되며, // ANGLE validates the read size against the PBO size. 주석이 함께 추가되었습니다. Robust-size 인자는 의도적으로 사실상 무의미한 값으로 설정되었고, PBO bounds 검증은 ANGLE에 위임됩니다. setPackParameters(alignment, rowLength, false)는 그대로 유지됩니다. 새로 추가된 layout test는 {alpha, antialias} context attribute 네 가지 조합 전체에 대해 PBO로의 readPixels를 검증하며, offset 값으로 0, 256, -1, 4, bufferSize, bufferSize-1, 0x7FFFFFFF, 0x7FFFFFFD를 사용합니다. 음수 offset에는 INVALID_VALUE를, read 범위를 넘어서는 모든 offset에는 INVALID_OPERATION을 기대합니다.
하나의 integer 값을 서로 호환되지 않는 두 addressing convention 아래에서 재사용하는 패턴입니다. buffer-relative offset이 이후 단계에서 absolute host pointer로 취급됩니다.
Background
이 코드가 있는 위치.
WebGL context는 GraphicsContextGL을 통해 GL 명령을 실행하며, GraphicsContextGLANGLE은 그 ANGLE 기반 구현체입니다. GPU-process WebGL 환경에서는 이 객체가 RemoteGraphicsContextGL 뒤편의 GPU process에서 실행되고, WebContent process의 RemoteGraphicsContextGLProxy가 IPC를 통해 이를 구동합니다. 이런 process 분리 구조 때문에 구현체 스스로 입력값을 검증해야 합니다. Compromise된 WebContent process는 임의의 IPC 메시지를 보낼 수 있으며, MESSAGE_CHECK가 존재하는 이유가 바로 여기에 있습니다.
Pixel Buffer Objects.
PBO는 GL_PIXEL_PACK_BUFFER에 바인딩되는 GL buffer object로, pixel read의 목적지 역할을 하면서 데이터를 CPU 메모리로 복사하지 않고 GPU-side storage에 유지할 수 있게 해줍니다. WebGL2에서는 gl.bindBuffer(gl.PIXEL_PACK_BUFFER, buf)로 이를 사용할 수 있습니다.
The GL pointer/offset convention.
glReadPixels와 같은 entry point는 void* destination을 인자로 받습니다. 하지만 pixel pack buffer가 바인딩된 상태에서는 이 인자가 실제로는 pointer가 아닙니다. 바인딩된 buffer 내부로의 byte offset이며, 관례적으로 작은 integer 값을 void*로 캐스팅해서 전달합니다. 즉 동일한 C 파라미터가 현재 바인딩 상태에 따라 전혀 다른 두 가지 의미를 갖게 되는 셈입니다. WebIDL에서 WebGL2RenderingContext.readPixels의 offset overload는 GLintptr를 인자로 받으며, 이는 script가 직접 전달하는 64비트 integer입니다.
Robust ANGLE entry points.
GL_ReadPixelsRobustANGLE을 비롯한 함수들은 destination이 담을 수 있는 바이트 수를 나타내는 bufferSize 인자를 추가로 받으며, 이를 통해 ANGLE이 들어맞지 않는 read를 거부할 수 있습니다. std::numeric_limits<GLsizei>::max()를 전달하면 이 검사 자체는 사실상 무력화되고, 실질적인 bound 역할은 ANGLE 자체의 PBO 크기 검증이 담당하게 됩니다.
Alpha-channel wipe.
alpha: false로 생성된 WebGL context는 opaque한 drawing buffer를 표시해야 하므로, WebKit은 read-back된 pixel 데이터를 wipeAlphaChannelFromPixels()로 후처리하여 CPU-side buffer의 각 pixel alpha byte를 덮어씁니다. 이는 std::span<uint8_t>를 대상으로 한 host-memory 연산입니다.
Multisample resolve.
antialias: true context에서는 렌더링이 m_multisampleFBO로 이루어집니다. Pixel을 read-back하려면 먼저 resolveMultisamplingIfNecessary()가 single-sample인 m_fbo로 blit해야 하며, read가 진행되는 동안에는 이 m_fbo가 read framebuffer로 바인딩되어 있어야 합니다.
std::span<uint8_t>.
Ownership도 validation도 없는 pointer-plus-length 쌍입니다. Span을 생성하는 행위는 pointer 위치에서 length 바이트가 읽고 쓸 수 있음을 확인하는 것이 아니라 단정하는 것에 가깝습니다. WebKit은 raw pointer로부터 span을 구성하는 call site를 WTF_ALLOW_UNSAFE_BUFFER_USAGE_BEGIN/END로 표시합니다.
Analysis
이 버그는 pointer/offset type confusion에 해당합니다. buffer-relative offset이, span의 base를 host address로 취급하는 코드 경로로 그대로 흘러 들어갑니다.
Before: After:
readPixelsBufferObject(offset) readPixelsBufferObject(offset)
└─ query PBO GL_BUFFER_SIZE ├─ require m_isForWebGL2
└─ span{ (uint8_t*)offset, size } ├─ require PIXEL_PACK_BUFFER_BINDING
│ base = script-chosen int ├─ resolveMultisamplingIfNecessary
▼ └─ GL_ReadPixelsRobustANGLE(
readPixelsImpl(span) ..., (void*)offset)
├─ GL read ──► lands in PBO (ok) └─ offset stays an offset;
└─ host fixup on span.data() no host span exists
└─ writes at absolute addr = offset
패치 이전 경로는 host buffer를 전혀 나타내지 않는 두 값으로 std::span<uint8_t>를 만들어내고 있었습니다. data pointer는 reinterpret_cast<uint8_t*>(offset), 즉 WebGL이 넘겨준 pixel pack buffer 내부 byte offset이었고, length는 PBO의 GL_BUFFER_SIZE였습니다. 이 span은 client-memory ArrayBufferView 형태에도 사용되는 동일한 helper인 readPixelsImpl()로 전달되었습니다.
GL 호출 자체만 놓고 보면 이 구성은 문제가 없습니다. Pack buffer가 바인딩된 상태에서는 pointer 인자가 관례상 offset을 의미하기 때문에, 하부의 robust read가 이를 올바르게 해석해 PBO 안에 데이터를 씁니다. 정작 빠져 있던 invariant는, 이 span이 경로상 다른 어떤 곳에서도 host memory로 취급되어서는 안 된다는 것이었습니다.
Helper가 caller가 넘긴 span에 대해 수행하는 전송-후 host-side fixup이 있다면 문제가 달라집니다. alpha: false context를 위한 opaque-alpha fixup이 바로 그 경우이며, commit message가 legacy 경로를 언급하는 부분도 이를 가리키는 것으로 보입니다. 이 fixup은 script가 선택한 buffer offset과 수치상 동일한 절대 주소의 메모리에 값을 기록하게 됩니다. 제공된 source 발췌본은 readPixelsImpl() 이전에서 잘려 있어, 이 fixup의 존재와 정확한 형태는 diff에서 해당 호출이 삭제된 정황과 WebKit에 대한 일반적인 지식을 근거로 추론한 것입니다. Host-memory write 메커니즘 전체가 이 추론에 의존합니다.
패치 이전 경로에서는 offset을 PBO의 실제 backing store로 되돌려 매핑하는 과정이 전혀 없었고, destination이 host-addressable한지 확인하는 절차도 없었습니다. offset == 0인 경우 span의 data pointer는 null이 됩니다.
새로 추가된 두 guard는 두 번째 memory-safety 구멍을 막았다기보다는, precondition을 강화하고 error code를 spec에 맞게 바로잡은 것으로 보는 편이 적절합니다. 패치 이전 코드는 GLsizei bufferSize = 0으로 초기화한 뒤 buffer-parameter 쿼리 결과로 값을 채웠습니다. Pack buffer가 바인딩되어 있지 않으면 이 쿼리는 실패해 bufferSize == 0으로 남고, 그 결과 span의 길이도 0이 되며 ANGLE에 전달되는 robust size 인자 역시 0이 됩니다. 즉 이 경우에는 read 자체가 거부되었을 뿐, 아무 곳에도 기록이 이루어지지 않았습니다. 이번에 추가된 guard는 이 실패 상황을 spec에 맞는 INVALID_OPERATION으로 명시적으로 드러내고, pointer 연산이 의존하는 precondition을 로컬에서 다시 확립합니다. 이 함수가 GPU process에서도 실행된다는 점을 감안하면 의미 있는 변화입니다.
Web content로부터의 도달 가능성은 직접적입니다. WebGL2RenderingContext::readPixels(x, y, w, h, format, type, GLintptr offset)이 readPixelsBufferObject에 도달하는 caller 형태이며, 새로 추가된 layout test는 이 전체 시퀀스를 JavaScript에서 그대로 구동합니다.
테스트를 따라가 보면 다음과 같습니다. 먼저 {alpha: false} 옵션으로 WebGL2 context를 생성합니다. 이 옵션은 공유 경로에서 opaque-alpha 후처리를 활성화시키는 속성입니다. 이어서 gl.createBuffer(), gl.bindBuffer(gl.PIXEL_PACK_BUFFER, pbo), gl.bufferData(gl.PIXEL_PACK_BUFFER, bufferSize, gl.DYNAMIC_READ)로 큰 pack buffer를 준비하고, drawing buffer를 clear한 뒤 0이 아닌 offset으로 gl.readPixels(0, 0, w, h, gl.RGBA, gl.UNSIGNED_BYTE, offset)를 호출합니다.
패치 이전에는 GL read 자체는 PBO 안에 정상적으로 기록되지만, readPixelsImpl 내부의 host-side fixup이 있다면 data.data() — 즉 offset 값 — 를 base address로 사용하게 됩니다. 64 * 64 * 4 크기의 PBO에 대해 offset = 256을 사용하는 테스트 케이스가 정확히 이 상황에 해당합니다. 패치 이후의 기대값(NO_ERROR, 그리고 getBufferSubData로 확인했을 때 256 위치에서 expectedNoAlpha가, 0 위치에서는 0이 나타나는 것)은 이제 read가 오직 buffer 내부에만 기록됨을 확인시켜줍니다.
추론된 alpha-wipe 경로에서 발생하는 corruption의 형태는, 절대 주소 offset에서 시작해 read rectangle 크기만큼 뻗어나가는 strided 1바이트 write입니다. Address 선택 범위는 fixup 이전에 이루어지는 GL read 단계에서 ANGLE이 동일한 offset을 검증한다는 점에 의해 제한됩니다. Write는 ANGLE이 이미 받아들인 read 뒤에만 이어지므로, offset은 페이지가 실제로 allocate한 PBO보다 작은 값이어야 하고, GL_BUFFER_SIZE는 GLsizei로 조회됩니다. Null page를 예약해두는 플랫폼에서는 낮은 주소 범위의 offset이 첫 byte를 쓰는 시점에 곧바로 fault를 일으킵니다. 따라서 즉각적으로 관찰되는 영향은 attacker가 유발하는 graphics process crash이며, 이는 버그 제목과도 일치합니다.
삭제된 span 구성 코드를 기준으로 보면, 공격자에게 가장 유리한 전개는 조건부로 존재합니다. 만약 페이지가 mapped memory 영역에 해당하는 값으로 offset을 통과시킬 수 있다면 — 이는 ANGLE이 그만큼 큰 pixel pack buffer allocation을 검증해줘야 한다는 조건을 전제로 합니다 — 동일한 전송-후 loop가 graphics process 안의 heap data나 allocator metadata에 대한 strided fixed-value write로 이어질 가능성이 있습니다. Heap grooming을 통제할 수 있는 상황이라면 단순 fault를 넘어 corruption primitive에 해당할 가능성도 존재합니다. 다만 기록되는 값 자체는 attacker가 제어할 수 없고, loop의 bound 역시 제공된 context에서 확인되지 않으므로, 이 primitive의 실질적인 품질은 어디까지나 예상되는 방향에 머무릅니다.
성립하지 않는 해석도 짚어둘 필요가 있습니다. GL_PIXEL_PACK_BUFFER_BINDING guard의 부재를 두 번째 write primitive로 — 즉 ANGLE이 PBO가 바인딩되지 않은 상태에서 offset을 진짜 client destination pointer로 취급하는 경우로 — 해석하는 것은 패치 이전 코드와 모순됩니다. GLsizei bufferSize = 0 초기화와 실패하는 size 쿼리가 결합되면 robust size는 0으로 남고 read 자체가 거부되었을 것이기 때문입니다.
이 vulnerability는 WebGL backend를 호스팅하는 process — GPU-process WebGL이 활성화된 경우에는 GPU process, 그렇지 않으면 WebContent process — 의 memory safety를 약화시킵니다. 여기서 깨지는 security model의 전제는, WebGL API 경계를 buffer offset으로 넘어오는 값이 ANGLE이 bounds-check하는 GL-side address 연산 범위 안에만 머물러야 하고, WebKit 자체 코드 안에서 host pointer로 바뀌어서는 안 된다는 것입니다. 패치 이전에는 페이지가 readPixels의 PBO offset을 선택하는 것이 곧, 공유 client-memory 경로의 host-side 후처리가 사용할 숫자 base address를 선택하는 것과 다름없었습니다. GPU process를 corrupt시키는 것만으로 sandbox escape가 성립하지는 않지만, GPU process는 graphics 및 media 자원에 대해 WebContent보다 더 높은 권한을 갖고 있어 흔히 second-stage target으로 노려지는 대상입니다.
이번 패치가 삭제한 FIXME 주석 — // FIXME: Remove redundant use of unsafe std::span by calling GL_ReadPixelsRobustANGLE directly. — 은 이미 문제의 그 구조를 지목하고 있었지만, 이를 correctness hazard가 아니라 단순한 중복 코드로 프레이밍하고 있었습니다. 실제 위험 요소는 span의 중복성이 아니라, 그 span의 base가 파일 안의 다른 모든 span과 전혀 다른 addressing convention을 갖고 있었다는 점입니다. WebKit의 WTF_ALLOW_UNSAFE_BUFFER_USAGE_BEGIN/END 마커는 바로 이런 지점들을 정확히 정리해둔 목록에 해당하며, 이 사례는 그 마커가 잡아내고자 하는 실패 양상을 그대로 보여줍니다. Host pointer가 전혀 아닌 값으로부터 span이 만들어지면, 그 span은 함수 한 단계만 더 깊이 전달되는 순간 정상적인 buffer와 구분할 수 없어집니다.
Audit directions
-
값의 의미가 external binding state에 따라 달라지는 정수가, 그 차이를 구분하지 못하는 공유 helper를 통해 전달되는 패턴. 이 invariant는, 의미가 external state에 좌우되는 값이라면 경계를 넘을 때마다 다시 태깅되거나 state가 재확인되어야 한다는 것입니다. 일반 pointer나 span으로 확장되어서는 안 됩니다. 좁게 보면:
Source/WebCore/platform/graphics/angle/에서reinterpret_cast<uint8_t*>(offset),reinterpret_cast<void*>(offset), 그리고GraphicsContextGLANGLE.cpp의asPointers()helper를 grep으로 찾아, 각 지점에서 host-memory에 접근하는 코드가 뒤따르는지 확인합니다. Cast 결과가 바로 다음GL_*호출 인자로 쓰이지 않고 다른 곳으로 흘러간다면, 그 지점이 단서입니다. 더 넓게 보면: 동일한 유형은 하나의 parameter가 두 개의 address space를 겸하는 API라면 어디서든 나타납니다. 예를 들어drawElements/vertexAttribPointer의 index 및 attribute offset,getBufferSubData,texImage*/texSubImage*와compressedTexImage*의 unpack-buffer variant, 그리고 모든bufferSubData경로가 해당됩니다.GCGLintptr/GLintptr를 받아 이후std::span을 구성하거나 그 값에 pointer arithmetic을 수행하는 함수라면 주목할 만한 형태입니다. 가장 넓게 보면: 이는 handle이 address로 해석되는 일반적인 유형에 해당합니다. Descriptor나 index, file offset이void*타입 parameter를 통해 몰래 전달되는 모든 곳에서 성립하며, Vulkan/Metal의 buffer offset,mmapoffset, io_uring의user_data필드, 정수를 opaque pointer로 넘기는 FFI binding 등이 여기에 해당합니다. Parameter의 타입은void*이지만 어떤 state에서는 그 의미가 "offset"인 경우, 첫 cast 이후의 모든 consumer는 dereference 여부를 점검해야 합니다. 이 지점부터는 type system이 더 이상 도움을 주지 못하기 때문입니다. -
서로 다른 memory model을 가진 두 caller가 하나의 공유 helper를 함께 호출하는 패턴. 위험한 지점은, 이 helper가 단독으로 보면 맞고 다수 caller 입장에서도 맞아 보인다는 데 있습니다. 정작 소수 caller는 애초에 자신을 위한 것이 아니었던 단계를 그대로 물려받게 됩니다. 좁게 보면:
GraphicsContextGLANGLE.cpp에서readPixelsImpl()을 비롯해GL_*RobustANGLE호출과 이후 전달된 span에 대한 접근을 모두 수행하는 다른*Impl()helper의 나머지 caller들을 모두 나열해봅니다. GL 호출 이후 caller가 넘긴 span을 인덱싱하는 구문이 있다면 — alpha wipe, endian swap, row flip, premultiply/unpremultiply 처리 등 — 그것이 단서입니다. 더 넓게 보면: 실제로는 GPU나 shared memory에 있을 수 있는 데이터에 CPU 측 fixup이 적용되는 곳이라면 어디서든 같은 형태가 나타납니다.texImage/readPixels의 pixel-format 변환 helper와ImageBuffer/PixelBuffer변환 경로에서, transfer 이후 무조건 적용되는 fixup이 있는지 살펴봐야 합니다. 가장 넓게 보면: transfer와 in-place fixup을 모두 수행하는 routine은 transfer 요청이 어디서 왔는지가 아니라 데이터가 실제로 어디에 도착했는지를 기준으로 parameterize되어야 합니다. Legacy in-place post-processing 단계가 zero-copy 경로보다 먼저 존재했던 모든 DMA 혹은 zero-copy 설계(io_uring, GPU staging buffer, shared-memory IPC ring buffer)에 동일하게 적용되는 원칙입니다. 판별 단서: 목적지의 residency를 signature로 표현할 수 없는 함수가 copy 경로와 zero-copy 경로 양쪽에서 호출된다면, 그것이 일치하는 단서입니다. -
Precondition 검증이 API-validation 계층에서만 이루어지는데, 정작 구현 계층은 IPC endpoint 역할도 겸하는 패턴. 낮은 권한의 caller를 대신해 service process에서 실행되는 함수라면, 자신의 pointer arithmetic이 의존하는 모든 state precondition을 로컬에서 다시 확립해야 합니다. 지금 당장은 부수적인 속성 — 여기서는 zero-initialised된 size query — 덕분에 우연히 안전하게 실패하고 있더라도 마찬가지입니다. 좁게 보면:
Source/WebKit/GPUProcess/graphics/RemoteGraphicsContextGL.cpp와 그.messages.in을 통해 노출되는 각GraphicsContextGLANGLE메서드를 확인합니다. 해당 메서드의 정확성이 GL binding state(*_BUFFER_BINDING, 바인딩된 FBO, 현재 program, context version)에 의존하는지, 그리고 그 state를 조회하거나 assert하는지를 살펴봐야 합니다. 이번 패치가 추가한 단서는GL_GetIntegerv(GL_PIXEL_PACK_BUFFER_BINDING, ...)가드와m_isForWebGL2체크이며, 다른 곳에 이 검증이 없다면 그 지점이 후보가 됩니다. 더 넓게 보면: safety 근거가 client 측에 있는 모든 service-process handler에서 같은 비대칭이 나타납니다.RemoteRenderingBackend,RemoteMediaPlayer, 그리고 WebGPU의RemoteBuffer/RemoteDevicehandler를 점검해, WebCore 쪽 wrapper에서만 검증되는 parameter가 있는지,MESSAGE_CHECK없이 size나 offset을 사용하는 handler가 있는지 찾아봐야 합니다. 가장 넓게 보면: privilege boundary의 caller 쪽에서 수행되는 validation은 강제력이 없는 권고 수준에 불과합니다. Chromium의 Mojo handler, kernel syscall의 인자 검증, 그리고 in-process 용도로 작성된 라이브러리를 재사용하는 모든 RPC 서비스에 동일하게 적용됩니다. 판별 단서: 주석이나 구조상 "caller가 이미 확인했다"는 것을 전제하는 구현 함수인데, 정작 그 caller 중 하나가 deserialiser인 경우입니다. -
Read 작업 자체가 아니라 read 이후 처리를 context attribute가 좌우하는 WebGL2 경로가 대상입니다. 이번 사례에서 의심되는 발동 조건이
alpha: false였고, 새로 추가된 test는{alpha, antialias}조합을 sweep하는 방식으로만 이를 검증하기 때문입니다. 데이터가 이미 전달된 이후contextAttributes().alpha,.antialias,.premultipliedAlpha,.preserveDrawingBuffer를 기준으로 분기하는GraphicsContextGLANGLEoperation을 추적하고, 각 분기가 client-memory 목적지와 PBO 목적지 모두에서 유효한지 확인해야 합니다. 판별 단서: destination 종류에 대응하는 분기 없이 context attribute만을 기준으로 나뉘는 post-transfer 분기가 단서입니다. 기본 attribute set만 다루는 기존 test는 이 경로를 검증하지 못하므로,LayoutTests/webgl/에서 non-default context attribute에 대한 coverage gap 자체가 어디를 살펴봐야 할지 알려주는 신호가 됩니다.