Closed Bug 1170794 (CVE-2015-4522) Opened 9 years ago Closed 9 years ago

Overflow in nsUnicodeToUTF8::GetMaxLength can create memory-safety bugs in callers

Categories

(Core :: Internationalization, defect)

38 Branch
defect
Not set
normal

Tracking

()

RESOLVED FIXED
mozilla41
Tracking Status
firefox41 --- fixed

People

(Reporter: q1, Assigned: baku)

Details

(Keywords: csectype-intoverflow, sec-moderate, Whiteboard: [post-critsmash-triage][adv-main41+])

Attachments

(1 file, 2 obsolete files)

User Agent: Mozilla/5.0 (Windows NT 5.1; rv:36.0) Gecko/20100101 Firefox/36.0
Build ID: 20150305021524

Steps to reproduce:

nsUnicodeToUTF8::GetMaxLength (38.0.1\intl\uconv\nsUnicodeToUTF8.cpp) computes the destination string length *aDestLength without checking for overflow. This means that it can return a destination string length _smaller_ than the source string length, which can then cause memory-safety bugs in its caller:

15: NS_IMETHODIMP nsUnicodeToUTF8::GetMaxLength(const char16_t * aSrc, 
16:                                              int32_t aSrcLength,
17:                                              int32_t * aDestLength)
18: {
19:   // aSrc is interpreted as UTF16, 3 is normally enough.
20:   // But when previous buffer only contains part of the surrogate pair, we 
21:   // need to complete it here. If the first word in following buffer is not
22:   // in valid surrogate range, we need to convert the remaining of last buffer
23:   // to 3 bytes.
24:   *aDestLength = 3*aSrcLength + 3;
25:   return NS_OK;
26: }

If aSrcLength >= 0x2aaaaaaa, line 24 computes a negative value and returns it in *aDestLength. If the caller then uses this value without checking it, it could, for example, fail to allocate enough space for a string, causing overwriting of unowned memory. For example, EncodeString (38.0.1\netwerk\base\nsStandardURL.cpp) uses the returned value to allocate a buffer:

66: static nsresult
67: EncodeString(nsIUnicodeEncoder *encoder, const nsAFlatString &str, nsACString &result)
68: {
69:    nsresult rv;
70:    int32_t len = str.Length();
71:    int32_t maxlen;
72:
73:    rv = encoder->GetMaxLength(str.get(), len, &maxlen);
74:    if (NS_FAILED(rv))
75:        return rv;
76:
77:    char buf[256], *p = buf;
78:    if (uint32_t(maxlen) > sizeof(buf) - 1) {
79:        p = (char *) malloc(maxlen + 1);
80:        if (!p)
81:            return NS_ERROR_OUT_OF_MEMORY;
82:    }
83:
84:    rv = encoder->Convert(str.get(), &len, p, &maxlen);
85:    if (NS_FAILED(rv))
86:        goto end;
87:    ...
92:    p[maxlen] = 0;
93:    result.Assign(p);
94:
95:    len = sizeof(buf) - 1;
96:    rv = encoder->Finish(buf, &len);
97:    if (NS_FAILED(rv))
98:        goto end;
99:    buf[len] = 0;    ...

If str.Length() == 0x55555554 and encoder isa nsUnicodeToUTF8, then nsUnicodeToUTF8::GetMaxLength will compute maxlen == 0xffffffff (== -1), and line 79 will compute and allocate a buffer of size -1+1 == 0. Line 84 won't cause a problem because of how nsUnicodeToUTF8::Convert is implemented, but line 92 will zero the byte at p-1.

This write will cause unpredictable effects, possibly including disclosure of sensitive information.
Attached patch crash3.patch (obsolete) — Splinter Review
Attachment #8614653 - Flags: review?(ehsan)
Assignee: nobody → amarchesini
Status: UNCONFIRMED → NEW
Ever confirmed: true
Attachment #8614653 - Flags: review?(ehsan) → review+
Component: Untriaged → Internationalization
Product: Firefox → Core
Comment on attachment 8614653 [details] [diff] [review]
crash3.patch

[Security approval request comment]
How easily could an exploit be constructed based on the patch?

I think quite hard.

Do comments in the patch, the check-in comment, or tests included in the patch paint a bulls-eye on the security problem?

Yes.

Which older supported branches are affected by this flaw?

All of the branches.

Do you have backports for the affected branches? If not, how different, hard to create, and risky will they be?

Backporting is easy.

How likely is this patch to cause regressions; how much testing does it need?

No regressions.
Attachment #8614653 - Flags: sec-approval?
This needs a security rating before it can get sec-approval+. 

Is this simply information disclosure? What would happen if it is exploited?
That's a good question. Zeroing a random byte of memory could do almost anything, depending upon what the byte is. If it's a prohibited operations mask, it could give an attacker permission to read and/or write something to which she shouldn't have access. If it's part of a pointer, it could cause data to be read or written from an improper location, with unknown effects. If it's part of a string, it could cause truncation, with unknown effects. If it's an object's virtual function pointer, it could cause execution of incorrect functions, again with unknown effects.
Has anybody audited other encodings for a similar attack? The base implementation of GetMaxLength, http://mxr.mozilla.org/mozilla-central/source/intl/uconv/util/nsUCSupport.cpp#550, does

*aDestLength = aSrcLength * mMaxLengthFactor;

where mMaxLengthFactor can be 2 or 4 in multi-byte character sets.
(In reply to Simon Montagu :smontagu from comment #5)
> Has anybody audited other encodings for a similar attack? The base
> implementation of GetMaxLength,
> http://mxr.mozilla.org/mozilla-central/source/intl/uconv/util/nsUCSupport.
> cpp#550, does
> 
> *aDestLength = aSrcLength * mMaxLengthFactor;
> 
> where mMaxLengthFactor can be 2 or 4 in multi-byte character sets.

That looks to be similarly affected based on a quick look.
Comment on attachment 8614653 [details] [diff] [review]
crash3.patch

r- dveditz

Checking this in highlights the problem while not fixing the identical problem in several easily-found equivalent spots (that is, all the other decoders that implement the same method).
Attachment #8614653 - Flags: review-
Without more evidence this can be reached/triggered we need to go with sec-moderate
Comment on attachment 8614653 [details] [diff] [review]
crash3.patch

clearing sec-approval? since sec-moderate bugs don't need sec-approval+
Attachment #8614653 - Flags: sec-approval?
Attached patch crash3.patch (obsolete) — Splinter Review
Attachment #8617445 - Flags: review?(dveditz)
Attachment #8614653 - Attachment is obsolete: true
Attached patch crash3.patchSplinter Review
Attachment #8617445 - Attachment is obsolete: true
Attachment #8617445 - Flags: review?(dveditz)
Attachment #8617799 - Flags: review?(dveditz)
Any news about this patch?
Flags: needinfo?(dveditz)
Flags: sec-bounty?
Comment on attachment 8617799 [details] [diff] [review]
crash3.patch

Review of attachment 8617799 [details] [diff] [review]:
-----------------------------------------------------------------

Looks good with some nits to fix.
r=dveditz

::: intl/uconv/nsIUnicodeDecoder.h
@@ +102,5 @@
>     * @param aSrc        [IN] the source data buffer
>     * @param aSrcLength  [IN] the length of source data buffer
>     * @param aDestLength [OUT] the needed size of the destination buffer
>     * @return            NS_EXACT_LENGTH if an exact length was computed
> +   *                    NS_ERROR_OUT_OF_MEMORY if OOM

Here you add NS_ERROR_OUT_OF_MEMORY to the comment, but in the implementations you return NS_ERROR_FAILURE (and it's not exactly OOM). I don't really care which you use but they should match.

@@ +107,5 @@
>     *                    NS_OK is all we have is an approximation
>     */
> +   MOZ_WARN_UNUSED_RESULT NS_IMETHOD GetMaxLength(const char * aSrc,
> +                                                  int32_t aSrcLength,
> +                                                  int32_t * aDestLength) = 0;

Personally I don't really care, but in all my recent patches I've been nagged to rewrite these to put the * on the left any time I've touched old code that had them written with the * (or &) on the right or in the middle as here. If you get the chance please edit this before checking in.

Ditto all the similar places below.

The key bit of this edit, MOZ_WARN_UNUSUED_RESULT, is of course great.

::: intl/uconv/ucvlatin/nsUnicodeToUTF16.cpp
@@ +57,5 @@
>  {
> +  mozilla::CheckedInt32 length = 2;
> +
> +  if(0 != mBOM) {
> +    length *= (aSrcLength+1);

Space after "if" please. In theory aSecLength could be max int32 and the +1 could overflow to -max int32. Here the "length *=" check should catch it, but it might be better to rewrite this so all the math is checked:

  mozilla::CheckedInt32 length = aSrcLength;

  if (0 != mBOM) {
    length += 1;
  }
  length *=2;
Attachment #8617799 - Flags: review?(dveditz) → review+
Flags: needinfo?(dveditz)
https://hg.mozilla.org/integration/mozilla-inbound/rev/d772d098a392

I forgot to run |hg qref| with the comments.
https://hg.mozilla.org/mozilla-central/rev/d87836297505
https://hg.mozilla.org/mozilla-central/rev/d772d098a392
Status: NEW → RESOLVED
Closed: 9 years ago
Flags: in-testsuite?
Resolution: --- → FIXED
Target Milestone: --- → mozilla41
This bug's fix inadvertently caused clang 3.6 & newer to spam some "-Winconsistent-missing-override" build warnings. (by adding 'override' annotations to classes that were previously 100% "override"-annotation-free -- so now they're "inconsistent", hence the warning)

I pushed a followup to add that keyword where it was previously missing, to fix this warning. Using blanket r+ that ehsan granted me for fixes of this sort over in bug 1126447 comment 2.
https://hg.mozilla.org/integration/mozilla-inbound/rev/46adc427071d
Flags: sec-bounty? → sec-bounty+
Whiteboard: [post-critsmash-triage]
Group: core-security → core-security-release
Whiteboard: [post-critsmash-triage] → [post-critsmash-triage][adv-main41+]
Alias: CVE-2015-4522
Group: core-security-release
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: