Closed Bug 1478950 Opened 7 years ago Closed 6 years ago

(FTPDirListConv) Assertion failure: CheckCapacity(aLength) (String is too large.), at xpcom/string/nsTSubstring.h:876

Categories

(Core Graveyard :: Networking: FTP, defect, P2)

defect

Tracking

(firefox-esr52 wontfix, firefox-esr60 wontfix, firefox61 wontfix, firefox62 wontfix, firefox63 wontfix, firefox67 wontfix, firefox68 wontfix, firefox69 fixed)

RESOLVED FIXED
mozilla69
Tracking Status
firefox-esr52 --- wontfix
firefox-esr60 --- wontfix
firefox61 --- wontfix
firefox62 --- wontfix
firefox63 --- wontfix
firefox67 --- wontfix
firefox68 --- wontfix
firefox69 --- fixed

People

(Reporter: u473386, Assigned: michal)

References

Details

(Keywords: sec-other, Whiteboard: [necko-triaged][adv-main63-][post-critsmash-triage][adv-main69-])

Attachments

(4 files, 1 obsolete file)

Attached file bug
User Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36 Steps to reproduce: This was found by the FTPDirListConv fuzzing target (WIP). Minimal test case attached. Assertion failure: CheckCapacity(aLength) (String is too large.), at mozilla-central/xpcom/string/nsTSubstring.h:876 ==4722==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000 (pc 0x7f94df0fdc15 bp 0x7ffc6a7bd6b0 sp 0x7ffc6a7bd6a0 T0) ==4722==The signal is caused by a WRITE memory access. ==4722==Hint: address points to the zero page. #0 0x7f94df0fdc14 in nsTSubstring<char>::nsTSubstring(char*, unsigned int, mozilla::detail::StringDataFlags, mozilla::detail::StringClassFlags) xpcom/string/nsTSubstring.h:876:5 #1 0x7f94df1127c8 in nsTDependentSubstring<char>::nsTDependentSubstring(char const*, char const*) xpcom/string/nsTDependentSubstring.cpp:57:5 #2 0x7f94df115d75 in nsTDependentSubstring<char> const Substring<char>(char const*, char const*) xpcom/string/nsTDependentSubstring.cpp:92:10 #3 0x7f94dfaad74c in nsFTPDirListingConv::DigestBufferLines(char*, nsTString<char>&) netwerk/streamconv/converters/nsFTPDirListingConv.cpp:270:37 #4 0x7f94dfaac62c in nsFTPDirListingConv::OnDataAvailable(nsIRequest*, nsISupports*, nsIInputStream*, unsigned long, unsigned int) netwerk/streamconv/converters/nsFTPDirListingConv.cpp:131:12 #5 0x7f94e94c1c02 in FuzzingRunFTPConv(nsCOMPtr<nsIInputStream>) netwerk/streamconv/converters/fuzztest/FTPDirListConv_fuzzer.cpp:64:9 The problem appears to be that bogus result.fe_fnlen is set in ParseFTPList.
Blocks: 1474492
Group: firefox-core-security → core-security
Status: UNCONFIRMED → NEW
Component: Untriaged → Networking: FTP
Ever confirmed: true
OS: Unspecified → All
Product: Firefox → Core
Hardware: Unspecified → All
Valentin, can you help looking into this or suggest someone who can? Thanks!
Flags: needinfo?(valentin.gosu)
Group: core-security → network-core-security
Assignee: nobody → valentin.gosu
Flags: needinfo?(valentin.gosu)
Priority: -- → P1
Whiteboard: [necko-triaged]
I can take a look at this. But I don't know how to reproduce this due to the missing of FTPDirListConv_fuzzer.cpp. @pdknsk, could you let me know where to find this file and how to run this test case? Thanks.
Flags: needinfo?(pdknsk)
It's still WIP. I have CC'd you to the other bug, which has it in patch FTPDirListConv-3.
Flags: needinfo?(pdknsk)
Sorry that I don't have a linux enviornment right now. It will take more time to investigate this bug for me.
I think it might be difficult to track down why fe_fnlen is set incorrectly in ParseFTPList. I propose a more general fix that checks fe_fnlen for bogus values, and skips it. It's the file name length of a single directory listing line. The bug is that it tries to pre-allocate a bogus value for the output string, I think. I suggest 1MB as a generous limit, or 32K as a resonable limit.
As suggested by pdknsk, add 32k limit to list line.
Comment on attachment 8998771 [details] Bug 1478950 - Add a max limit to FTP list line Valentin Gosu [:valentin] has approved the revision.
Attachment #8998771 - Flags: review+
Assignee: valentin.gosu → kershaw
There is no need to upload a new version of the patch if only the reviewer gets added. That's something the sheriffs can do. https://hg.mozilla.org/integration/mozilla-inbound/rev/f3b637d1359222dffc41dd9ba4d5b56416e2cce1
Keywords: checkin-needed
Group: network-core-security → core-security-release
Status: NEW → RESOLVED
Closed: 7 years ago
Resolution: --- → FIXED
Target Milestone: --- → mozilla63
Please request Beta and ESR60 approval when you get a chance. Also, this needs a sec rating.
Flags: needinfo?(kershaw)
(In reply to Ryan VanderMeulen [:RyanVM] from comment #12) > Please request Beta and ESR60 approval when you get a chance. Also, this > needs a sec rating. I'd like to decide the sec rating first. After that I'll ask for sec-approval and uplift.
Flags: needinfo?(kershaw)
Keywords: sec-low
@decoder, please correct me if I am wrong. I think this bug's sec rating is low, right? Thanks.
Flags: needinfo?(choller)
Essentially this bug is sec-none, the crash cannot be exploited in any way. The reason why we have marked this as s-s is to not point to ongoing fuzzing operations in the linked bug. Marked it as sec-other for now.
Flags: needinfo?(choller)
Keywords: sec-lowsec-other
(In reply to Christian Holler (:decoder) from comment #15) > Essentially this bug is sec-none, the crash cannot be exploited in any way. > The reason why we have marked this as s-s is to not point to ongoing fuzzing > operations in the linked bug. Marked it as sec-other for now. Thanks. Ryan, if you agree, I think we don't have to uplift this one.
Flags: needinfo?(ryanvm)
Agreed, sounds reasonable.
Attachment #8998771 - Attachment is obsolete: true
I'm afraid the bug has been fixed incorrectly. The problem is not the LIST line length exceeding a certain value (the test case is only 37 bytes). It's rather that ParseFTPList parses the LIST line somehow incorrectly and gets a bogus file name length (for a file name entry in that LIST line). https://dxr.mozilla.org/mozilla-central/source/netwerk/streamconv/converters/nsFTPDirListingConv.cpp#271 It then tries to allocate that bogus length, which fails. I added some debug code and the value of result.fe_fnlen in this case is 4294967294. This makes me suspect an unsigned underflow bug in ParseFTPList. Note there may be an actual security bug here. Substring takes start/end pointers. It's just that CheckCapacity is called before it tries to read 4GB past the pointer. I think a good fix might be to check if the end pointer is still within the line.
Status: RESOLVED → REOPENED
Resolution: FIXED → ---
I'm thinking code like this. if (result.fe_fname + result.fe_fnlen > eol) { // fail parsing or just skip the single bogus line }
(Skipping the line probably confuses the aString mechanism, which I don't know how it's supposed to work.)
@pdknsk, it looks like you are the perfect person to fix this, since you already know where the problem is and understand how to verify it. Do you mind if I re-assign this bug to you? If you don't have enough cycles, I can still work on this.
Flags: needinfo?(pdknsk)
Sure, but it might take a while.
Flags: needinfo?(pdknsk)
(In reply to pdknsk from comment #22) > Sure, but it might take a while. Thanks.
Assignee: kershaw → pdknsk
Should the original fix here be backed out?
Flags: needinfo?(kershaw)
(In reply to :Gijs (he/him) from comment #24) > Should the original fix here be backed out? I think we don't have to back it out, since it's reasonable to add a limit check.
Flags: needinfo?(kershaw)
Attached patch ftp-patchSplinter Review
I think this should definitely be classified as a security bug, even though the attached earlier test case isn't (but only because it gets lucky). I'll explain. The function parses an FTP listing, and converts it into a different format for display. Each line is fed to ParseFTPLine, which then returns a pointer to the start of the file name (fe_fname) within that line and the length of the file name (fe_fnlen). If fe_fnlen is bogus (4294967294 with the test case) it over-reads past the line into the output string. With the attached test case CheckCapacity asserts that the output string cannot exceed 2GB, so it fails before. For a security bug the fuzzer needs to produce a test case that causes bogus fe_fnlen of less than 2GB. I think that chance is (or was) good.
Comment on attachment 9003704 [details] [diff] [review] ftp-patch Setting a reviewer so this doesn't get lost. :decoder, do you want to adjust the severity given comment 26?
Flags: needinfo?(choller)
Attachment #9003704 - Flags: review?(valentin.gosu)
Attachment #9003704 - Flags: review?(valentin.gosu) → review+
I think we need a better root cause analysis here. I've investigated this a bit and the bug seems to be related to OS/2 server support. The buggy fe_fnlen value is set here: https://searchfox.org/mozilla-central/rev/55da592d85c2baf8d8818010c41d9738c97013d2/netwerk/streamconv/converters/ParseFTPList.cpp#914 It is the result of a pointer subtraction with -2 as the result. In this case, the filename (fe_fname) has string length 0. > DEBUG: fe_fname is 0x604000051734 with length 0 > DEBUG: &(line[linelen_sans_wsp])) is 0x604000051732 If the difference here was controllable (e.g. not fixed to -2) and could be forced to wrap around so much that we don't hit the 2GB limit check, then this would cause an over-read later when constructing the substring. In order to determine if this is really exploitable, we would have to understand this code better and figure out if the difference here is a fixed value. I have some fuzzing running that tries to find other examples of this bug with a different difference right now, but so far no results. Moving this needinfo to a Necko peer. Valentin: This code needs some investigating and I don't feel like fixing symptoms around it if I don't really understand the code. The question is, do we have anyone that really understands this? Should we send someone over to Google to drag dougt back here and fix it? ;P Alternatively, we could remove this code entirely if it isn't required anymore (at least the OS/2 support). It certainly looks scary enough.
Flags: needinfo?(choller)
Flags: needinfo?(valentin.gosu)
Another way how this could become a security bug is if at some point the string limit is raised to 4GB. I think a general catch-all check is perhaps not so bad, considering the maze that is ParseFTPList. On the user side, that (most likely not in a valid format) FTP line is missing in the output.
(In reply to Christian Holler (:decoder) from comment #28) > Moving this needinfo to a Necko peer. Valentin: This code needs some > investigating and I don't feel like fixing symptoms around it if I don't > really understand the code. The question is, do we have anyone that really > understands this? I don't think we have anyone who already knows how that code is working. In the past we fixed issues on a case-by-case basis, and that seemed to work. I really hope we get to a place where we can remove FTP support entirely. > Should we send someone over to Google to drag dougt back here and fix it? ;P I didn't know that was an option. YES! =) > Alternatively, we could remove this code entirely if it > isn't required anymore (at least the OS/2 support). It certainly looks scary > enough. I am OK with removing OS/2 support.
Flags: needinfo?(valentin.gosu)
Keep in mind we do have gtest support for testing FTP parsing stuff, that I added in bug 1402151.
Assignee: pdknsk → nobody
I think what we need to move forward here is someone (or data) that can tell us which of these FTP implementations we need and which we don't. I am strongly against disabling the entire FTP functionality because it is important to the open source community. But we support certain odd formats in there that *should* have no relevancy anymore nowdays, e.g.: * Virtual Machine/Conversational Monitor System (IBM Mainframe) * OS/2 * 16-bit Windows (e.g. Windows 3.1) I am not sure what a good way would be to move forward. Here are some ideas: * We could add Telemetry to figure out if anyone actually uses any of these formats (and I would doubt it). * We could disable some of these at runtime and hide them behind a pref (mitigates security risks but still allows people that really need it to use it). * An entirely different approach would be to look at Chrome's rewritten FTP code and import that instead. * If we had the time and resources, rewrite this in Rust \o/ Forwarding this to Dragana to get some input on this.
Flags: needinfo?(dd.mozilla)
I think the format names may not necessarily reflect the system, but perhaps only the origin. Chrome also supports VMS. https://cs.chromium.org/chromium/src/net/ftp/ftp_directory_listing_parser_vms.cc
(In reply to pdknsk from comment #33) > I think the format names may not necessarily reflect the system, but perhaps > only the origin. Chrome also supports VMS. > > https://cs.chromium.org/chromium/src/net/ftp/ > ftp_directory_listing_parser_vms.cc Hm, if this is used somewhere else as well, then we might need to keep the code around. Maybe we could import Google's code, it looks a lot saner than ours.
Whiteboard: [necko-triaged] → [necko-triaged][adv-main63-]
Let me add my 2 cents on how I think we can move this issue forward: 1. We figure out *exactly* which assign of fe_fnlen that gets the length wrong, and exactly what the input line is that triggers this. I would feel much safer if we would act at once when we figure out the format is wrong rather than check the end results and see if the length is bananas. 2. Then we tighten the parser for that line format so that we can't get the name length wrong. And we land that fix. If we find further problems once this is landed, we iterate on this. I don't believe in importing someone else's code for this, and I fear that disabling parts of the parser with a pref only postpones this work since someone will eventually enable that section and get hit by this bug down the line. pdknsk, can we get a recipe to reproduce this or can you work on making ParseFTPList() instead return an error when this happens?
Flags: needinfo?(pdknsk)
I'm not familiar with the error reporting machinery, but you can just modify patch ftp-patch I attached previously. + if (result.fe_fname + result.fe_fnlen > eol) { + // report error + } The exact line to trigger this is attached as 37-bytes attachment bug.
Flags: needinfo?(pdknsk)
Please raise the priority again if this is actually exploitable.
Status: REOPENED → NEW
Priority: P1 → P3

This needs to be fixed because it is blocking any kind of additional fuzz testing on the FTP code (which we consider important to perform as this code is really old and not well-tested).

(In reply to Christian Holler (:decoder) from comment #38)

This needs to be fixed because it is blocking any kind of additional fuzz testing on the FTP code (which we consider important to perform as this code is really old and not well-tested).

Move this to P2 for now.
I'll also bring this to our round table of necko meeting.

Priority: P3 → P2

Michal will try to fix this bug.
Thanks!

Assignee: nobody → michal.novotny

The parsing code uses filename without checking its presence. This patch ensures that the filename contains at least one non white space character.

Has the security diagnosis from comment 15/16 changed or is this still a wontfix for uplifts?

Flags: needinfo?(dd.mozilla) → needinfo?(michal.novotny)

I think the severity hasn't changed. result.fe_fnlen will always be slightly less than UINT32_MAX and assertion in
nsTSubstring::nsTSubstring is a release assertion, so it should always assert and we won't access memory out of the buffer.

Flags: needinfo?(michal.novotny)
Flags: qe-verify-
Whiteboard: [necko-triaged][adv-main63-] → [necko-triaged][adv-main63-][post-critsmash-triage]
Whiteboard: [necko-triaged][adv-main63-][post-critsmash-triage] → [necko-triaged][adv-main63-][post-critsmash-triage][adv-main69-]
Group: core-security-release
Product: Core → Core Graveyard
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Creator:
Created:
Updated:
Size: