Closed
Bug 1174015
(CVE-2016-1951)
Opened 9 years ago
Closed 9 years ago
Overflow in prprf/GrowStuff can cause memory-safety bug
Categories
(NSPR :: NSPR, defect, P1)
NSPR
NSPR
Tracking
(firefox44 wontfix, firefox45+ fixed, firefox46 fixed, firefox47 fixed, firefox-esr38 wontfix, firefox-esr45- fixed)
People
(Reporter: q1, Assigned: wtc)
References
Details
(Keywords: csectype-intoverflow, reporter-external, sec-moderate, Whiteboard: [post-critsmash-triage])
Attachments
(1 file, 3 obsolete files)
2.99 KB,
patch
|
KaiE
:
review+
wtc
:
checked-in+
|
Details | Diff | Splinter Review |
GrowStuff (38.0.1\nsprpub\pr\src\io\prprf.c) can cause an overflow if the size of the existing buffer in units of bytes (maxlen) + the size of the string to be appended (len) > 0xffffffff. In this case, the function either allocates a tiny buffer or doesn't even attempt to allocate one large enough, then writes the new string beyond the buffer's end.
1081: static int GrowStuff(SprintfState *ss, const char *sp, PRUint32 len)
1082: {
1083: ptrdiff_t off;
1084: char *newbase;
1085: PRUint32 newlen;
1086:
1087: off = ss->cur - ss->base;
1088: if (off + len >= ss->maxlen) {
1089: /* Grow the buffer */
1090: newlen = ss->maxlen + ((len > 32) ? len : 32);
1091: if (ss->base) {
1092: newbase = (char*) PR_REALLOC(ss->base, newlen);
1093: } else {
1094: newbase = (char*) PR_MALLOC(newlen);
1095: }
1096: if (!newbase) {
1097: /* Ran out of memory */
1098: return -1;
1099: }
1100: ss->base = newbase;
1101: ss->maxlen = newlen;
1102: ss->cur = ss->base + off;
1103: }
1104:
1105: /* Copy data */
1106: while (len) {
1107: --len;
1108: *ss->cur++ = *sp++;
...
On 32-bit platforms, line 1088 can overflow. In that case, the code doesn't allocate a new buffer, but instead writes the new data beginning just beyond the end of the existing one. Presumably this can occur only if a caller attempts to write a single very long string, because otherwise the system would run out of memory.
On 64-bit platforms, line 1088 doesn't overflow (because ptrdiff_t isa signed 64-bit int), but line 1090 does. In that case, the code allocates a buffer that is much too small, then writes the new data far beyond its end.
The functions that use GrowStuff (PR_vsmprintf and PR_vsprintf_append in prptr.c) are used in several places, but I don't know whether any of the callers ultimately derive potentially very long strings from untrusted sources.
[BTW, I notice that the new Bugzilla doesn't handle pasting of long lines as gracefully as the old one].
Updated•9 years ago
|
Assignee: nobody → wtc
Component: Untriaged → NSPR
Product: Firefox → NSPR
Version: 38 Branch → other
Updated•9 years ago
|
Flags: sec-bounty?
Updated•9 years ago
|
Keywords: csectype-intoverflow,
sec-moderate
Assignee | ||
Comment 1•9 years ago
|
||
q1: thank you very much for the bug report.
Kai and q1: please review this patch. I check for potential
overflows before the two additions.
Note that PR_Malloc and PR_Realloc functions are defined to
take a PRUint32 allocation size argument. This is why the
sums must not exceed PR_UINT32_MAX.
Attachment #8624977 -
Flags: review?(kaie)
Attachment #8624977 -
Flags: feedback?(q1)
Assignee | ||
Updated•9 years ago
|
Status: NEW → ASSIGNED
OS: Unspecified → All
Priority: -- → P1
Hardware: Unspecified → All
Target Milestone: --- → 4.10.9
Comment on attachment 8624977 [details] [diff] [review]
Proposed patch
>diff --git a/pr/src/io/prprf.c b/pr/src/io/prprf.c
>--- a/pr/src/io/prprf.c
>+++ b/pr/src/io/prprf.c
>@@ -1100,19 +1100,28 @@ PR_IMPLEMENT(PRUint32) PR_vsxprintf(PRSt
> */
> static int GrowStuff(SprintfState *ss, const char *sp, PRUint32 len)
> {
> ptrdiff_t off;
> char *newbase;
> PRUint32 newlen;
>
> off = ss->cur - ss->base;
Hmm, I think that the above expression is not technically safe. ptrdiff_t is a *signed* type per C++11 s.5.7(5). Under 32 bit Windows, it's typedefed to int, so differences > 0x7fffffff underflow to negative values. This seems to invoke the clause of s.5.7(5) that says:
"As with any other arithmetic overflow, if the result does not fit in the space provided, the behavior is undefined."
It depends what that section means by "fit". The standards team is discussing changes to this section that would ratify my conclusion (http://open-std.org/JTC1/SC22/WG21/docs/papers/2015/n4430.html ):
"If the expressions P and Q point to, respectively, elements i and j of the same array object, the expression P - Q has the value i - j; otherwise, the behavior is undefined. [ Note: If the value i - j is not in the range of representable values of type std::ptrdiff_t, the behavior is undefined. — end note ]"
I *suppose* that, on real platforms, this expression produces the same bit pattern it would produce if ptrdiff_t were unsigned, so this is realistically safe?
Perhaps we should disallow strings long enough to invoke this problem on any supported platform (> 0x7fffffff). If you choose to do that, I recommend using mfbt\CheckedInt.h for all calculations involved in determining buffer sizes.
>+ if (PR_UINT32_MAX - len < off) {
With the above provisos, I think that this is OK, though the reason is rather subtle: s.4.7(2) by way of the "usual arithmetic conversions" s.5(10):
"If the destination type is unsigned, the resulting value is the least unsigned integer congruent to the source
integer (modulo 2 n where n is the number of bits used to represent the unsigned type). [Note: In a two’s
complement representation, this conversion is conceptual and there is no change in the bit pattern (if there
is no truncation). —end note ]"
>+ /* off + len would be too big. */
>+ return -1;
>+ }
> if (off + len >= ss->maxlen) {
> /* Grow the buffer */
>- newlen = ss->maxlen + ((len > 32) ? len : 32);
>+ PRUint32 increment = (len > 32) ? len : 32;
>+ if (PR_UINT32_MAX - ss->maxlen < increment) {
>+ /* ss->maxlen + increment would overflow. */
>+ return -1;
>+ }
>+ newlen = ss->maxlen + increment;
Same comments as above.
Assignee | ||
Comment 4•9 years ago
|
||
(In reply to q1 from comment #2)
> Comment on attachment 8624977 [details] [diff] [review]
> Proposed patch
>
> >diff --git a/pr/src/io/prprf.c b/pr/src/io/prprf.c
> >--- a/pr/src/io/prprf.c
> >+++ b/pr/src/io/prprf.c
> >@@ -1100,19 +1100,28 @@ PR_IMPLEMENT(PRUint32) PR_vsxprintf(PRSt
> > */
> > static int GrowStuff(SprintfState *ss, const char *sp, PRUint32 len)
> > {
> > ptrdiff_t off;
> > char *newbase;
> > PRUint32 newlen;
> >
> > off = ss->cur - ss->base;
>
> Hmm, I think that the above expression is not technically safe.
ptrdiff_t is defined as the type of the difference between two pointers
of the same type. So I think a C implementation needs to do whatever it
takes to prevent differences from underflowing to negative values, such
as making malloc() fail if the requested size is > 0x7fffffff under 32-bit
Windows.
I will attach a new version of the patch that doesn't allow ss->maxlen
to be > 0x7fffffff (PR_INT32_MAX). That will ensure ss->cur - ss->base
<= 0x7fffffff (PR_INT32_MAX).
Note: NSPR cannot use mfbt/CheckedInt.h. NSPR can only use system
libraries.
Assignee | ||
Comment 5•9 years ago
|
||
Please review the new patch. Thanks.
Attachment #8624977 -
Attachment is obsolete: true
Attachment #8624977 -
Flags: review?(kaie)
Attachment #8624977 -
Flags: feedback?(q1)
Attachment #8625396 -
Flags: review?(kaie)
Assignee | ||
Comment 6•9 years ago
|
||
In this patch I updated the assertion and check in PR_vsnprintf
more accurately, preserving the comparisons of |outlen| with 0.
Attachment #8625396 -
Attachment is obsolete: true
Attachment #8625396 -
Flags: review?(kaie)
Attachment #8625397 -
Flags: review?(kaie)
Updated•9 years ago
|
Target Milestone: 4.10.9 → ---
Assignee | ||
Updated•9 years ago
|
Target Milestone: --- → 4.11
Updated•9 years ago
|
Group: core-security → core-security-release
Comment 7•9 years ago
|
||
+ if (PR_UINT32_MAX - ss->maxlen < len || ss->maxlen + len > PR_INT32_MAX) {
I agree this code is correct.
But this code took me too long to understand. I would prefer to simplify it, to make it easier to understand for less intelligent people like me. ;-)
Given that the sum must be smaller than the signed max, we can require that each must be smaller than the signed max. If both are smaller than the signed max, the sum will be smaller than the unsigned max.
Therefore I'd prefer to rewrite the failure condition above as:
/* If both len and maxlen are smaller than PR_INT32_MAX, the sum cannot overflow. */
if (len > PR_INT32_MAX || ss->maxlen > PR_INT32_MAX || ss->maxlen + len > PR_INT32_MAX) {
return -1;
}
If you don't like my code, then please add a comment to your original code like this:
/* We require "ss->maxlen + len < PR_UINT32_MAX".
* It ensures our calculation of the sum will not overflow PRUint32.
* Rewriting the test as "PR_UINT32_MAX - ss->maxlen < len" ensures we don't need
* a larger type for the test.
*/
If you think that a rewrite or a comment is unnecessary, I'd argue, this overflow risk wasn't obvious to the original programmer, and it might not be obvious for future readers.
Comment 8•9 years ago
|
||
+ PRUint32 increment = (len > 32) ? len : 32;
+ if (PR_UINT32_MAX - ss->maxlen < increment) {
+ /* ss->maxlen + increment would overflow. */
+ return -1;
+ }
What if "ss->maxlen + 32 > PR_INT32_MAX",
but "ss->maxlen + len <= PR_INT32_MAX" ?
Do we want to detect this scenario, and only grow by len, even if "len<32" ?
Comment 9•9 years ago
|
||
Comment on attachment 8625397 [details] [diff] [review]
Proposed patch v3
Your code looks correct to me. I believe it might be acceptable that we might not use the full size up to PR_INT32_MAX, but might potentially limit it to PR_INT32_MAX-31.
I should have your full patch first, because you added a comment to the later calculations, which made the intention clear. So, please consider to add some kind of comment to the first condition
Because I don't want to delay this fix further, r=kaie, with or without further improvements.
Attachment #8625397 -
Flags: review?(kaie) → review+
Assignee | ||
Comment 10•9 years ago
|
||
Kai: thanks for the review! I added a comment to the first check
as you requested.
https://hg.mozilla.org/projects/nspr/rev/96381e3aaae2
Attachment #8625397 -
Attachment is obsolete: true
Attachment #8692172 -
Flags: review?(kaie)
Attachment #8692172 -
Flags: checked-in+
Assignee | ||
Updated•9 years ago
|
Status: ASSIGNED → RESOLVED
Closed: 9 years ago
Resolution: --- → FIXED
Target Milestone: 4.11 → 4.12
Updated•9 years ago
|
Flags: sec-bounty? → sec-bounty+
Updated•9 years ago
|
Attachment #8692172 -
Flags: review?(kaie) → review+
Comment 11•9 years ago
|
||
Should this get a CVE?
Who wants to contribute a wording for release notes?
Should this get backported to Firefox branches?
Flags: needinfo?(dveditz)
Comment 12•9 years ago
|
||
How about:
* Fixed a memory allocation bug in the PR_vsmprintf and
PR_vsprintf_append functions.
Comment 13•9 years ago
|
||
(In reply to Kai Engert (:kaie) from comment #12)
> How about:
>
> * Fixed a memory allocation bug in the PR_vsmprintf and
> PR_vsprintf_append functions.
Since nobody has responded, and since the function PR_vsxprintf might also be affected, and because I don't have time to fully analyze in order to write the release notes, I will use a simplified text like the following:
* fixed a memory allocation bug related to the PR_*printf functions
Comment 14•9 years ago
|
||
You can use CVE-2016-1951 for the CVE.
It would probably be nice to uplift this to Aurora and Beta so it's on the next ESR branch
Alias: CVE-2016-1951
Flags: needinfo?(dveditz)
Comment 15•9 years ago
|
||
(In reply to Daniel Veditz [:dveditz] from comment #14)
> You can use CVE-2016-1951 for the CVE.
thanks
> It would probably be nice to uplift this to Aurora and Beta so it's on the
> next ESR branch
Aurora 46 has already been uplifted to use NSPR 4.12
It would be a good idea to uplift Beta 45 to NSPR 4.12, too.
Comment 16•9 years ago
|
||
[Tracking Requested - why for this release]:
This bug is already fixed in Firefox 46, which means we'll need it in the ESR 45.1 release to match. In terms of testing it would be better to uplift NSS 4.12 to 45 directly (bug 1244062) now rather than do it after 45 for 45.1.
status-firefox44:
--- → wontfix
status-firefox45:
--- → affected
status-firefox46:
--- → fixed
status-firefox47:
--- → fixed
status-firefox-esr38:
--- → wontfix
tracking-firefox45:
--- → ?
Depends on: 1244062
Comment 18•9 years ago
|
||
Too late for 45 as today is the release day.
Comment 20•9 years ago
|
||
Is it ok to mention this CVE publicly and link to the commit which fixes it?
(In a public Red Hat bug that is), i am asking because there is no mention on the CVE id in the release notes or anywhere else and it makes little sense to keep this private now.
Comment 21•9 years ago
|
||
(In reply to Huzaifa Sidhpurwala from comment #20)
> Is it ok to mention this CVE publicly and link to the commit which fixes it?
> (In a public Red Hat bug that is), i am asking because there is no mention
> on the CVE id in the release notes or anywhere else and it makes little
> sense to keep this private now.
Because we haven't shipped a release with this fixed yet. This is fixed in Firefox 46. We just shipped 45.
So, when 46 comes out, this will be in an advisory then.
As a sec-moderate, it is unlikely to be taken into an ESR release as well.
Updated•9 years ago
|
Flags: qe-verify-
Whiteboard: [post-critsmash-triage]
Comment 22•9 years ago
|
||
(In reply to Al Billings [:abillings] from comment #21)
> (In reply to Huzaifa Sidhpurwala from comment #20)
> > Is it ok to mention this CVE publicly and link to the commit which fixes it?
> > (In a public Red Hat bug that is), i am asking because there is no mention
> > on the CVE id in the release notes or anywhere else and it makes little
> > sense to keep this private now.
>
> Because we haven't shipped a release with this fixed yet. This is fixed in
> Firefox 46. We just shipped 45.
>
> So, when 46 comes out, this will be in an advisory then.
>
> As a sec-moderate, it is unlikely to be taken into an ESR release as well.
Al, I think the above is wrong.
This bug is fixed in NSPR 4.12.
NSPR 4.12 is already used by Firefox ESR 45.
The upgrade was done in bug 1244062.
See this page for an overview of NSPR/NSS versions used in the Firefox branches:
http://kuix.de/mozilla/versions/
Comment 23•9 years ago
|
||
(In reply to Kai Engert (:kaie) from comment #22)
> This bug is fixed in NSPR 4.12.
> NSPR 4.12 is already used by Firefox ESR 45.
> The upgrade was done in bug 1244062.
In that case, should the flags on this bug be updated? If so, please do that.
Comment 24•9 years ago
|
||
(In reply to Kai Engert (:kaie) from comment #22)
> Al, I think the above is wrong.
Too bad no one noticed a month ago.
> This bug is fixed in NSPR 4.12.
> NSPR 4.12 is already used by Firefox ESR 45.
Comment 15 from you stated that NSPR 4.12 needed uplift to Beta. No one ever said differently in this bug so, without going digging on my own, I had no idea this was fixed in 45...
> The upgrade was done in bug 1244062.
Someone should have mentioned that here a month ago.
> See this page for an overview of NSPR/NSS versions used in the Firefox
> branches:
> http://kuix.de/mozilla/versions/
Yes, I'm aware of this but when I have comments in a bug saying "We should maybe take this on beta", that implies it isn't on beta (which is 45) and then no one said otherwise.
Updated•9 years ago
|
Updated•9 years ago
|
status-firefox-esr45:
--- → fixed
Comment 25•8 years ago
|
||
Folks, should this be public now?
Updated•8 years ago
|
Group: core-security-release
Updated•6 months ago
|
Keywords: reporter-external
You need to log in
before you can comment on or make changes to this bug.
Description
•