@@ -53,8 +53,33 @@ CHIP_ERROR ASN1Reader::Next()
5353 VerifyOrReturnError (!EndOfContents, ASN1_END);
5454 VerifyOrReturnError (!IndefiniteLen, ASN1_ERROR_UNSUPPORTED_ENCODING);
5555
56- // Note: avoid using addition assignment operator (+=), which may result in integer overflow
57- // in the right hand side of an assignment (mHeadLen + ValueLen).
56+ // Defense-in-depth against integer overflow: mHeadLen + ValueLen are both uint32_t and
57+ // their sum could in principle wrap around, advancing mElemStart to an unintended
58+ // location. DecodeHead already caps ValueLen at the remaining buffer, so this cannot
59+ // wrap from any input reaching Next() through the public API; the guard hardens against
60+ // a future caller or a regression in those upstream bounds checks.
61+ VerifyOrReturnError (mHeadLen <= UINT32_MAX - ValueLen, ASN1_ERROR_LENGTH_OVERFLOW);
62+ // Defend against an inconsistent reader state where mElemStart has advanced past
63+ // mContainerEnd (e.g. a prior failed unwind left stale pointers). Without this
64+ // check the pointer subtraction below would underflow when cast to size_t and the
65+ // bounds comparison would silently pass.
66+ VerifyOrReturnError (mContainerEnd >= mElemStart , ASN1_ERROR_INVALID_STATE);
67+ // Compare in integer space rather than pointer space. On 32-bit targets size_t == uint32_t,
68+ // so the size_t cast provides no additional width; the preceding LENGTH_OVERFLOW guard is
69+ // what prevents wrap there. The size_t widening eliminates pointer-arithmetic UB on 64-bit
70+ // targets where computing `mElemStart + mHeadLen + ValueLen` could otherwise wrap past the
71+ // end of the address space before the comparison runs.
72+ //
73+ // BEHAVIOR CHANGE: a bounds-exceeded result here means a child element claims more bytes
74+ // than its parent container holds (malformed encoding). Previously this advanced mElemStart
75+ // past mContainerEnd and the subsequent `mElemStart != mContainerEnd` check returned ASN1_END,
76+ // which callers (and the ASN1_EXIT_* macros) treat as a clean end of container -- silently
77+ // swallowing the malformed input. It now surfaces as ASN1_ERROR_INVALID_ENCODING. A child
78+ // ending exactly at mContainerEnd still passes this `<=` check and reports ASN1_END as before,
79+ // so well-formed certificates are unaffected.
80+ VerifyOrReturnError (static_cast <size_t >(mHeadLen ) + ValueLen <= static_cast <size_t >(mContainerEnd - mElemStart ),
81+ ASN1_ERROR_INVALID_ENCODING);
82+
5883 mElemStart = mElemStart + mHeadLen + ValueLen;
5984
6085 ResetElementState ();
@@ -80,6 +105,14 @@ CHIP_ERROR ASN1Reader::GetConstructedType(const uint8_t *& val, uint32_t & valLe
80105{
81106 VerifyOrReturnError (Constructed, ASN1_ERROR_INVALID_STATE);
82107
108+ // Defend against an inconsistent reader state where mElemStart has advanced past
109+ // mContainerEnd. Required ahead of any size_t-widened subtraction so the result
110+ // cannot underflow.
111+ VerifyOrReturnError (mContainerEnd >= mElemStart , ASN1_ERROR_INVALID_STATE);
112+ // Guard against integer overflow: mHeadLen + ValueLen are both uint32_t and their sum
113+ // could wrap around, producing a bogus length for the caller.
114+ VerifyOrReturnError (mHeadLen <= UINT32_MAX - ValueLen, ASN1_ERROR_LENGTH_OVERFLOW);
115+
83116 val = mElemStart ;
84117 valLen = mHeadLen + ValueLen;
85118
@@ -105,6 +138,17 @@ CHIP_ERROR ASN1Reader::EnterContainer(uint32_t offset)
105138{
106139 VerifyOrReturnError (mNumSavedContexts != kMaxContextDepth , ASN1_ERROR_MAX_DEPTH_EXCEEDED);
107140
141+ // Peek-then-commit: validate all preconditions BEFORE mutating reader
142+ // state. If any guard fires the saved-context stack, mElemStart, and
143+ // mContainerEnd are all left untouched so the reader stays in a
144+ // consistent state.
145+ if (!IndefiniteLen)
146+ {
147+ VerifyOrReturnError (CanCastTo<uint32_t >(mBufEnd - Value), ASN1_ERROR_VALUE_OVERFLOW);
148+ VerifyOrReturnError (static_cast <uint32_t >(mBufEnd - Value) >= ValueLen, ASN1_ERROR_VALUE_OVERFLOW);
149+ }
150+
151+ // All checks passed - now commit by pushing the saved context and updating reader state.
108152 mSavedContexts [mNumSavedContexts ].ElemStart = mElemStart ;
109153 mSavedContexts [mNumSavedContexts ].HeadLen = mHeadLen ;
110154 mSavedContexts [mNumSavedContexts ].ValueLen = ValueLen;
@@ -115,8 +159,6 @@ CHIP_ERROR ASN1Reader::EnterContainer(uint32_t offset)
115159 mElemStart = Value + offset;
116160 if (!IndefiniteLen)
117161 {
118- VerifyOrReturnError (CanCastTo<uint32_t >(mBufEnd - Value), ASN1_ERROR_VALUE_OVERFLOW);
119- VerifyOrReturnError (static_cast <uint32_t >(mBufEnd - Value) >= ValueLen, ASN1_ERROR_VALUE_OVERFLOW);
120162 mContainerEnd = Value + ValueLen;
121163 }
122164
@@ -129,10 +171,33 @@ CHIP_ERROR ASN1Reader::ExitContainer()
129171{
130172 VerifyOrReturnError (mNumSavedContexts != 0 , ASN1_ERROR_INVALID_STATE);
131173
132- ASN1ParseContext & prevContext = mSavedContexts [--mNumSavedContexts ];
174+ // Peek-then-commit: validate the saved context BEFORE mutating any reader
175+ // state. On any error the saved-context stack is left untouched so the
176+ // reader stays in a consistent state and a follow-up call sees the same
177+ // failure (rather than silently operating on a half-popped stack).
178+ const ASN1ParseContext & prevContext = mSavedContexts [mNumSavedContexts - 1 ];
133179
134180 VerifyOrReturnError (!prevContext.IndefiniteLen , ASN1_ERROR_UNSUPPORTED_ENCODING);
135181
182+ // Guard against integer overflow: HeadLen + ValueLen are both uint32_t and their sum
183+ // could wrap around, potentially advancing mElemStart to an unintended location.
184+ VerifyOrReturnError (prevContext.HeadLen <= UINT32_MAX - prevContext.ValueLen , ASN1_ERROR_LENGTH_OVERFLOW);
185+ // Defend against a saved context with ContainerEnd < ElemStart so the pointer
186+ // subtraction below cannot underflow when widened to size_t.
187+ VerifyOrReturnError (prevContext.ContainerEnd >= prevContext.ElemStart , ASN1_ERROR_INVALID_STATE);
188+ // Compare in integer space rather than pointer space. On 32-bit targets size_t == uint32_t
189+ // and the LENGTH_OVERFLOW guard above is what prevents wrap; the size_t widening eliminates
190+ // pointer-arithmetic UB on 64-bit targets where `prevContext.ElemStart + prevContext.HeadLen
191+ // + prevContext.ValueLen` could otherwise wrap past the end of the address space. A
192+ // bounds-exceeded result here means the saved container declared more bytes than its parent
193+ // holds (malformed encoding), NOT a clean end of container, so it must surface as
194+ // INVALID_ENCODING rather than ASN1_END.
195+ VerifyOrReturnError (static_cast <size_t >(prevContext.HeadLen ) + prevContext.ValueLen <=
196+ static_cast <size_t >(prevContext.ContainerEnd - prevContext.ElemStart ),
197+ ASN1_ERROR_INVALID_ENCODING);
198+
199+ // All checks passed - now commit by popping the saved context and updating reader state.
200+ --mNumSavedContexts ;
136201 mElemStart = prevContext.ElemStart + prevContext.HeadLen + prevContext.ValueLen ;
137202
138203 mContainerEnd = prevContext.ContainerEnd ;
@@ -155,7 +220,15 @@ CHIP_ERROR ASN1Reader::GetInteger(int64_t & val)
155220 VerifyOrReturnError (Value != nullptr , ASN1_ERROR_INVALID_STATE);
156221 VerifyOrReturnError (ValueLen >= 1 , ASN1_ERROR_INVALID_ENCODING);
157222 VerifyOrReturnError (ValueLen <= sizeof (int64_t ), ASN1_ERROR_VALUE_OVERFLOW);
158- VerifyOrReturnError (mElemStart + mHeadLen + ValueLen <= mContainerEnd , ASN1_ERROR_UNDERRUN);
223+ // Mirrors the guard sequence in Next(): (1) reject inconsistent state where
224+ // mContainerEnd < mElemStart (without this the size_t-widened subtraction below would
225+ // underflow to a huge value and the bounds compare would trivially pass), (2) reject
226+ // mHeadLen + ValueLen wrap on 32-bit targets where size_t == uint32_t, (3) bounds-check
227+ // via the size_t-widened comparison.
228+ VerifyOrReturnError (mContainerEnd >= mElemStart , ASN1_ERROR_INVALID_STATE);
229+ VerifyOrReturnError (mHeadLen <= UINT32_MAX - ValueLen, ASN1_ERROR_LENGTH_OVERFLOW);
230+ VerifyOrReturnError (static_cast <size_t >(mHeadLen ) + ValueLen <= static_cast <size_t >(mContainerEnd - mElemStart ),
231+ ASN1_ERROR_UNDERRUN);
159232
160233 if ((*Value & 0x80 ) == 0x80 )
161234 {
@@ -175,7 +248,13 @@ CHIP_ERROR ASN1Reader::GetBoolean(bool & val)
175248{
176249 VerifyOrReturnError (Value != nullptr , ASN1_ERROR_INVALID_STATE);
177250 VerifyOrReturnError (ValueLen == 1 , ASN1_ERROR_INVALID_ENCODING);
178- VerifyOrReturnError (mElemStart + mHeadLen + ValueLen <= mContainerEnd , ASN1_ERROR_UNDERRUN);
251+ // Mirrors the guard sequence in Next(): (1) reject inconsistent state where
252+ // mContainerEnd < mElemStart, (2) reject mHeadLen + ValueLen wrap on 32-bit
253+ // (size_t == uint32_t), (3) bounds-check via size_t-widened comparison.
254+ VerifyOrReturnError (mContainerEnd >= mElemStart , ASN1_ERROR_INVALID_STATE);
255+ VerifyOrReturnError (mHeadLen <= UINT32_MAX - ValueLen, ASN1_ERROR_LENGTH_OVERFLOW);
256+ VerifyOrReturnError (static_cast <size_t >(mHeadLen ) + ValueLen <= static_cast <size_t >(mContainerEnd - mElemStart ),
257+ ASN1_ERROR_UNDERRUN);
179258 VerifyOrReturnError (Value[0 ] == 0 || Value[0 ] == 0xFF , ASN1_ERROR_INVALID_ENCODING);
180259
181260 val = (Value[0 ] != 0 );
@@ -188,7 +267,13 @@ CHIP_ERROR ASN1Reader::GetUTCTime(ASN1UniversalTime & outTime)
188267 // Supported Encoding: YYMMDDHHMMSSZ
189268 VerifyOrReturnError (Value != nullptr , ASN1_ERROR_INVALID_STATE);
190269 VerifyOrReturnError (ValueLen >= 1 , ASN1_ERROR_INVALID_ENCODING);
191- VerifyOrReturnError (mElemStart + mHeadLen + ValueLen <= mContainerEnd , ASN1_ERROR_UNDERRUN);
270+ // Mirrors the guard sequence in Next(): (1) reject inconsistent state where
271+ // mContainerEnd < mElemStart, (2) reject mHeadLen + ValueLen wrap on 32-bit
272+ // (size_t == uint32_t), (3) bounds-check via size_t-widened comparison.
273+ VerifyOrReturnError (mContainerEnd >= mElemStart , ASN1_ERROR_INVALID_STATE);
274+ VerifyOrReturnError (mHeadLen <= UINT32_MAX - ValueLen, ASN1_ERROR_LENGTH_OVERFLOW);
275+ VerifyOrReturnError (static_cast <size_t >(mHeadLen ) + ValueLen <= static_cast <size_t >(mContainerEnd - mElemStart ),
276+ ASN1_ERROR_UNDERRUN);
192277 VerifyOrReturnError (ValueLen == 13 && Value[12 ] == ' Z' , ASN1_ERROR_UNSUPPORTED_ENCODING);
193278
194279 return outTime.ImportFrom_ASN1_TIME_string (CharSpan (reinterpret_cast <const char *>(Value), ValueLen));
@@ -199,7 +284,13 @@ CHIP_ERROR ASN1Reader::GetGeneralizedTime(ASN1UniversalTime & outTime)
199284 // Supported Encoding: YYYYMMDDHHMMSSZ
200285 VerifyOrReturnError (Value != nullptr , ASN1_ERROR_INVALID_STATE);
201286 VerifyOrReturnError (ValueLen >= 1 , ASN1_ERROR_INVALID_ENCODING);
202- VerifyOrReturnError (mElemStart + mHeadLen + ValueLen <= mContainerEnd , ASN1_ERROR_UNDERRUN);
287+ // Mirrors the guard sequence in Next(): (1) reject inconsistent state where
288+ // mContainerEnd < mElemStart, (2) reject mHeadLen + ValueLen wrap on 32-bit
289+ // (size_t == uint32_t), (3) bounds-check via size_t-widened comparison.
290+ VerifyOrReturnError (mContainerEnd >= mElemStart , ASN1_ERROR_INVALID_STATE);
291+ VerifyOrReturnError (mHeadLen <= UINT32_MAX - ValueLen, ASN1_ERROR_LENGTH_OVERFLOW);
292+ VerifyOrReturnError (static_cast <size_t >(mHeadLen ) + ValueLen <= static_cast <size_t >(mContainerEnd - mElemStart ),
293+ ASN1_ERROR_UNDERRUN);
203294 VerifyOrReturnError (ValueLen == 15 && Value[14 ] == ' Z' , ASN1_ERROR_UNSUPPORTED_ENCODING);
204295
205296 return outTime.ImportFrom_ASN1_TIME_string (CharSpan (reinterpret_cast <const char *>(Value), ValueLen));
@@ -222,19 +313,28 @@ CHIP_ERROR ASN1Reader::GetBitString(uint32_t & outVal)
222313 VerifyOrReturnError (Value != nullptr , ASN1_ERROR_INVALID_STATE);
223314 VerifyOrReturnError (ValueLen >= 1 , ASN1_ERROR_INVALID_ENCODING);
224315 VerifyOrReturnError (ValueLen <= 5 , ASN1_ERROR_UNSUPPORTED_ENCODING);
225- VerifyOrReturnError (mElemStart + mHeadLen + ValueLen <= mContainerEnd , ASN1_ERROR_UNDERRUN);
316+ // Mirrors the guard sequence in Next(): (1) reject inconsistent state where
317+ // mContainerEnd < mElemStart, (2) reject mHeadLen + ValueLen wrap on 32-bit
318+ // (size_t == uint32_t), (3) bounds-check via size_t-widened comparison.
319+ VerifyOrReturnError (mContainerEnd >= mElemStart , ASN1_ERROR_INVALID_STATE);
320+ VerifyOrReturnError (mHeadLen <= UINT32_MAX - ValueLen, ASN1_ERROR_LENGTH_OVERFLOW);
321+ VerifyOrReturnError (static_cast <size_t >(mHeadLen ) + ValueLen <= static_cast <size_t >(mContainerEnd - mElemStart ),
322+ ASN1_ERROR_UNDERRUN);
226323
227324 if (ValueLen == 1 )
228325 {
229326 outVal = 0 ;
230327 }
231328 else
232329 {
233- outVal = ReverseBits (Value[1 ]);
234- int shift = 8 ;
330+ outVal = ReverseBits (Value[1 ]);
331+ unsigned int shift = 8 ;
235332 for (uint32_t i = 2 ; i < ValueLen; i++, shift += 8 )
236333 {
237- outVal |= static_cast <uint32_t >(ReverseBits (Value[i]) << shift);
334+ // Cast to uint32_t before shifting: ReverseBits returns uint8_t which
335+ // would be promoted to (signed) int, and shifts of 24+ on a value with
336+ // the high bit set are undefined behavior on signed integers.
337+ outVal |= (static_cast <uint32_t >(ReverseBits (Value[i])) << shift);
238338 }
239339 }
240340
0 commit comments