Bug 2461973 (CVE-2026-110133) - CVE-2026-110133 ghostscript: Heap buffer overflow in Ghostscript TrueType outliner due to nPointsTotal integer underflow during retry rollback
Summary: CVE-2026-110133 ghostscript: Heap buffer overflow in Ghostscript TrueType out...
Keywords:
Status: NEW
Alias: CVE-2026-110133
Product: Security Response
Classification: Other
Component: vulnerability
Version: unspecified
Hardware: All
OS: Linux
medium
medium
Target Milestone: ---
Assignee: Product Security DevOps Team
QA Contact:
URL:
Whiteboard:
Depends On: 2563298
Blocks:
TreeView+ depends on / blocked
 
Reported: 2026-04-26 17:54 UTC by OSIDB Bzimport
Modified: 2026-10-29 09:52 UTC (History)
3 users (show)

Fixed In Version:
Clone Of:
Environment:
Last Closed:
Embargoed:


Attachments (Terms of Use)

Description OSIDB Bzimport 2026-04-26 17:54:44 UTC
AI_ONLY_REPORT
package: ghostscript-10.02.1-16.el10_0
------
Summary: TrueType Outliner Heap Buffer Overflow via nPointsTotal Underflow:  
inconsistent retry rollback in the TrueType outliner can drive  
`nPointsTotal` negative, weaken point-capacity checks, and permit a heap  
out-of-bounds write when parsing crafted fonts.
Requirements to exploit: An attacker must supply a crafted TrueType font,  
or a document embedding one, and cause `ghostscript-10.02.1-16.el10_0` to  
render it through the affected outliner path with TrueType instruction  
execution enabled. No prior privileges are required; common interactive  
workflows require the victim to open or render the content.
Component affected: `ghostscript-10.02.1-16.el10_0`, TrueType outliner in  
`base/ttfmain.c`, function `ttfOutliner__BuildGlyphOutlineAux`
Version affected: `ghostscript-10.02.1-16.el10_0`
Patch available: no released package fix established; proposed patch  
included below
Version fixed: unknown
Upstream coordination: Not notified.
CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H - 8.8 (HIGH)
AV:N - A malicious font or a document embedding it can be delivered  
through normal remote content channels; strictly local-only deployments may  
score this lower environmentally.
AC:L - The trigger requires crafted glyph data, but the necessary  
conditions are within attacker-controlled input rather than relying on  
unusual target state.
PR:N - No authentication or prior access is needed.
UI:R - In common interactive use, a victim must open or render  
attacker-supplied content.
S:U - The corruption occurs within the Ghostscript process.
C:H - A heap out-of-bounds write in native parsing code can plausibly  
expose process memory if exploited further.
I:H - The same primitive can plausibly modify process memory.
A:H - The available evidence directly supports severe process  
disruption, including crash.
Impact: Important. This is a native heap out-of-bounds write in a  
content-parsing path reachable from attacker-controlled font data. It is  
not Critical because exploitation depends on crafted content being  
processed and is not a wormable, no-interaction compromise. It exceeds  
Moderate because the bug yields direct memory corruption and can at least  
cause denial of service in untrusted document-processing workflows, with  
plausible confidentiality and integrity impact if successfully exploited  
further.
Embargo: yes
Reason: The issue is a content-driven memory corruption flaw in a  
document/font parsing path, and no released fix is established yet.
Acknowledgement: Aisle Research
Vulnerability Details: In `ttfOutliner__BuildGlyphOutlineAux`,  
`self->nPointsTotal` is used as cumulative accounting for the shared point  
buffers in `exec->pts`. The simple-glyph path increments this counter by  
`nPoints`, but the retry path entered after `fBadInstruction` subtracts  
`nPoints + 2` before retrying without hinting. Because the subtraction is  
larger than the earlier addition, repeated retry-triggering glyphs can  
drive the signed counter negative. Once that happens, the guard  
`self->nPointsTotal + nPoints + 2 > exec->n_points` can undercount actual  
occupancy and admit writes beyond the intended end of the point arrays.
```c
nPoints = gOutline->pointCount = endPoints[gOutline->contourCount -  
1] + 1;
if (nPoints < 0 || self->nPointsTotal + nPoints + 2 >  
exec->n_points) {
error = fBadFontData; goto ex;
}
...
{   short coord = (self->bVertical ? 0 : sideBearing -  
subglyph.bbox.xMin);
F26Dot6* x = pts->org_x;
onCurve = pts->touch;
while (onCurve < stop) {
if ((flag = *onCurve++) & XSHORT) {
if (flag & SHORT_X_IS_POS)
coord += ttfReader__Byte(r);
else
coord -= ttfReader__Byte(r);
} else if (!(flag & NEXT_X_IS_ZERO))
coord += ttfReader__Short(r);
*x++ = Scale_X(&exec->metrics, coord);
}
}
...
self->nContoursTotal += gOutline->contourCount;
self->nPointsTotal += nPoints;
...
if (error == fBadInstruction && execute_bytecode) {
/* reset a load of stuff so we can try again without hinting */
exec = pFont->exec;
pts = &exec->pts;
usage = tti->usage + tti->usage_top;
glyph = NULL;
self->nPointsTotal -= (nPoints + 2);
nPoints = 0;
self->nContoursTotal = gOutline>contourCount;
error = fNoError;
execute_bytecode = false;
r->Seek(r, nPosBeg);
goto retry;
}
```
The composite-glyph hinting path makes this worse. In that block, `nPoints`  
is set to `gOutline->pointCount + 2` before `Context_Run(exec, FALSE)`, but  
`self->nPointsTotal` is not incremented there. If that path also falls into  
`fBadInstruction`, the same rollback subtracts counts that were not added  
in that frame, increasing the negative drift further. Composite loading  
also advances the shared `pts` pointers by the number of points already  
stored before descending into subglyphs, so inaccurate cumulative  
accounting directly affects where later point writes land.
A standalone PoC font and sanitizer trace are not included here, so this  
report does not claim a demonstrated end-to-end code-execution exploit. The  
code-level mismatch is sufficient to support a real heap out-of-bounds  
write condition, with denial of service as the minimum likely impact.
Steps to reproduce:
1. Build `ghostscript-10.02.1-16.el10_0` with sanitizers and debug symbols,  
for example `-fsanitize=address,undefined -fno-omit-frame-pointer -O1 -g`.
2. Prepare a crafted TrueType font containing composite glyphs and glyph  
bytecode that triggers interpreter errors in the `TT_Err_Invalid_Opcode` to  
`TT_Err_Invalid_Displacement` range so that `error` becomes  
`fBadInstruction`.
3. Ensure cumulative point usage is close to `exec->n_points` so the  
capacity check is tight.
4. Render content that exercises the crafted glyph through normal  
Ghostscript font processing.
5. Set breakpoints or watchpoints at the retry block and the capacity gate,  
and watch `self->nPointsTotal` for negative drift before the check executes.
6. Continue execution and observe a heap-buffer-overflow in or after the  
point population loops writing `pts->org_x` and `pts->org_y`.
Mitigation: Until a fix is available, avoid processing untrusted documents  
or standalone fonts through the affected TrueType path where possible. If  
the deployment exposes a way to disable TrueType instruction execution,  
doing so avoids the `fBadInstruction` retry path described here. Running  
Ghostscript in a restrictive sandbox reduces blast radius but does not  
remove the underlying bug.
Proposed Fix: Restore the counters to their saved pre-call state on retry  
instead of applying arithmetic rollback with mismatched deltas.
```diff
diff --git a/base/ttfmain.c b/base/ttfmain.c
— a/base/ttfmain.c
+++ b/base/ttfmain.c
@@ -500,6 +500,8 @@ static FontError  
ttfOutliner__BuildGlyphOutlineAux(ttfOutliner *self, int glyphIndex,
const byte *glyph = NULL;
int glyph_size;
bool execute_bytecode = true;
+    int nPointsTotalSaved = self->nPointsTotal;
+    int nContoursTotalSaved = self->nContoursTotal;
int nPoints = 0;
@@ -861,8 +863,8 @@ ex:;
pts = &exec->pts;
usage = tti->usage + tti->usage_top;
glyph = NULL;
       self->nPointsTotal -= (nPoints + 2);
+        self->nPointsTotal = nPointsTotalSaved;
          nPoints = 0;

       self->nContoursTotal = gOutline>contourCount;
+        self->nContoursTotal = nContoursTotalSaved;
          error = fNoError;
          execute_bytecode = false;
          r->Seek(r, nPosBeg);
```


------
This report was generated using AI technology. Always review AI-generated  
content prior to use

Comment 1 Christopher Lusk 2026-06-26 17:24:07 UTC
Tracker filed for rhel-10.3: https://issues.redhat.com/browse/RHEL-189163


Note You need to log in before you can comment on or make changes to this bug.