Closed Bug 205524 Opened 21 years ago Closed 21 years ago

Reading encrypted mail sent by N7.01 causes infinite memory leak

Categories

(NSS :: Libraries, defect, P1)

defect

Tracking

(Not tracked)

RESOLVED FIXED

People

(Reporter: rrelyea, Assigned: rrelyea)

Details

(Whiteboard: [3.7.5])

Attachments

(3 files, 1 obsolete file)

When reading large encrypted mail sent by N7.01 or Com 4.7 as one Chunk (instead
of piecemeal like N7), NSS uses all available memory to decrypt the message.
From ddrinan.
AOL Communicator decodes the mail contents by making *one* call to
NSS_CMSDecoder_Update(). When decoding large messages from N7 (> IMB), this call
takes a long time and exhausts virtual memory. What I see are lots (i.e.
hundreds, maybe thousands) of calls to the followind code in 
nss_cms_decoder_work_data()in nss/lib/smime/cmsdecode.c:
	if (storage->len == 0) {
	    dest = (unsigned char *)PORT_ArenaAlloc(p7dcx->cmsg->poolp, len);
	} else {
	    dest = (unsigned char *)PORT_ArenaGrow(p7dcx->cmsg->poolp, 
				  storage->data,
				  storage->len,
				  storage->len + len);
	}
An interesting fact is that a large message encrypted by an application which
makes a single call (like AOL Communicator) does not
generate as many calls to this function (I estimate somewhere less than 10 calls).

So the questions are: why does N7 generate so many calls to
nss_cms_decoder_work_data(), why does this call exhaust VM in AOL Communicator
and not on N7.
Individual data in arena's cannot be freed unless the whole arena is freed.
One thing that might be going on here is that the arena in question here is a
temporary arena.

In each call that N7 makes the arena is created used and freed, so it never
really grows. Photon making a single call may be defeating that. One easy
workaround may be simply to break up the photon calls when dealing with S/MIME
messages (call it in a loop with block sizes of 32K each rather than the whole
thing). I would suggest this might be a good solution for M5, then write a
separate bug against NSS for the real issue).

bob
from ddrinan:
I implemented Bob's proposed solution of breaking the message up into 32K
chunks. Unfortunately the problem still occurs with the exact same symptoms i.e.
mail exhausts VM.
This patch is now checked into the NSS tip, however Photon is using NSS 3.7.x
and NSS is on NSS 3.9. Since this patch is deep in the CMS code, I suggest not
picking it up for N6 and 'baking' it internally for 'N7' since bugs in the
patch
may cause worse effects than the bug we are trying to fix.
Here are the size analysis for the two different scenarios...


Original Code:

Total Memory of Message: M
Size of Memory Increment: I
Number of allocation calls: n = M/I
Total Memory Allocated: T=M+ 1/2*M*(M-I)/I
   for I << M, this is approximately:
       T=M*(1+n/2)
   for I = M this is:
       T=M
----------------------------------------------
New Code:

Total Memory of Message: M
Size of Memory Increment: I
Number of alocation steps: n= Log2(M/I+1)
   for I << M This is approximently:
	n=Log2(M/I)
   for I = M this is:
	n=Log2(2) = 1
Total Memory Allocated:4*M-2*I*Log2(M/I+1)
   for I << M This is approximately:
	T=4*M
   for I = M this is:
      T=2*M
-----------------------------------------------

Examples: 

A.Read a Message sent with Netscape 7
which is 2 Meg in size, and uses 1k buffers
M=2M
I=1K

Original Code:
n=2048
T=2M*(1+1024)=2G

New Code:
n=11
T=8M 

B. Read a Message sent with Photon
which is 2Meg is size:
M=2M
I=2M

Original Code:
n=1
T=2M

New Code:
n=1
T=4M

BTW I think the N7 usually spoon feeds the decoder on the order of 16 bytes at a
time or so, for David's 32K buffers, NSS was still using 32Meg of memory! for
each block.

bob
Setting Priority & target Mileston (P1, 3.8.1)
Priority: -- → P1
Target Milestone: --- → 3.8.1
Comment on attachment 123148 [details] [diff] [review]
replace arena grow with code that grows memory nLog n, not n!

This patch is correct, but I'd like to suggest a change
to make it easier to understand.

The NSSCMSDecoderData structure is defined as two SECItem's,

  struct NSSCMSDecoderDataStr {
      SECItem data;	/* must be first */
      SECItem storage;
  };

But it really just needs to be a SECItem plus an unsigned
int that represents the actual buffer length:

  struct NSSCMSDecoderDataStr {
      SECItem data;	/* must be first */
      unsigned int buffer_len;
  };

Another suggested change is to make NSSCMSDecoderData a
new member of the NSSCMSContentUnion, instead of using the
"void * pointer" member of that union.	This would require
modifying the cmst.h header file.
This is an incremental patch on top of patch 123148 .
It includes the first part of Wan-Teh's suggestion. I did not implement the
second part because I want to keep the data structure localized to this .c
file, and not accessible from other .c files in this directory.

bob
Comment on attachment 123322 [details] [diff] [review]
Simplify memory patch by using only a buffer size count rather than a full secitem

Bob, could you declare totalBufferSize as an *unsigned* int
and generate a new patch using cvs diff -u?  Thanks.
Attachment #123322 - Attachment is obsolete: true
Now that your latest patch no longer uses two SECItems, another simplifying 
measure would be to use PORT_ArenaGrow to grow it, rather than effectively
duplicating PORT_ArenaGrow in this patch.
I actually purposefully did NOT use the grow code because I found it confusing.
The name implied that the memory would incrementally increase, which it did not,
leading to the original bug. You can see what is happenning with the current
code much more clearly.

bob
Comment on attachment 123337 [details] [diff] [review]
Patch 123322 correct as wtc suggested.

r=wtc.	Do you want to implement Nelson's suggestion?
Attachment #123337 - Flags: review+
Attachment #123148 - Flags: review+
The grow code doesn't allocate a new block if it is able to actually make the
previous allocation larger.  If the previously-allocated block that you're
trying to grow is the last one allocated in an arena, and there is adequate room
after it in the arena to grow it to the desired size, then the grow function
will enlarge the old allocation without allocating a whole new block and copying
all the data.
That seems desirable, even if it is slightly less straighforward.  
This is an incremental patch that should be applied on top
of the previous two patches.

1. Declare "needLen" as "unsigned int" instead of "int".
2. Use PORT_ArenaGrow except for the initial allocation.

Please review and test this patch.

Do you think we should make a similar change to the
PORT_ArenaGrow calls in cmsencode.c and cmsarray.c to
reduce memory usage?
I still don't believe the Grow is the right way to go. It confuses any readers
or reviewers into thinking the previous memory is freed. We know in this case it
does not have any benefit (or we wouldn't run into this problem).

I don't find the new code any easier or less complicated the the original patch.

I also think in the longer term, we should be looking at nelson's suggestion of
using a linked list or using the decoder's supplied memory.

bob
Comment on attachment 123337 [details] [diff] [review]
Patch 123322 correct as wtc suggested.

Requesting mozilla 1.4 approval.  This patch is low risk.  It
reduces the memory usage when decrypting encrypted mail from
O(n^2) to O(n).  It only affects users who read encrypted mail.
Attachment #123337 - Flags: approval1.4?
Bob, if using PORT_ArenaGrow confuses readers, then should we eliminate that 
function?  

I don't think anyone is confused by it.
I find the code that calls PORT_ArenaGrow less confusing than the alternative.

It is a function with a clear purpose.  It *DOES* have benefit in the case
where the the block to be grown is less than the arena's size and the new 
size after growing is also less than or equal to that arena's size.  
It doesn't free the previosuly used space in that case, it *reuses* it.

This is admittedly few cases (the doubling algorithm will quickly exceed the
standard arena size, causing all newly allocated blocks to be in their own 
arenas with no extra space in the arena), but it's more than zero cases.

The alternative code will never reuse the previously used space, even when
it could be reused.
Comment on attachment 123337 [details] [diff] [review]
Patch 123322 correct as wtc suggested.

a=sspitzer
Attachment #123337 - Flags: approval1.4? → approval1.4+
Fix (attachment 123148 [details] [diff] [review] and attachment 123337 [details] [diff] [review]) checked into
NSS_3_7_BRANCH (3.7.5), NSS_3_8_BRANCH (3.8.1), NSS_CLIENT_TAG
(mozilla 1.4 final), and the tip (3.9).
Whiteboard: [3.7.5]
Marked the bug fixed.
Status: NEW → RESOLVED
Closed: 21 years ago
Resolution: --- → FIXED
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Creator:
Created:
Updated:
Size: