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•2 years ago
|
Comment 1•2 years ago
|
||
FWIW, code coverage data on SearchFox says we are running at least some of the code in OpenTypeSTAT::Parse.
Reporter | ||
Comment 2•2 years 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•2 years 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•2 years 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•2 years 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•2 years 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•2 years 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•2 years 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•2 years ago
|
||
Reporter | ||
Comment 13•2 years ago
|
||
Reporter | ||
Comment 14•2 years ago
|
||
Reported to Google at https://bugs.chromium.org/p/chromium/issues/detail?id=1518885 .
Updated•2 years ago
|
Updated•2 years ago
|
Assignee | ||
Comment 15•2 years ago
|
||
cc'ing Dominik, who has received the corresponding report to chromium.
Updated•2 years ago
|
Comment 16•2 years 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•2 years 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•2 years 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•2 years ago
|
Assignee | ||
Comment 19•2 years 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•2 years ago
|
||
Assignee | ||
Comment 21•2 years ago
|
||
Assignee | ||
Comment 22•2 years ago
|
||
Once we take these patches locally, I'll also open a PR to land them upstream in OTS.
Comment 23•2 years ago
|
||
![]() |
||
Comment 24•2 years 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•2 years ago
|
Comment 25•2 years 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•2 years 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•2 years ago
|
Assignee | ||
Comment 27•2 years ago
|
||
Original Revision: https://phabricator.services.mozilla.com/D204917
Updated•2 years ago
|
Assignee | ||
Comment 28•2 years ago
|
||
Original Revision: https://phabricator.services.mozilla.com/D204918
Updated•2 years ago
|
Comment 29•2 years 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•2 years ago
|
Updated•2 years ago
|
Updated•2 years ago
|
Updated•2 years ago
|
Comment 30•2 years ago
|
||
uplift |
Updated•2 years ago
|
Updated•2 years ago
|
Updated•2 years ago
|
Comment 31•2 years ago
|
||
Is this something we're going to want on ESR also?
Assignee | ||
Comment 32•2 years ago
|
||
I'd suggest we take it, yes. I'll do the moz-phab uplift
thing...
Assignee | ||
Comment 33•2 years 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•2 years ago
|
Assignee | ||
Comment 34•2 years 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•2 years ago
|
||
Original Revision: https://phabricator.services.mozilla.com/D204917
Updated•2 years ago
|
Assignee | ||
Comment 36•2 years ago
|
||
Original Revision: https://phabricator.services.mozilla.com/D204918
Updated•2 years ago
|
Comment 37•2 years 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•2 years ago
|
Updated•2 years ago
|
Updated•2 years ago
|
Comment 38•2 years ago
|
||
uplift |
Updated•2 years ago
|
Updated•1 years ago
|
Comment 39•1 years ago
|
||
Updated•1 years ago
|
Updated•1 years ago
|
Updated•1 year ago
|
Updated•1 year ago
|
Description
•