ASN1 encoder asserts on CMMF templates

RESOLVED FIXED in 3.10

Status

NSS
Libraries
P2
normal
RESOLVED FIXED
14 years ago
13 years ago

People

(Reporter: Nelson Bolyard (seldom reads bugmail), Assigned: Nelson Bolyard (seldom reads bugmail))

Tracking

(Blocks: 1 bug)

Dependency tree / graph

Firefox Tracking Flags

(Not tracked)

Details

Attachments

(5 attachments)

When the ASN.1 encoder is called to encode a simple (?) CMMF message 
containing one or more certs, the encoder asserts.  Going past the first
assertion causes an essentially identical assertion to occur shortly 
thereafter. Turning off the assertions causes the encoder to output a 
message with invalid SEQUENCE lengths.  

The template in question is CMMFCertRepContentTemplate.
The function that attempts to encode with it is CMMF_EncodeCertRepContent.

Here are some of the relevant templates from file nss/lib/crmf/asn1cmn.c:

const SEC_ASN1Template CMMFSequenceOfCertsTemplate[] = {
    { SEC_ASN1_SEQUENCE_OF| SEC_ASN1_XTRN, 0,
                        SEC_ASN1_SUB(SEC_SignedCertificateTemplate)}
};

const SEC_ASN1Template CMMFCertRepContentTemplate[] = {
    { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CMMFCertRepContent)},
    { SEC_ASN1_CONSTRUCTED | SEC_ASN1_OPTIONAL | SEC_ASN1_CONTEXT_SPECIFIC | 1,
      offsetof(CMMFCertRepContent, caPubs), CMMFSequenceOfCertsTemplate },
    { SEC_ASN1_SEQUENCE_OF, offsetof(CMMFCertRepContent, response),
      CMMFCertResponseTemplate},
    { 0 }
};

The first assertion occurs in  function sec_asn1e_contents_length.  
It is trying to compute the length of the first component in the last 
SEQUENCE above, namely the constructed, optional, context-specific 1.
It has fetched the kind of the subtemplate, and is now asserting:

    /* Having any of these bits is not expected here...  */
    PORT_Assert ((underlying_kind & (SEC_ASN1_EXPLICIT | SEC_ASN1_OPTIONAL
                                     | SEC_ASN1_INLINE | SEC_ASN1_POINTER
                                     | SEC_ASN1_DYNAMIC | SEC_ASN1_MAY_STREAM
                                     | SEC_ASN1_SAVE | SEC_ASN1_SKIP)) == 0);

Later it asserts in function sec_asn1e_init_state_based_on_template,
in a very similar code path.

        /*
         * This is an implicit, non-universal (meaning, application-private
         * or context-specific) field.  This results in a "magic" tag but
         * encoding based on the underlying type.  We pushed a new state
         * that is based on the subtemplate (the underlying type), but
         * now we will sort of alias it to give it some of our properties
         * (tag, optional status, etc.).
         */

        under_kind = state->theTemplate->kind;
        if ((under_kind & SEC_ASN1_MAY_STREAM) && !ignore_stream) {
            may_stream = PR_TRUE;
        }
        under_kind &= ~SEC_ASN1_MAY_STREAM;
    } else {
        under_kind = encode_kind;
    }

    /*
     * Sanity check that there are no unwanted bits marked in under_kind.
     * These bits were either removed above (after we recorded them) or
     * they simply should not be found (signalling a bad/broken template).
     * XXX is this the right set of bits to test here? (i.e. need to add
     * or remove any?)
     */
    PORT_Assert ((under_kind & (SEC_ASN1_EXPLICIT | SEC_ASN1_OPTIONAL
                                | SEC_ASN1_SKIP | SEC_ASN1_INNER
                                | SEC_ASN1_DYNAMIC | SEC_ASN1_MAY_STREAM
                                | SEC_ASN1_INLINE | SEC_ASN1_POINTER)) == 0);


The encoder clearly doesn't expect that an implicit, non-pointer,
context-specific template will point to a subtemplate that has any of those
flags in them.  In this case, we have an implicit constructed context-specific
template pointing to a subtemplate that has the XTRN (a.k.a "DYNAMIC") flag.

Is that wrong?  invalid?  (and if so, why?)

Is some flag missing (such as EXPLICIT) that ought to be present in the outer
constructed context-specific template?

Does there need to be another level of indirection here?  

Taking a step back from the ASN.1 encoder issue itself, it seems to me that
this code is trying to go about this the wrong way.  Given that the cert(s)
to be sent out are already fully encoded, the templates should probably be
sending a sequence of ANY, rather than a sequence of
SEC_SignedCertificateTemplate.  

The CMMF code shouldn't be re-encoding the certs from their components 
(as it is now trying to do), but rather should be sending out the already 
encoded certs.  So, that will need to be fixed, and the fix may ultimately 
moot the question of why this particular set of templates doesn't work.  

But I want to know why this combination of templates fails, and whether 
these templates are invalid, or whether this is a bug in the encoder.  
If the encoder is buggy, I think it needs to be fixed, whether the CMMF
library is fixed or not.

We need a document that specifies what combinations of flags in a template
are not allowed, and what combinations of templates and subtemplates are 
not allowed, and WHY (e.g. would violate ASN.1, or just an implementation
limitation).  The existing document on our templates, at
http://www.mozilla.org/projects/security/pki/nss/tech-notes/tn1.html 
is very helpful, but stops short of documenting the disallowed and/or 
required combinations.

Julien, any light you can shed on this subject would be appreciated.

One more thought.  In XXXXXXXXXX there is a comment that asks:

        /* XXX Should we recurse here? */

I think the answer is yes.  Doing so eliminates the first assertion, but
not the second, and improves the lengths in the encoded output, but still
does not make them correct (they end up being about double the correct
values).
(Assignee)

Comment 1

14 years ago
above I reported that when assertions are disabled, the output has invalid
SEQUENCE lengths.  That is the only problem, AFAIK.  The rest of the output
appears normal, and dumpasn1 has no trouble decoding, but the byte counts
in the first few SEQUENCES are very large seemingly random numbers.  

The string XXXXXXXXXXX in the above comment should be "lib/util/secasn1e.c".
Status: NEW → ASSIGNED
Priority: -- → P2
Target Milestone: --- → 3.10
(Assignee)

Comment 2

14 years ago
I now have a LARGE patch to secasn1e.c that fixes both bug 244922 and bug 245429. 
However, it is difficult to review because it changes to many thing. 
So I have broken it up into a series of patches, each of which 
accomplishes a relatively small task (even if it touches many lines),
and thus is easy to review.  I shall attach those patches to this bug.
They all need to be reviewed (and checked in) in order.  
(Assignee)

Comment 3

14 years ago
Created attachment 150323 [details] [diff] [review]
patch part 1 - global rename some variables

rename "explicit" to "isExplicit" because MSVC6 thinks explicit is a keyword.
rename "ignore_stream" to "disallowStreaming" because it doesn't merely ignore.

rename "ignoresubstream" to "disallowStreaming" for the same reason.
(Assignee)

Comment 4

14 years ago
Created attachment 150324 [details] [diff] [review]
Patch part 2 - split big function into two

Function sec_asn1e_write_contents was two functions combined into one, 
separated by one huge if-then-else.  They do different things and take 
different arguments.  So, I split them into two separate functions:
sec_asn1e_write_contents and sec_asn1e_write_contents_from_buf 
the latter of which takes a buf pointer and length argument.  
The if statement that chooses between the two is now in the caller.  

It may be best to review this patch using bugzilla's side-by-size diff 
feature, especially if it can be persuaded to ignore whitespace diffs.
(Assignee)

Updated

14 years ago
Attachment #150324 - Attachment description: Patch part 2 - → Patch part 2 - split big function into two
(Assignee)

Comment 5

14 years ago
Created attachment 150325 [details] [diff] [review]
patch part 3 - misc fixes.

Fix some casts.  Wrap some long lines.	Remove a bogus assert.
Factor a function call out of the arguments of another function call,
which is mostly to make that code more easily debugged.
(Assignee)

Comment 6

14 years ago
Created attachment 150326 [details] [diff] [review]
Patch part 4 - handle dynamic subtemplates

This patch is really the fix for bug 245429.  It changes two places that 
handle implicit and explicit subtemplates.  The key points to this fix are:
a) recurse to handle implicit AND explicit subtemplates (formerly recursed only

   for explicit.
b) clear the DYNAMIC bit from the copy, since only GetSubtemplate needs it, 
   and that function always looks directly at the template.
c) After asserting that certain bits must be clear, clear them to ensure that 
   they will be clear when assertions are turned off. 
d) removes some old and now-pointless comments.

There is one typo in a comment in this patch.  Look for INNER< 
I fix it in the next patch.
(Assignee)

Comment 7

14 years ago
Created attachment 150327 [details] [diff] [review]
patch part 5 - handle streaming, separate no-header cases

This is the biggest of the patches.  It fixes bug 244922.  
The 4 preceeding patches are prerequisites.

This patch changes the arguments to static function sec_asn1e_contents_length.
It adds an argument that tells the function whether the caller will be 
definite or indefinite length encoding the result.  
If the caller is indefinite length encoding, then the only real purpose this
function serves is to determine if the contents are normal, or if they 
should be omitted for some reason, such as optional and not-present (zero 
length), or being a decoder-only template such as a SAVE, or if the header
should be ommitted because the contents is pre-encoded (an ANY).  

These different "header exception" conditions are now returned through an 
enumerated type.  Previously it was a boolean, which didn't provide enough
info to make proper choices.  

The code that forces a non-zero return length, when a zero would otherwise
be returned, is now used in ALL types encoded here, and not only in one 
default case.  However, it is used ONLY when the caller is indefinite 
length encoding, because in that case the actual length doesn't matter
except to know whether it is zero or not.  In the definite length encoding
cases, changing the length from zero to 1 always causes an error.  

all.sh passes with the above 5 patches in place.  crmftest (which is not
yet part of all.sh) gets farther than it did before.  OCSP testing remains
to be done.  I will work on that testing while these patches are being 
reviewed.
(Assignee)

Comment 8

14 years ago
Comment on attachment 150323 [details] [diff] [review]
patch part 1 - global rename some variables

Julien, please review all the bug comments above, and then please review this
patch.	Thanks.
Attachment #150323 - Flags: review?(julien.pierre.bugs)
(Assignee)

Comment 9

14 years ago
Comment on attachment 150324 [details] [diff] [review]
Patch part 2 - split big function into two

Julien, please review.	Thanks.
Attachment #150324 - Flags: review?(julien.pierre.bugs)
(Assignee)

Comment 10

14 years ago
Comment on attachment 150325 [details] [diff] [review]
patch part 3 - misc fixes.

Julien, please review.	I will wait on asking anyone for review of patch parts
4 and 5 until the first 3 small ones are reviewed.
Attachment #150325 - Flags: review?(julien.pierre.bugs)

Comment 11

14 years ago
Comment on attachment 150323 [details] [diff] [review]
patch part 1 - global rename some variables

r=wtc.	There is one problem in this patch that
should be fixed.  The global find-and-replace in
this file changed an "explicit" in the comment
below to "isExplicit", which I think is wrong.

>-    PRBool explicit,		/* we are handling an explicit header */
>+    PRBool isExplicit,		/* we are handling an isExplicit header */
Attachment #150323 - Flags: superreview+

Comment 12

14 years ago
Comment on attachment 150323 [details] [diff] [review]
patch part 1 - global rename some variables

One stylistic nit: it would look nicer to use
is_explicit and disallow_streaming instead to
follow the prevalent identifier naming convention
in this file.  (See the is_string and may_stream
members of the same structure.)
(Assignee)

Comment 13

14 years ago
Comment on attachment 150323 [details] [diff] [review]
patch part 1 - global rename some variables

Wan-Teh, thanks for the review.  I will consider adding 
another patch below (patch part 7) to address any and all
stylistic changes suggested, after parts 1-6 are reviewed.
Attachment #150323 - Flags: review?(julien.pierre.bugs)

Comment 14

14 years ago
Nelson, I'm having trouble applying your patches :
dos2unix < /home/jp96085/p/245429a.txt | patch
Hmm...  Looks like a unified diff to me...
The text leading up to this was:
--------------------------
|--- secasn1e.c 2004/06/09 04:35:34     1.9
|+++ secasn1e.c 2004/06/09 04:38:49     1.10
--------------------------
Patching file secasn1e.c using Plan A...
Hunk #1 failed at 38.
Hunk #2 succeeded at 82.
Hunk #3 succeeded at 188.
Hunk #4 succeeded at 200.
Hunk #5 succeeded at 221.
Hunk #6 succeeded at 298.
Hunk #7 succeeded at 368.
Hunk #8 succeeded at 483.
Hunk #9 succeeded at 497.
Hunk #10 succeeded at 509.
Hunk #11 succeeded at 570.
Hunk #12 succeeded at 719.
Hunk #13 succeeded at 780.
Hunk #14 succeeded at 833.
1 out of 14 hunks failed--saving rejects to secasn1e.c.rej
done

[jp96085@variation]/home/jp96085/nss/tip/mozilla/security/nss/lib/util 101 %
dos2unix < /home/jp96085/p/245429b.txt | patch
Hmm...  Looks like a unified diff to me...
The text leading up to this was:
--------------------------
|--- secasn1e.c 2004/06/09 04:38:49     1.10
|+++ secasn1e.c 2004/06/09 04:41:02     1.11
--------------------------
Patching file secasn1e.c using Plan A...
Hunk #1 failed at 38.
Hunk #2 succeeded at 901.
Hunk #3 succeeded at 1215.
1 out of 3 hunks failed--saving rejects to secasn1e.c.rej
done

Could you regenerate patches that apply to the tip ? Or tell me what branch to
apply them to ? I wanted to look at the files side by side in my favorite
editor, Visual slick edit, which has a better diff function than cvs. In
particular I wanted to verify there is no actual runtime change in your patch 2.
(Assignee)

Comment 15

14 years ago
Julien, I believe you'll find that the only "failed" hunks of patch are
hunks that change only the line that contains the RCS ID.  If that is so,
then your attempts to patch your files have all succeeded.    
These patches should patch OK against the trunk, except for the RCS IDs,
which aren't important.
(Assignee)

Comment 16

14 years ago
Comment on attachment 150324 [details] [diff] [review]
Patch part 2 - split big function into two

Wan-Teh, please review
Attachment #150324 - Flags: review?(julien.pierre.bugs) → review?(wchang0222)
(Assignee)

Comment 17

14 years ago
Comment on attachment 150325 [details] [diff] [review]
patch part 3 - misc fixes.

Wan-Teh, please review
Attachment #150325 - Flags: review?(julien.pierre.bugs) → review?(wchang0222)

Comment 18

14 years ago
Comment on attachment 150325 [details] [diff] [review]
patch part 3 - misc fixes.

>@@ -1204,7 +1206,6 @@
>     sec_asn1e_state *state;
> 
>     if (cx->status == needBytes) {
>-	PORT_Assert (buf != NULL && len != 0);
> 	cx->status = keepGoing;
>     }

Why is this assertion bogus?

Comment 19

14 years ago
Comment on attachment 150324 [details] [diff] [review]
Patch part 2 - split big function into two

> static void
>-sec_asn1e_write_contents (sec_asn1e_state *state,
>+sec_asn1e_write_contents_from_buf (sec_asn1e_state *state,
> 			  const char *buf, unsigned long len)
> {
>     PORT_Assert (state->place == duringContents);
>+    PORT_Assert (state->top->from_buf);
>+    PORT_Assert (state->may_stream && !state->disallowStreaming);

Can you explain the second new assertion?

Another question: are the following two always true?

    state->top == cx
    cx-> current == state

>+static void
>+sec_asn1e_write_contents (sec_asn1e_state *state)
>+{
>+    unsigned long len = 0;

It is not necessary to initialize 'len' to 0, right?  (I don't
object to that.  I just wanted to check my understanding.)
(Assignee)

Comment 20

14 years ago
In answer to comment 18, the code handles this case without problem.
Therefore, there is no need to assert that this case is not present.

In answer to comment 19, 
a) the second assertion is to detect a programming error, where the caller
of EncoderUpdate is attempting to encode data from a buffer but the current
state is not one for which that is an allowed behavior.  

I have considered writing a complete how-to guide for the encoder that 
would explain how it was (apparently) intended to be used.  Perhaps that
document would make it clearer.  Would you like me to write that document?

b) there is a linked list of states, which is treated like a stack.
Each state in the list points to the encoder context.  The encoder 
context points to one end of the list.

c) I think it is not necessary to initialize len.  This is defensive programming.

Comment 21

14 years ago
Comment on attachment 150325 [details] [diff] [review]
patch part 3 - misc fixes.

r=wtc.	I don't know why the original casts were
wrong (no compiler warnings on Windows and Linux),
but the new casts are fine.
Attachment #150325 - Flags: review?(wchang0222) → review+

Comment 22

14 years ago
Comment on attachment 150324 [details] [diff] [review]
Patch part 2 - split big function into two

r=wtc.
Attachment #150324 - Flags: review?(wchang0222) → review+

Comment 23

14 years ago
Nelson,

I think an encoder document would be useful.

Are the part 4 and part 5 patches ready to be reviewed ?
(Assignee)

Comment 24

14 years ago
Comment on attachment 150326 [details] [diff] [review]
Patch part 4 - handle dynamic subtemplates

Julien and/or Wan-Teh, please review.

Here are additional comments, beyond those in comment 6 above:

There are two large functions in this file that strongly parallel each other
in structure and function.  They are sec_asn1e_contents_length and 
sec_asn1e_init_state_based_on_template.  Neither of them was coded to 
correctly handle a subtemplate that contained any of the following SEC_ASN1_
flags: EXPLICIT, OPTIONAL, INNER, INLINE, POINTER, DYNAMIC, SAVE.  
They both needed to be fixed.  

The fixes for the two are quite different.  For sec_asn1e_contents_length
the fix is tiny and elegant (IMO).  After selecting the subtemplate, it
simply recurses.  This correctly handles ALL the flags, because the 
length computation doesn't need to be modified by the parent template's 
state.

But for sec_asn1e_init_state_based_on_template, a similar approach of 
recursion causes the wrong encoding to be output.  This is because the 
child's encoding DOES need to be altered by the state of the parent 
template.  So, the patch attempts to combine the state and go on.  
I am not sure that this combination of state/flags is correct in all 
resonable cases.  I only know that it produces the correct results for
the test cases at hand, which come from the crmf/cmmf libraries.  
I believe the result is more correct than before in all case, but there
may exist cases (for which I do not have examples) in which they do not
produce correct results.   I felt it worthwhile to checkin this fix now,
and perhaps extend it when more test cases are available.  I invite your
comment on this approach.

There are some obvious typos in some of the comments.  I believe they 
are all fixed in the next patch (part 5).  Please don't r- this patch just
for those, but make a note and I will fix any before checkin.
Attachment #150326 - Flags: superreview?(wchang0222)
Attachment #150326 - Flags: review?(julien.pierre.bugs)
(Assignee)

Updated

14 years ago
Blocks: 245941
(Assignee)

Updated

14 years ago
Whiteboard: patch part 4 awaiting review
(Assignee)

Comment 25

14 years ago
Comment on attachment 150326 [details] [diff] [review]
Patch part 4 - handle dynamic subtemplates

Bob, please read all the above comments in this bug, and then review this
patch.
Attachment #150326 - Flags: review?(julien.pierre.bugs) → review?(rrelyea0264)

Comment 26

14 years ago
Comment on attachment 150326 [details] [diff] [review]
Patch part 4 - handle dynamic subtemplates

r=relyea

The tweaks to sec_asn1e_init_state_based_ontemplate are subtle. It's hard to
see if they are beneficial, detrimental, or neutral without a full review of
the function itself.

The rest of the changes are clearly correct.
Attachment #150326 - Flags: review?(rrelyea0264) → review+
(Assignee)

Updated

14 years ago
Whiteboard: patch part 4 awaiting review → patch part 4 awaiting super-review
(Assignee)

Comment 27

14 years ago
checked in patch 1
/cvsroot/mozilla/security/nss/lib/util/secasn1e.c,v  <--  secasn1e.c
new revision: 1.15; previous revision: 1.14

checked in patch 2
new revision: 1.16; previous revision: 1.15

checked in patch 3
new revision: 1.17; previous revision: 1.16
(Assignee)

Comment 28

14 years ago
Patch 4 checked in, after Bob's review.
/cvsroot/mozilla/security/nss/lib/util/secasn1e.c,v  <--  secasn1e.c
new revision: 1.18; previous revision: 1.17
(Assignee)

Comment 29

14 years ago
I am marking this bug resolved/FIXED.
The one remaining patch, patch part 5, is really a fix for bug 244922,
which presently remains open.
Status: ASSIGNED → RESOLVED
Last Resolved: 14 years ago
Resolution: --- → FIXED
Whiteboard: patch part 4 awaiting super-review
(Assignee)

Comment 30

14 years ago
Comment on attachment 150326 [details] [diff] [review]
Patch part 4 - handle dynamic subtemplates

This patch (#4) was checked in after Bob's review.
Attachment #150326 - Flags: superreview?(wchang0222)
(Assignee)

Comment 31

13 years ago
Comment on attachment 150327 [details] [diff] [review]
patch part 5 - handle streaming, separate no-header cases

Julien, 

This is the bug I mentioned that tries to deal with the ambiguity of NULL 
pointers in optional templates while streaming.  Note that there have been 
several past attempts to address this problem, one of which created the 
SEC_ASN1_NO_STREAM flag.  

There were 5 patches for this bug.  The first 4 have been checked in.  
The fifth one requires real ASN.1 decoder expertise.  Please review this.
Attachment #150327 - Flags: review?(julien.pierre.bugs)

Comment 32

13 years ago
Nelson,

I'll try to review the 5th patch. But I have no expertise with the streaming
encoder case. I think we'll need some test cases in our QA test suite to make
sure things are correct here.
OS: Windows 2000 → All
Hardware: PC → All
(Assignee)

Comment 33

13 years ago
As noted in comment 7 above, patch 5 is really a fix for bug 244922 .  
Please read comment 7 and bug 244922 for the best descriptions.  

Comment 34

13 years ago
Tested patch #5 on linux and solaris x86. Crl generation tests pass now.
Noticed that patch didn't apply properly to the current tip. Additional
fixes are necessary in order to compile the tree.

Comment 35

13 years ago
Patch #5 is still not checked in, yet this bug is closed . Either the bug needs
to be reopened, or patch #5 should be attached to a different bug .
(Assignee)

Comment 36

13 years ago
(In reply to comment #35)
> Patch #5 is still not checked in, yet this bug is closed . Either the bug needs
> to be reopened, or patch #5 should be attached to a different bug .

Please see comment 33 above

Updated

13 years ago
Attachment #150327 - Flags: review?(julien.pierre.bugs)
You need to log in before you can comment on or make changes to this bug.