Bug 1804564 Comment 0 Edit History

Note: The actual edited comment in the bug view page will always show the original commenter’s name and original timestamp.

`EncodeInputStream()` (xpcom/io/base64.cpp) repeatedly reads from the specified stream and encodes into `aDest` until the stream is exhausted. However, the function uses the requested input data length `aCount` or the (then-current) stream length `count64` returned by `aInputStream->Available()` to calculate how much memory to allocate for the output buffer `aDest`` (line 209, below). If `aInputStream` can return additional data beyond the specified input data length --  which is permissible for `nsIInputStream` objects -- `EncodeInputStream()` will write beyond bounds:

      186: template <typename T>
      187: nsresult EncodeInputStream(nsIInputStream* aInputStream, T& aDest,
      188:                            uint32_t aCount, uint32_t aOffset) {
      189:   nsresult rv;
      190:   uint64_t count64 = aCount;
      191: 
      192:   if (!aCount) {
      193:     rv = aInputStream->Available(&count64);
      194:     if (NS_WARN_IF(NS_FAILED(rv))) {
      195:       return rv;
      196:     }
      197:     // if count64 is over 4GB, it will be failed at the below condition,
      198:     // then will return NS_ERROR_OUT_OF_MEMORY
      199:     aCount = (uint32_t)count64;
      200:   }
      201: 
      202:   const auto base64LenOrErr = CalculateBase64EncodedLength(count64, aOffset);
      203:   if (base64LenOrErr.isErr()) {
      204:     // XXX For some reason, it was NS_ERROR_OUT_OF_MEMORY here instead of
      205:     // NS_ERROR_FAILURE, so we keep that.
      206:     return NS_ERROR_OUT_OF_MEMORY;
      207:   }
      208: 
      209:   if (!aDest.SetLength(base64LenOrErr.inspect(), mozilla::fallible)) {
      210:     return NS_ERROR_OUT_OF_MEMORY;
      211:   }
      212: 
      213:   EncodeInputStream_State<T> state;
      214:   state.charsOnStack = 0;
      215:   state.c[2] = '\0';
      216:   state.buffer = aOffset + aDest.BeginWriting();
      217: 
      218:   while (true) {
      219:     uint32_t read = 0;
      220: 
      221:     rv = aInputStream->ReadSegments(&EncodeInputStream_Encoder<T>,
      222:                                     (void*)&state, aCount, &read);
      223:     if (NS_FAILED(rv)) {
      224:       if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
      225:         MOZ_CRASH("Not implemented for async streams!");
      226:       }
      227:       if (rv == NS_ERROR_NOT_IMPLEMENTED) {
      228:         MOZ_CRASH("Requires a stream that implements ReadSegments!");
      229:       }
      230:       return rv;
      231:     }
      232: 
      233:     if (!read) {
      234:       break;
      235:     }
      236:   }
      ...
      249:   return NS_OK;
      250: 
      251: }

(code from trunk)

The bug is that the `while` loop beginning line 218 doesn't adjust `aCount` to account for the input it has processed. This means that the 2nd and succeeding iterations of the loop use the original value, thus emptying the stream of its entire contents, even if that exceeds `aCount` bytes.

It is unclear whether this bug can be manifested. `EncodeInputStream()` is called only by `Base64EncodeInputStream()` (same module), which is called only via three paths. The first two probably do not allow the bug to be manifested, but the third might:

   1. `ReadAsDataURL()` (FileReaderSync.cpp), which first converts the stream into an `nsStringInputStream` by calling `ConvertAsyncToSyncStream()`, so the stream is a fixed size, so the bug cannot be invoked on this path.

   2. `HTMLCanvasElement::ToDataURLImpl()`, which appears to create a fixed-size stream representing canvas element contents. The stream isa `imgIEncoder`, which isa `nsIAsyncInputStream`. However, all code paths initialize the stream using its `InitFromData()` method, which synchronously fills the stream. So, the bug cannot be invoked on this path.

   3. `nsScriptableBase64Encoder::EncodeTo[C]String()` (xpcom/io/nsScriptableBase64Encoder.cpp), which is called only via the interface returned when using `ModuleID::Anonymous404` (`CreateInstanceImpl()` in xpcom/components/StaticComponents.cpp). This interface appears to be available to privileged code by using the "@mozilla.org/scriptablebase64encoder;1" interface name (StaticComponents.cpp)
`EncodeInputStream()` (xpcom/io/base64.cpp) repeatedly reads from the specified stream and encodes into `aDest` until the stream is exhausted. However, the function uses the requested input data length `aCount` or the (then-current) stream length `count64` returned by `aInputStream->Available()` to calculate how much memory to allocate for the output buffer `aDest` (line 209, below). If `aInputStream` can return additional data beyond the specified input data length --  which is permissible for `nsIInputStream` objects -- `EncodeInputStream()` will write beyond bounds:

      186: template <typename T>
      187: nsresult EncodeInputStream(nsIInputStream* aInputStream, T& aDest,
      188:                            uint32_t aCount, uint32_t aOffset) {
      189:   nsresult rv;
      190:   uint64_t count64 = aCount;
      191: 
      192:   if (!aCount) {
      193:     rv = aInputStream->Available(&count64);
      194:     if (NS_WARN_IF(NS_FAILED(rv))) {
      195:       return rv;
      196:     }
      197:     // if count64 is over 4GB, it will be failed at the below condition,
      198:     // then will return NS_ERROR_OUT_OF_MEMORY
      199:     aCount = (uint32_t)count64;
      200:   }
      201: 
      202:   const auto base64LenOrErr = CalculateBase64EncodedLength(count64, aOffset);
      203:   if (base64LenOrErr.isErr()) {
      204:     // XXX For some reason, it was NS_ERROR_OUT_OF_MEMORY here instead of
      205:     // NS_ERROR_FAILURE, so we keep that.
      206:     return NS_ERROR_OUT_OF_MEMORY;
      207:   }
      208: 
      209:   if (!aDest.SetLength(base64LenOrErr.inspect(), mozilla::fallible)) {
      210:     return NS_ERROR_OUT_OF_MEMORY;
      211:   }
      212: 
      213:   EncodeInputStream_State<T> state;
      214:   state.charsOnStack = 0;
      215:   state.c[2] = '\0';
      216:   state.buffer = aOffset + aDest.BeginWriting();
      217: 
      218:   while (true) {
      219:     uint32_t read = 0;
      220: 
      221:     rv = aInputStream->ReadSegments(&EncodeInputStream_Encoder<T>,
      222:                                     (void*)&state, aCount, &read);
      223:     if (NS_FAILED(rv)) {
      224:       if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
      225:         MOZ_CRASH("Not implemented for async streams!");
      226:       }
      227:       if (rv == NS_ERROR_NOT_IMPLEMENTED) {
      228:         MOZ_CRASH("Requires a stream that implements ReadSegments!");
      229:       }
      230:       return rv;
      231:     }
      232: 
      233:     if (!read) {
      234:       break;
      235:     }
      236:   }
      ...
      249:   return NS_OK;
      250: 
      251: }

(code from trunk)

The bug is that the `while` loop beginning line 218 doesn't adjust `aCount` to account for the input it has processed. This means that the 2nd and succeeding iterations of the loop use the original value, thus emptying the stream of its entire contents, even if that exceeds `aCount` bytes.

It is unclear whether this bug can be manifested. `EncodeInputStream()` is called only by `Base64EncodeInputStream()` (same module), which is called only via three paths. The first two probably do not allow the bug to be manifested, but the third might:

   1. `ReadAsDataURL()` (FileReaderSync.cpp), which first converts the stream into an `nsStringInputStream` by calling `ConvertAsyncToSyncStream()`, so the stream is a fixed size, so the bug cannot be invoked on this path.

   2. `HTMLCanvasElement::ToDataURLImpl()`, which appears to create a fixed-size stream representing canvas element contents. The stream isa `imgIEncoder`, which isa `nsIAsyncInputStream`. However, all code paths initialize the stream using its `InitFromData()` method, which synchronously fills the stream. So, the bug cannot be invoked on this path.

   3. `nsScriptableBase64Encoder::EncodeTo[C]String()` (xpcom/io/nsScriptableBase64Encoder.cpp), which is called only via the interface returned when using `ModuleID::Anonymous404` (`CreateInstanceImpl()` in xpcom/components/StaticComponents.cpp). This interface appears to be available to privileged code by using the "@mozilla.org/scriptablebase64encoder;1" interface name (StaticComponents.cpp)

Back to Bug 1804564 Comment 0