Bug 314526
| Summary: | REGRESSION (312740@main): [ASan/TSan] `angle::PoolAllocator` has inconsistent layout across translation units, crashing GPU process on WebGL shader compile | ||
|---|---|---|---|
| Product: | WebKit | Reporter: | David Kilzer (:ddkilzer) <ddkilzer> |
| Component: | WebGL | Assignee: | David Kilzer (:ddkilzer) <ddkilzer> |
| Status: | RESOLVED FIXED | ||
| Severity: | Normal | CC: | djg, kbr, kkinnunen, webkit-bug-importer |
| Priority: | P2 | Keywords: | InRadar, Regression |
| Version: | WebKit Nightly Build | ||
| Hardware: | Unspecified | ||
| OS: | Unspecified | ||
| Bug Depends on: | 313441 | ||
| Bug Blocks: | |||
David Kilzer (:ddkilzer)
ASan (and TSan) builds of WebKit cause any WebGL shader compile performed in the GPU process to abort with an AddressSanitizer stack-buffer-overflow report in `angle::PoolAllocator::PoolAllocator()`. This is observed across the majority of tests in `LayoutTests/fast/canvas/webgl/` on ASan builds.
Stack trace (representative crash thread, from a local ASan build):
```
==PID==ERROR: AddressSanitizer: stack-buffer-overflow
WRITE of size 88 at 0x... thread TN
0 libclang_rt.asan_osx_dynamic.dylib: __asan_memset
1 libANGLE-shared.dylib: angle::PoolAllocator::PoolAllocator()
2 libANGLE-shared.dylib: sh::TCompiler::compile(angle::Span<char const* const>, ShCompileOptions const&)
3 libANGLE-shared.dylib: rx::ShaderTranslateTask::translate(void*, ShCompileOptions const&, std::string const&)
4 libANGLE-shared.dylib: gl::CompileTask::operator()()
5 libANGLE-shared.dylib: angle::UnlockedTailCall::runImpl(void*)
6 WebCore: WebCore::GraphicsContextGLANGLE::compileShader(unsigned int)
7 WebKit: WebKit::RemoteGraphicsContextGL::compileShader(unsigned int)
8 WebKit: WebKit::RemoteGraphicsContextGL::didReceiveStreamMessage(IPC::StreamServerConnection&, IPC::Decoder&)
[...]
```
The ASan frame report shows the compiler-allocated stack slot for `scopedAlloc` (a `TScopedPoolAllocator` whose only member is an `angle::PoolAllocator`) is 24 bytes wide, but `angle::PoolAllocator::PoolAllocator()` performs an 88-byte `__asan_memset` to zero-initialize its members. The call writes past the end of the 24-byte slot, corrupting the adjacent ASan redzone and other stack locals.
The 88-byte write size comes from the layout `std::vector<Segment> mSingleObjectSegments` + `Span<uint8_t> mCurrentPool` + `std::vector<Segment> mPoolSegments` + `std::vector<Segment> mUnusedSegments`. The 24-byte layout contains only `mSingleObjectSegments`. The three extra vectors are conditionally present under `#if !defined(ANGLE_DISABLE_POOL_ALLOC)` in `Source/ThirdParty/ANGLE/src/common/PoolAlloc.h`.
`ANGLE_DISABLE_POOL_ALLOC` is defined at the top of `PoolAlloc.h` based on whether `ANGLE_WITH_ASAN` or `ANGLE_WITH_TSAN` is visible there. Those macros are defined in `common/platform.h` from `__has_feature()`. The `#if defined(ANGLE_WITH_ASAN) || defined(ANGLE_WITH_TSAN)` check in `PoolAlloc.h` runs BEFORE `common/platform.h` is transitively included (via `common/angleutils.h`, further down in the same header). As a result, whether the sanitizer macros are visible at that check depends on which headers the including translation unit already pulled in:
* `Source/ThirdParty/ANGLE/src/common/PoolAlloc.cpp` includes `common/PoolAlloc.h` as its first include. At the macro check in `PoolAlloc.h`, `platform.h` has not been processed yet, so `ANGLE_WITH_ASAN` is NOT visible -> `ANGLE_DISABLE_POOL_ALLOC` is NOT defined -> `PoolAllocator` has the 88-byte layout, and the emitted `PoolAllocator::PoolAllocator()` body (defaulted) zero-initializes 88 bytes.
* `Source/ThirdParty/ANGLE/src/compiler/translator/Compiler.cpp` (the `sh::TCompiler::compile` caller) includes `compiler/translator/Compiler.h` first, which transitively pulls in `common/platform.h` via `Diagnostics.h`/`SymbolTable.h` BEFORE `PoolAlloc.h` is processed. At the macro check, `ANGLE_WITH_ASAN` IS visible -> `ANGLE_DISABLE_POOL_ALLOC` IS defined -> `PoolAllocator` has the 24-byte layout, and the stack slot for `scopedAlloc.mAllocator` is 24 bytes wide.
This is an ODR violation caused by a preprocessor macro check running before the macro-defining header is included. Introduced by Bug 313441 / 312740@main, where the `ANGLE_DISABLE_POOL_ALLOC` selection was added at the top of `PoolAlloc.h`. The previous macro selection at the same position (`#if !defined(NDEBUG)`) did not have this failure mode because `NDEBUG` is a standard compiler-provided macro that is visible regardless of include order.
Steps to reproduce:
1. Build WebKit for macOS with ASan.
2. Run any WebGL layout test that compiles a shader: `Tools/Scripts/run-webkit-tests --no-build --release --no-timeout --no-show-results fast/canvas/webgl/gl-getshadersource.html` (or any similar test).
3. Observe GPU-process crash with ASan `stack-buffer-overflow` report in `WebKitBuild/Release/layout-test-results/com.apple.WebKit.GPU.Development-*-crash-log.txt`.
Binary-level confirmation from `otool -tv` of `libANGLE-shared.dylib` pre-patch shows `angle::PoolAllocator::PoolAllocator()` calling `__asan_memset` with `w2 = #0x58` (88 bytes); consumer translation units allocate only 24 bytes of stack for the corresponding `scopedAlloc`.
| Attachments | ||
|---|---|---|
| Add attachment proposed patch, testcase, etc. |
David Kilzer (:ddkilzer)
<rdar://problem/176749270>
David Kilzer (:ddkilzer)
Pull request: https://github.com/WebKit/WebKit/pull/64653
EWS
Committed 312987@main (346deb2b1c82): <https://commits.webkit.org/312987@main>
Reviewed commits have been landed. Closing PR #64653 and removing active labels.