diff --git a/regex-automata/src/dfa/dense.rs b/regex-automata/src/dfa/dense.rs index 057536303..0dc3324ef 100644 --- a/regex-automata/src/dfa/dense.rs +++ b/regex-automata/src/dfa/dense.rs @@ -2340,10 +2340,14 @@ impl<'a> DFA<&'a [u32]> { // table, match states and accelerators below. If any validation fails, // then we return an error. let (dfa, nread) = unsafe { DFA::from_bytes_unchecked(slice)? }; + // Note: Validation order is important here: + // - MatchState::validate can be called with an untrusted DFA. + // - TransistionTable::validate uses dfa.ms through match_len + // - StartTable::validate needs a valid transition table + dfa.accels.validate()?; + dfa.ms.validate(&dfa)?; dfa.tt.validate(&dfa)?; dfa.st.validate(&dfa)?; - dfa.ms.validate(&dfa)?; - dfa.accels.validate()?; // N.B. dfa.special doesn't have a way to do unchecked deserialization, // so it has already been validated. for state in dfa.states() { @@ -5230,4 +5234,17 @@ mod tests { let got = dfa.try_search_rev(&input); assert_eq!(Err(expected), got); } + + // This panics in TransitionTable::validate if the match states are not validated first. + #[test] + fn regression_validation_order() { + let mut dfa = DFA::new("abc").unwrap(); + dfa.ms = MatchStates { + slices: vec![], + pattern_ids: vec![], + pattern_len: 1, + }; + let (buf, _) = dfa.to_bytes_native_endian(); + DFA::from_bytes(&buf).unwrap_err(); + } }