Severity: LOW (consistency / robustness)
Component: crates/marmot-markdown
Summary
The numeric-character-reference decoder scans an unbounded run of digits, unlike every other bounded scanner in the crate. The named-entity decoder caps its scan at 32 chars and the inline reference-label scanner caps at 999; decode_numeric has no digit-count cap. The overflow itself is safe (saturating_mul/saturating_add, and out-of-range → U+FFFD) and the work is linear in input length, so this is not a DoS amplification — it is a genuine consistency gap with the crate's own "cap every unbounded scan" discipline and with CommonMark, which bounds numeric references to a small digit count.
Location
crates/marmot-markdown/src/entity.rs:35-60 (decode_numeric):
let mut k = val_start;
while k < b.len() {
let c = b[k];
let digit = /* break on non-digit */;
code = code.saturating_mul(base).saturating_add(digit);
k += 1; // <-- no bound on number of digits scanned
}
Contrast:
crates/marmot-markdown/src/entity.rs:23 — decode_named caps at 32: if j - start > 32 { return None; }.
crates/marmot-markdown/src/inline.rs:918 — inline reference-label scanner caps at 999.
Concrete scenario
A message containing &# followed by an arbitrarily long digit run with no terminating ; (e.g. �…0) makes decode_numeric scan the entire run before returning None. Safe and linear, but unbounded relative to the crate's other scanners and the CommonMark bound.
Suggested fix
Cap the digit-scan length (CommonMark uses at most 7 digits for decimal / 6 for hex before rejecting), consistent with decode_named and the label scanners.
Dedup
Distinct from #800 (block-level link-reference-definition label length cap — a different function, block.rs) and #759 (numeric refs decoding C0/C1 control chars — about the decoded value, not the scan bound). Neither covers the missing digit-count cap in decode_numeric.
Filed from a meticulous automated code-review pass; verified against source and deduplicated against the existing open/closed issue set.
Severity: LOW (consistency / robustness)
Component:
crates/marmot-markdownSummary
The numeric-character-reference decoder scans an unbounded run of digits, unlike every other bounded scanner in the crate. The named-entity decoder caps its scan at 32 chars and the inline reference-label scanner caps at 999;
decode_numerichas no digit-count cap. The overflow itself is safe (saturating_mul/saturating_add, and out-of-range →U+FFFD) and the work is linear in input length, so this is not a DoS amplification — it is a genuine consistency gap with the crate's own "cap every unbounded scan" discipline and with CommonMark, which bounds numeric references to a small digit count.Location
crates/marmot-markdown/src/entity.rs:35-60(decode_numeric):Contrast:
crates/marmot-markdown/src/entity.rs:23—decode_namedcaps at 32:if j - start > 32 { return None; }.crates/marmot-markdown/src/inline.rs:918— inline reference-label scanner caps at 999.Concrete scenario
A message containing
&#followed by an arbitrarily long digit run with no terminating;(e.g.�…0) makesdecode_numericscan the entire run before returningNone. Safe and linear, but unbounded relative to the crate's other scanners and the CommonMark bound.Suggested fix
Cap the digit-scan length (CommonMark uses at most 7 digits for decimal / 6 for hex before rejecting), consistent with
decode_namedand the label scanners.Dedup
Distinct from #800 (block-level link-reference-definition label length cap — a different function,
block.rs) and #759 (numeric refs decoding C0/C1 control chars — about the decoded value, not the scan bound). Neither covers the missing digit-count cap indecode_numeric.Filed from a meticulous automated code-review pass; verified against source and deduplicated against the existing open/closed issue set.