Bug 2461973 (CVE-2026-110133)

Summary: CVE-2026-110133 ghostscript: Heap buffer overflow in Ghostscript TrueType outliner due to nPointsTotal integer underflow during retry rollback
Product: [Other] Security Response Reporter: OSIDB Bzimport <bzimport>
Component: vulnerabilityAssignee: Product Security DevOps Team <prodsec-dev>
Status: NEW --- QA Contact:
Severity: medium Docs Contact:
Priority: medium    
Version: unspecifiedCC: rhel-process-autobot, security-response-team, watson-tool-maintainers
Target Milestone: ---Keywords: Security
Target Release: ---   
Hardware: All   
OS: Linux   
Whiteboard:
Fixed In Version: Doc Type: ---
Doc Text:
A flaw was found in Ghostscript. When processing a specially crafted TrueType font or document embedding one, an accounting error during font rendering can bypass buffer size checks, triggering a heap-based buffer overflow. A remote attacker could exploit this by enticing a victim or automated service to open the malicious content, potentially resulting in a Denial of Service (DoS) crash or arbitrary code execution.
Story Points: ---
Clone Of: Environment:
Last Closed: Type: ---
Regression: --- Mount Type: ---
Documentation: --- CRM:
Verified Versions: Category: ---
oVirt Team: --- RHEL 7.3 requirements from Atomic Host:
Cloudforms Team: --- Target Upstream Version:
Embargoed:
Bug Depends On: 2563298    
Bug Blocks:    

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