Read beyond bounds in OpenTypeSTAT::Parse()
Categories
(Core :: Graphics: Text, defect)
Tracking
()
People
(Reporter: mozillabugs, Assigned: jfkthame)
References
Details
(4 keywords, Whiteboard: [adv-main125+][adv-esr115.10+])
Attachments
(12 files)
491 bytes,
text/html
|
Details | |
384.90 KB,
application/octet-stream
|
Details | |
48 bytes,
text/x-phabricator-request
|
Details | Review | |
48 bytes,
text/x-phabricator-request
|
Details | Review | |
48 bytes,
text/x-phabricator-request
|
Details | Review | |
48 bytes,
text/x-phabricator-request
|
phab-bot
:
approval-mozilla-beta+
|
Details | Review |
48 bytes,
text/x-phabricator-request
|
phab-bot
:
approval-mozilla-beta+
|
Details | Review |
48 bytes,
text/x-phabricator-request
|
phab-bot
:
approval-mozilla-beta+
|
Details | Review |
48 bytes,
text/x-phabricator-request
|
phab-bot
:
approval-mozilla-esr115+
|
Details | Review |
48 bytes,
text/x-phabricator-request
|
phab-bot
:
approval-mozilla-esr115+
|
Details | Review |
48 bytes,
text/x-phabricator-request
|
phab-bot
:
approval-mozilla-esr115+
|
Details | Review |
233 bytes,
text/plain
|
Details |
OpenTypeSTAT::Parse()
(gfx\ots\src\stat.cc
) can read beyond bounds if a font header contains specially-crafted values for designAxisSize
, designAxisCount
, and designAxisOffset
. The bug can be reached only on 32-bit platforms, and it appears to be latent -- Searchfox indicates that OpenTypeSTAT::Parse()
is not called anywhere, though it is built into the Firefox executable.
The bug is on lines 62-68 (FIREFOX_121_0_RELEASE):
31: bool OpenTypeSTAT::Parse(const uint8_t* data, size_t length) {
32: Buffer table(data, length);
33: if (!table.ReadU16(&this->majorVersion) ||
34: !table.ReadU16(&this->minorVersion) ||
35: !table.ReadU16(&this->designAxisSize) ||
36: !table.ReadU16(&this->designAxisCount) ||
37: !table.ReadU32(&this->designAxesOffset) ||
38: !table.ReadU16(&this->axisValueCount) ||
39: !table.ReadU32(&this->offsetToAxisValueOffsets) ||
40: !(this->minorVersion < 1 || table.ReadU16(&this->elidedFallbackNameID))) {
41: return Drop("Failed to read table header");
42: }
...
51: if (this->designAxisSize < sizeof(AxisRecord)) {
52: return Drop("Invalid designAxisSize");
53: }
54:
55: size_t headerEnd = table.offset();
56:
57: if (this->designAxisCount == 0) {
58: if (this->designAxesOffset != 0) {
59: Warning("Unexpected non-zero designAxesOffset");
60: this->designAxesOffset = 0;
61: }
62: } else {
63: if (this->designAxesOffset < headerEnd ||
64: size_t(this->designAxesOffset) +
65: size_t(this->designAxisCount) * size_t(this->designAxisSize) > length) {
66: return Drop("Invalid designAxesOffset");
67: }
68: }
The issue is that the calculation on lines 64-5 can overflow on 32-bit builds, because in those builds, size_t
is 32 bits. So, for example, if designAxesOffset
is 0xffffffff
, designAxisCount
is 16
, and designAxisSize
is 8
, the calculation yields 0x7f
in 32 bits. This would then pass the length check on line 65. Once that occurs, line 71 sets table.offset_
to 0xffffffff
. The table.Readxxx()
functions on lines 74-6 then read from buffer_ + 0xffffffff
, then wrap back to 0
...:
69:
70: for (size_t i = 0; i < this->designAxisCount; i++) {
71: table.set_offset(this->designAxesOffset + i * this->designAxisSize);
72: this->designAxes.emplace_back();
73: auto& axis = this->designAxes[i];
74: if (!table.ReadU32(&axis.axisTag) ||
75: !table.ReadU16(&axis.axisNameID) ||
76: !table.ReadU16(&axis.axisOrdering)) {
77: return Drop("Failed to read design axis");
78: }
79: if (!CheckTag(axis.axisTag)) {
80: return Drop("Bad design axis tag");
81: }
82: if (!ValidateNameId(axis.axisNameID)) {
83: return true;
84: }
85: }
because the ReadUxxx()
functions' checks also overflow, e.g., ReadU32()
(gfx/ots/src/ots.h
) does:
135: bool ReadU32(uint32_t *value) {
136: if (offset_ + 4 > length_) {
137: return OTS_FAILURE();
138: }
139: std::memcpy(value, buffer_ + offset_, sizeof(uint32_t));
140: *value = ots_ntohl(*value);
141: offset_ += 4;
142: return true;
143: }
where offset_
isa size_t
, so lines 136 and 141 overflow from 0xffffffff
to 3
on the first call to ReadU32()
, passing the check on lines 136-38 and corrupting the value of offset
on line 141.
There appears also to be a similar bug involving offsetToAxisValueOffsets
in the same code.
Updated•1 year ago
|
Comment 1•1 year ago
|
||
FWIW, code coverage data on SearchFox says we are running at least some of the code in OpenTypeSTAT::Parse.
Reporter | ||
Comment 2•1 year ago
|
||
(In reply to Andrew McCreight [:mccr8] from comment #1)
FWIW, code coverage data on SearchFox says we are running at least some of the code in OpenTypeSTAT::Parse.
This is interesting, as SearchFox search says it's not called, yet it gets built into the executable, so something must reference it (the linker should remove unreferenced code). Aha! It gets called when visiting...wait for it....google.com . So this isn't latent.
Comment hidden (obsolete) |
Comment hidden (obsolete) |
Reporter | ||
Comment 6•1 year ago
|
||
(In reply to Jeff Muizelaar [:jrmuizel] from comment #5)
Has this been reported to Chrome?
No. Does the Chrome team own OTS? Would Mozilla like me to report it there while this bug is pending here?
Comment 7•1 year ago
|
||
I'm not sure it's exact ownership, but it's originally from Google. I think reporting it there probably makes sense.
Reporter | ||
Comment 8•1 year ago
|
||
(In reply to Jeff Muizelaar [:jrmuizel] from comment #7)
I'm not sure it's exact ownership, but it's originally from Google. I think reporting it there probably makes sense.
I'll report it to the Chromium team. It seems to be widely-used, but I'm not sure who (if anyone) is actually the official maintainer at this point. https://bugzilla.mozilla.org/show_bug.cgi?id=1341088 seems to indicate that https://github.com/khaledhosny/ots was/is(?) the official repository for it, but the "security" tab there has no content, so reporting it there doesn't seem safe.
Assignee | ||
Comment 9•1 year ago
|
||
Khaled is (and has always been, IIRC) the primary maintainer, though others (including myself) sometimes contribute. But I'm not sure what his preference would be for reporting potentially sensitive issues.
I do have an email address for him, if you'd like me to reach out directly. Though in this case, I'd be inclined to simply open a PR to use 64-bit arithmetic in the problematic calculation, and it'd probably get merged pretty quickly.
Reporter | ||
Comment 10•1 year ago
|
||
If (as seems likely) this bug turns out to be exploitable, I'd like to minimize the number of projects that get 0-dayed. Does Mozilla have a policy on this issue?
Reporter | ||
Comment 11•1 year ago
|
||
The data that can be forced to be read using this mechanism could be unowned heap if the STAT
table in the font file is compressed. If the STAT
table is uncompressed, the data is from a preceding part of the font file, (See GetTableData()
in gfx/ots/src/ots.cc
for details)
Here's a POC that demonstrates the read beyond bounds. Use it thusly:
- Build FF for a 32-bit architecture (I used Win32).
- Copy
ffbug_2218.htm
to a webserver in<root>/ffbug_2218/ffbug_2218.htm
. - Copy
Montserrat-VariableFont_wght-hacked.ttf
to<root>/resources/fonts/Montserrat/Montserrat-VariableFont_wght-hacked.ttf
(or put it somewhere else and modify the path inffbug_2218.htm
appropriately. - Start FF and attach a debugger to it.
- Set a BP on
OpenTypeSTAT::Parse()
. - Load
ffbug_2218.htm
. - When the BP fires, step lines 33-42.
- Examine
this->designAxesOffset
andthis->offsetToAxisValueOffsets
. Notice that they're0xfffffffc
and0xfffffffe
, respectively. - Step lines 62-8 and notice that the check doesn't detect the invalid value for
this->designAxesOffset
. - Step into line 71 and notice that it sets
table.offset_
to0xfffffffc
. - Step into line 74 and notice that
ReadU32()
's check overflows, and that thememcpy()
reads frombuffer+0xfffffffc
, which wraps tobuffer-4
(wherebuffer
is the lowest valid address here). - Set execution to line 91 to demonstrate the second, similar read beyond bounds.
- Notice that the check on lines 96-102 doesn't detect the broken value of
this->offsetToAxisValueOffsets
. - Step the first iteration of the loop beginning on line 104 and notice that line 107 reads from
buffer-2
instead of frombuffer
.
There are also some other overflow-vulnerable computations in this file, such as line 110. Really this code should be using CheckedInt
or something like it.
Reporter | ||
Comment 12•1 year ago
|
||
Reporter | ||
Comment 13•1 year ago
|
||
Reporter | ||
Comment 14•1 year ago
|
||
Reported to Google at https://bugs.chromium.org/p/chromium/issues/detail?id=1518885 .
Updated•1 year ago
|
Updated•1 year ago
|
Assignee | ||
Comment 15•1 year ago
|
||
cc'ing Dominik, who has received the corresponding report to chromium.
Updated•1 year ago
|
Comment 16•1 year ago
|
||
Jonathan, could you please assign a Severity to this?
Also, are we dependent on movement upstream for this one, or is there anything we can do to mitigate it?
Assignee | ||
Comment 17•1 year ago
|
||
The potential consequences of the out-of-bounds reads would be either (a) crash (so this could be a DoS vector), or (b) allowing an invalid STAT table to pass validation, so we pass it through to the rasterizer. In theory that should be harmless, as the rasterizer should handle it safely, but we prefer not to rely on that (especially as we can't audit/fix issues there might be in Core Text or DirectWrite) which is why we validate the resources before using them.
So while this is clearly a valid issue, I don't think it's higher than S3 at this point, though if there were actually an exploitable STAT-based bug in CT or DW, that could make it more significant.
I'd be fine with patching this locally, if we want to fix it in gecko ahead of upstream.
Comment 18•1 year ago
|
||
The severity field for this bug is set to S3. However, the bug is flagged with the sec-high
keyword.
:jfkthame, could you consider increasing the severity of this security bug?
For more information, please visit BugBot documentation.
Updated•1 year ago
|
Assignee | ||
Comment 19•1 year ago
|
||
This is just https://github.com/khaledhosny/ots/pull/277 from upstream, not really part
of this issue but touching the same code, so simplest to include it here.
Assignee | ||
Comment 20•1 year ago
|
||
Assignee | ||
Comment 21•1 year ago
|
||
Assignee | ||
Comment 22•1 year ago
|
||
Once we take these patches locally, I'll also open a PR to land them upstream in OTS.
Comment 23•11 months ago
|
||
![]() |
||
Comment 24•11 months ago
|
||
https://hg.mozilla.org/mozilla-central/rev/dfd86e34e53f
https://hg.mozilla.org/mozilla-central/rev/eed1acdcdfbd
https://hg.mozilla.org/mozilla-central/rev/df5fdbb9e3ec
Updated•11 months ago
|
Comment 25•11 months ago
|
||
The patch landed in nightly and beta is affected.
:jfkthame, is this bug important enough to require an uplift?
- If yes, please nominate the patch for beta approval.
- If no, please set
status-firefox125
towontfix
.
For more information, please visit BugBot documentation.
Assignee | ||
Comment 26•11 months ago
|
||
This is just https://github.com/khaledhosny/ots/pull/277 from upstream, not really part
of this issue but touching the same code, so simplest to include it here.
Original Revision: https://phabricator.services.mozilla.com/D204916
Updated•11 months ago
|
Assignee | ||
Comment 27•11 months ago
|
||
Original Revision: https://phabricator.services.mozilla.com/D204917
Updated•11 months ago
|
Assignee | ||
Comment 28•11 months ago
|
||
Original Revision: https://phabricator.services.mozilla.com/D204918
Updated•11 months ago
|
Comment 29•11 months ago
|
||
Uplift Approval Request
- Explanation of risk level: just makes bounds-checking arithmetic more careful to avoid potential overflow
- User impact if declined: potential for out-of-bounds accesses during font validation; could result in crash, unclear whether more serious consequences
- Code covered by automated testing: no
- Is Android affected?: yes
- Needs manual QE test: no
- String changes made/needed: none
- Risk associated with taking this patch: low risk
- Fix verified in Nightly: no
- Steps to reproduce for manual QE testing: n/a
Updated•11 months ago
|
Updated•11 months ago
|
Updated•11 months ago
|
Updated•11 months ago
|
Comment 30•11 months ago
|
||
uplift |
Updated•11 months ago
|
Updated•11 months ago
|
Updated•11 months ago
|
Comment 31•11 months ago
|
||
Is this something we're going to want on ESR also?
Assignee | ||
Comment 32•11 months ago
|
||
I'd suggest we take it, yes. I'll do the moz-phab uplift
thing...
Assignee | ||
Comment 33•11 months ago
|
||
This is just https://github.com/khaledhosny/ots/pull/277 from upstream, not really part
of this issue but touching the same code, so simplest to include it here.
Original Revision: https://phabricator.services.mozilla.com/D204916
Updated•11 months ago
|
Assignee | ||
Comment 34•11 months ago
|
||
Huh, looks like that uplift
only did the first patch, which doesn't actually address the issue.... I'll try again.
Assignee | ||
Comment 35•11 months ago
|
||
Original Revision: https://phabricator.services.mozilla.com/D204917
Updated•11 months ago
|
Assignee | ||
Comment 36•11 months ago
|
||
Original Revision: https://phabricator.services.mozilla.com/D204918
Updated•11 months ago
|
Comment 37•11 months ago
|
||
esr115 Uplift Approval Request
- User impact if declined: potential for out-of-bounds accesses during font validation
- Code covered by automated testing: no
- Fix verified in Nightly: no
- Needs manual QE test: no
- Steps to reproduce for manual QE testing: n/a
- Risk associated with taking this patch: low
- Explanation of risk level: just makes bounds-checking arithmetic more careful to avoid potential overflow
- String changes made/needed: none
- Is Android affected?: yes
Updated•11 months ago
|
Updated•11 months ago
|
Updated•11 months ago
|
Comment 38•11 months ago
|
||
uplift |
Updated•11 months ago
|
Updated•11 months ago
|
Comment 39•11 months ago
|
||
Updated•11 months ago
|
Updated•11 months ago
|
Updated•9 months ago
|
Updated•5 months ago
|
Description
•