CVE-2023-34411 (published 2023-06-05T04:15Z)

The xml-rs crate before 0.8.14 for Rust and Crab allows a denial of service (panic) via an invalid <! token (such as <!DOCTYPEs/%<!A nesting) in an XML document. The earliest affected version is 0.8.9.

CVSS v3 base score: 7.5, HIGH (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H)

Weakness information:
cwe_id: CWE-611
cwe_name: Improper Restriction of XML External Entity Reference
description: The product processes an XML document that can contain XML entities with URIs that resolve to documents outside of the intended sphere of control, causing the product to embed incorrect documents into its output.
extended_description: 
            XML documents optionally contain a Document Type Definition (DTD), which, among other features, enables the definition of XML entities. It is possible to define an entity by providing a substitution string in the form of a URI. The XML parser can access the contents of this URI and embed these contents back into the XML document for further processing.
            By submitting an XML file that defines an external entity with a file:// URI, an attacker can cause the processing application to read the contents of a local file. For example, a URI such as "file:///c:/winnt/win.ini" designates (in Windows) the file C:\Winnt\win.ini, or file:///etc/passwd designates the password file in Unix-based systems. Using URIs with other schemes such as http://, the attacker can force the application to make outgoing requests to servers that the attacker cannot reach directly, which can be used to bypass firewall restrictions or hide the source of attacks such as port scanning.
            Once the content of the URI is read, it is fed back into the application that is processing the XML. This application may echo back the data (e.g. in an error message), thereby exposing the file contents.
         
         

Fix information:
repo_url: https://github.com/netvl/xml-rs
commit_details: repo_url: https://github.com/netvl/xml-rs
author: Carlos López
author_date: 2023-06-01 14:17:14+02:00
msg: Avoid panic when displaying unexpected token error

Avoid entering unreachable!() when displaying an error message for an
invalid `<!` token. This fixes a panic when processing a malformed
XML which contains such token in an unexpected place, like the
following:

`<!DOCTYPEs/%<!A`
repository: repo_url: https://github.com/netvl/xml-rs
repo_name: netvl/xml-rs
description: An XML library in Rust
repo_language: Rust
owner: netvl
file_changes: filename: lexer.rs
change_type: ModificationType.MODIFY
diff: @@ -81,6 +81,7 @@ impl fmt::Display for Token {
                 Token::EqualsSign                 => "=",
                 Token::SingleQuote                => "'",
                 Token::DoubleQuote                => "\"",
+                Token::MarkupDeclarationStart     => "<!",
                 _                          => unreachable!()
             }.fmt(f),
         }

programming_language: Rust
repo_url: https://github.com/00xc/xml-rs
commit_details: repo_url: https://github.com/00xc/xml-rs
author: Kornel
author_date: 2023-05-06 21:34:40+01:00
msg: Parse DOCTYPE markup declarations
repository: repo_url: https://github.com/00xc/xml-rs
repo_name: 00xc/xml-rs
description: An XML library in Rust
owner: 00xc
file_changes: filename: README.md
change_type: ModificationType.MODIFY
diff: @@ -25,8 +25,9 @@ clean manner.
 
 This parser is mostly full-featured, however, there are limitations:
 * Only UTF-8 is supported;
-* DTD validation is not supported, `<!DOCTYPE>` declarations are completely ignored; thus no
+* There is only very rudimentary parsing of `<!DOCTYPE>` declarations; thus no
   support for custom entities too; internal DTD declarations are likely to cause parsing errors;
+* DTD validation is not supported;
 * attribute value normalization is not performed, and end-of-line characters are not normalized either.
 
 Other than that the parser tries to be mostly XML-1.1-compliant.

programming_language: Markdown
filename: lexer.rs
change_type: ModificationType.MODIFY
diff: @@ -54,6 +54,8 @@ pub(crate) enum Token {
     ReferenceStart,
     /// `;`
     ReferenceEnd,
+    /// `<!` of `ENTITY`
+    MarkupDeclarationStart,
 }
 
 impl fmt::Display for Token {
@@ -143,6 +145,7 @@ impl Token {
     }
 }
 
+#[derive(Copy, Clone)]
 enum State {
     /// Default state
     Normal,
@@ -154,8 +157,10 @@ enum State {
     CommentStarted,
     /// Triggered on '<!D' up to '<!DOCTYPE'
     DoctypeStarted(DoctypeStartedSubstate),
+    /// Other items like `<!ELEMENT` in DTD
+    InsideMarkupDeclaration,
     /// Triggered after DoctypeStarted to handle sub elements
-    DoctypeFinishing(u8),
+    InsideDoctype,
     /// Triggered on '<![' up to '<![CDATA'
     CDataStarted(CDataStartedSubstate),
     /// Triggered on '?'
@@ -174,6 +179,13 @@ enum State {
     InsideCdata,
     /// After `<?`
     InsideProcessingInstruction,
+    /// `<!ENTITY "here">`
+    InsideMarkupDeclarationQuotedString(QuoteStyle),
+}
+
+#[derive(Copy, Clone, Eq, PartialEq)]
+enum QuoteStyle {
+    Single, Double
 }
 
 #[derive(Copy, Clone)]
@@ -229,6 +241,8 @@ pub(crate) struct Lexer {
     head_pos: TextPosition,
     char_queue: VecDeque<char>,
     st: State,
+    /// Default state to go back to after a tag end (may be `InsideDoctype`)
+    normal_state: State,
     skip_errors: bool,
     inside_token: bool,
     eof_handled: bool
@@ -248,21 +262,16 @@ impl Lexer {
             head_pos: TextPosition::new(),
             char_queue: VecDeque::with_capacity(4),  // TODO: check size
             st: State::Normal,
+            normal_state: State::Normal,
             skip_errors: false,
             inside_token: false,
             eof_handled: false
         }
     }
 
-    /// Enables error handling so `next_token` will return `Some(Err(..))`
-    /// upon invalid lexeme.
-    #[inline]
-    pub fn enable_errors(&mut self) { self.skip_errors = false; }
-
     /// Disables error handling so `next_token` will return `Some(Chunk(..))`
     /// upon invalid lexeme with this lexeme content.
-    #[inline]
-    pub fn disable_errors(&mut self) { self.skip_errors = true; }
+    pub(crate) fn disable_errors(&mut self) { self.skip_errors = true; }
 
     /// Reset the eof handled flag of the lexer.
     #[inline]
@@ -326,9 +335,9 @@ impl Lexer {
             State::TagStarted | State::CommentOrCDataOrDoctypeStarted |
             State::CommentStarted | State::CDataStarted(_)| State::DoctypeStarted(_) |
             State::CommentClosing(ClosingSubstate::Second) |
-            State::InsideComment |
+            State::InsideComment | State::InsideMarkupDeclaration |
             State::InsideProcessingInstruction | State::ProcessingInstructionClosing |
-            State::DoctypeFinishing(_) =>
+            State::InsideDoctype | State::InsideMarkupDeclarationQuotedString(_) =>
                 Err(self.error("Unexpected end of stream")),
             State::EmptyTagClosing =>
                 Ok(Some(Token::Character('/'))),
@@ -369,7 +378,7 @@ impl Lexer {
             State::CommentStarted                 => self.comment_started(c),
             State::CDataStarted(s)                => self.cdata_started(c, s),
             State::DoctypeStarted(s)              => self.doctype_started(c, s),
-            State::DoctypeFinishing(d)            => self.doctype_finishing(c, d),
+            State::InsideDoctype                  => self.inside_doctype(c),
             State::EmptyTagClosing                => self.empty_element_closing(c),
             State::CommentClosing(s)              => self.comment_closing(c, s),
             State::CDataClosing(s)                => self.cdata_closing(c, s),
@@ -378,6 +387,8 @@ impl Lexer {
             State::InsideCdata                    => self.inside_cdata(c),
             State::InsideProcessingInstruction    => self.inside_processing_instruction(c),
             State::ProcessingInstructionClosing   => self.processing_instruction_closing(c),
+            State::InsideMarkupDeclaration       => self.markup_declaration(c),
+            State::InsideMarkupDeclarationQuotedString(q) => self.markup_declaration_string(c, q),
         }
     }
 
@@ -393,6 +404,13 @@ impl Lexer {
         Ok(Some(token))
     }
 
+    #[inline]
+    fn move_to_and_reset_normal(&mut self, st: State, token: Token) -> Result {
+        self.normal_state = st;
+        self.st = st;
+        Ok(Some(token))
+    }
+
     #[inline]
     fn move_to_with_unread(&mut self, st: State, cs: &[char], token: Token) -> Result {
         self.char_queue.extend(cs.iter().copied());
@@ -434,6 +452,7 @@ impl Lexer {
     }
 
     fn inside_processing_instruction(&mut self, c: char) -> Result {
+        // These tokens are used by `<?xml?>` parser
         match c {
             '?'                        => self.move_to(State::ProcessingInstructionClosing),
             '<'                        => Ok(Some(Token::OpeningTagStart)),
@@ -461,10 +480,10 @@ impl Lexer {
     fn tag_opened(&mut self, c: char) -> Result {
         match c {
             '?'                        => self.move_to_with(State::InsideProcessingInstruction, Token::ProcessingInstructionStart),
-            '/'                        => self.move_to_with(State::Normal, Token::ClosingTagStart),
+            '/'                        => self.move_to_with(self.normal_state, Token::ClosingTagStart),
             '!'                        => self.move_to(State::CommentOrCDataOrDoctypeStarted),
-            _ if is_whitespace_char(c) => self.move_to_with_unread(State::Normal, &[c], Token::OpeningTagStart),
-            _ if is_name_char(c)       => self.move_to_with_unread(State::Normal, &[c], Token::OpeningTagStart),
+            _ if is_whitespace_char(c) => self.move_to_with_unread(self.normal_state, &[c], Token::OpeningTagStart),
+            _ if is_name_char(c)       => self.move_to_with_unread(self.normal_state, &[c], Token::OpeningTagStart),
             _                          => self.handle_error("<", c)
         }
     }
@@ -475,6 +494,7 @@ impl Lexer {
             '-' => self.move_to(State::CommentStarted),
             '[' => self.move_to(State::CDataStarted(CDataStartedSubstate::E)),
             'D' => self.move_to(State::DoctypeStarted(DoctypeStartedSubstate::D)),
+            'E' | 'A' | 'N' if matches!(self.normal_state, State::InsideDoctype) => self.move_to_with(State::InsideMarkupDeclaration, Token::MarkupDeclarationStart),
             _ => self.handle_error("<!", c),
         }
     }
@@ -500,6 +520,27 @@ impl Lexer {
         )
     }
 
+    /// Encountered '<!…' that isn't DOCTYPE or CDATA
+    fn markup_declaration(&mut self, c: char) -> Result {
+        match c {
+            '<'                        => self.handle_error("<!", c),
+            '>'                        => self.move_to_with(self.normal_state, Token::TagEnd),
+            '&'                        => Ok(Some(Token::ReferenceStart)),
+            ';'                        => Ok(Some(Token::ReferenceEnd)),
+            '"'                        => self.move_to_with(State::InsideMarkupDeclarationQuotedString(QuoteStyle::Double), Token::DoubleQuote),
+            '\''                       => self.move_to_with(State::InsideMarkupDeclarationQuotedString(QuoteStyle::Single), Token::SingleQuote),
+            _ => Ok(None),
+        }
+    }
+
+    fn markup_declaration_string(&mut self, c: char, q: QuoteStyle) -> Result {
+        match c {
+            '"' if q == QuoteStyle::Double  => self.move_to_with(State::InsideMarkupDeclaration, Token::DoubleQuote),
+            '\'' if q == QuoteStyle::Single => self.move_to_with(State::InsideMarkupDeclaration, Token::SingleQuote),
+            _ => Ok(None),
+        }
+    }
+
     /// Encountered '<!D'
     fn doctype_started(&mut self, c: char, s: DoctypeStartedSubstate) -> Result {
         use self::DoctypeStartedSubstate::{D, DO, DOC, DOCT, DOCTY, DOCTYP};
@@ -509,16 +550,17 @@ impl Lexer {
             DOC    ; 'T' ; DOCT   ; "<!DOC",
             DOCT   ; 'Y' ; DOCTY  ; "<!DOCT",
             DOCTY  ; 'P' ; DOCTYP ; "<!DOCTY";
-            DOCTYP ; 'E' ; "<!DOCTYP" ; self.move_to_with(State::DoctypeFinishing(1), Token::DoctypeStart)
+            DOCTYP ; 'E' ; "<!DOCTYP" ; self.move_to_and_reset_normal(State::InsideDoctype, Token::DoctypeStart)
         )
     }
 
     /// State used while awaiting the closing bracket for the <!DOCTYPE tag
-    fn doctype_finishing(&mut self, c: char, d: u8) -> Result {
+    fn inside_doctype(&mut self, c: char) -> Result {
         match c {
-            '<' => self.move_to(State::DoctypeFinishing(d + 1)),
-            '>' if d == 1 => self.move_to_with(State::Normal, Token::TagEnd),
-            '>' => self.move_to(State::DoctypeFinishing(d - 1)),
+            '>' => self.move_to_and_reset_normal(State::Normal, Token::TagEnd),
+            '<'                        => self.move_to(State::TagStarted),
+            '&'                        => Ok(Some(Token::ReferenceStart)),
+            ';'                        => Ok(Some(Token::ReferenceEnd)),
             _ => Ok(None),
         }
     }
@@ -526,7 +568,7 @@ impl Lexer {
     /// Encountered '?'
     fn processing_instruction_closing(&mut self, c: char) -> Result {
         match c {
-            '>' => self.move_to_with(State::Normal, Token::ProcessingInstructionEnd),
+            '>' => self.move_to_with(self.normal_state, Token::ProcessingInstructionEnd),
             _ => self.move_to_with_unread(State::InsideProcessingInstruction, &[c], Token::Character('?')),
         }
     }
@@ -534,8 +576,8 @@ impl Lexer {
     /// Encountered '/'
     fn empty_element_closing(&mut self, c: char) -> Result {
         match c {
-            '>' => self.move_to_with(State::Normal, Token::EmptyTagEnd),
-            _ => self.move_to_with_unread(State::Normal, &[c], Token::Character('/')),
+            '>' => self.move_to_with(self.normal_state, Token::EmptyTagEnd),
+            _ => self.move_to_with_unread(self.normal_state, &[c], Token::Character('/')),
         }
     }
 
@@ -547,7 +589,7 @@ impl Lexer {
                 _ => self.move_to_with_unread(State::InsideComment, &[c], Token::Character('-')),
             },
             ClosingSubstate::Second => match c {
-                '>' => self.move_to_with(State::Normal, Token::CommentEnd),
+                '>' => self.move_to_with(self.normal_state, Token::CommentEnd),
                 // double dash not followed by a greater-than is a hard error inside comment
                 _ => self.handle_error("--", c),
             },
@@ -576,7 +618,7 @@ impl Lexer {
                 _ => self.move_to_with_unread(State::Normal, &[c], Token::Character(']')),
             },
             ClosingSubstate::Second => match c {
-                '>' => self.move_to_with(State::Normal, Token::CDataEnd),
+                '>' => self.move_to_with(self.normal_state, Token::CDataEnd),
                 _ => self.move_to_with_unread(State::Normal, &[']', c], Token::Character(']')),
             },
         }
@@ -825,19 +867,54 @@ mod tests {
     #[test]
     fn doctype_with_internal_subset_test() {
         let (mut lex, mut buf) = make_lex_and_buf(
-            r#"<a><!DOCTYPE ab[<!ELEMENT ba> ]> "#
+            r#"<a><!DOCTYPE ab[<!ELEMENT ba ">>>>>"> ]> "#
         );
         assert_oks!(for lex and buf ;
             Token::OpeningTagStart
             Token::Character('a')
             Token::TagEnd
             Token::DoctypeStart
+            Token::MarkupDeclarationStart
+            Token::DoubleQuote
+            Token::DoubleQuote
+            Token::TagEnd
             Token::TagEnd
             Token::Whitespace(' ')
         );
         assert_none!(for lex and buf);
     }
 
+    #[test]
+    fn doctype_internal_pi_comment() {
+        let (mut lex, mut buf) = make_lex_and_buf(
+            "<!DOCTYPE a [\n<!ELEMENT leopard ANY> <!-- <?non?>--> <?pi > ?> \n]>"
+        );
+        assert_oks!(for lex and buf ;
+            Token::DoctypeStart
+            Token::MarkupDeclarationStart
+            Token::TagEnd
+            Token::CommentStart
+            Token::Whitespace(' ')
+            Token::Character('<')
+            Token::Character('?')
+            Token::Character('n')
+            Token::Character('o')
+            Token::Character('n')
+            Token::Character('?')
+            Token::Character('>')
+            Token::CommentEnd
+            Token::ProcessingInstructionStart
+            Token::Character('p')
+            Token::Character('i')
+            Token::Whitespace(' ')
+            Token::TagEnd // not really
+            Token::Whitespace(' ')
+            Token::ProcessingInstructionEnd
+            Token::TagEnd // DTD
+        );
+        assert_none!(for lex and buf);
+    }
+
     #[test]
     fn end_of_stream_handling_ok() {
         macro_rules! eof_check(
@@ -872,7 +949,6 @@ mod tests {
         eof_check!("<![CDA"   ; 0, 6);
         eof_check!("<![CDAT"  ; 0, 7);
         eof_check!("<![CDATA" ; 0, 8);
-        // eof_check!("--"       ; 0, 2);
     }
 
     #[test]

programming_language: Rust
filename: inside_cdata.rs
change_type: ModificationType.MODIFY
diff: @@ -7,7 +7,6 @@ impl PullParser {
     pub fn inside_cdata(&mut self, t: Token) -> Option<Result> {
         match t {
             Token::CDataEnd => {
-                self.lexer.enable_errors();
                 let event = if self.config.cdata_to_characters {
                     None
                 } else {

programming_language: Rust
filename: inside_doctype.rs
change_type: ModificationType.MODIFY
diff: @@ -6,10 +6,23 @@ impl PullParser {
     pub fn inside_doctype(&mut self, t: Token) -> Option<Result> {
         match t {
             Token::TagEnd => {
-                self.lexer.enable_errors();
                 self.into_state_continue(State::OutsideTag)
             }
 
+            Token::MarkupDeclarationStart => {
+                self.into_state_continue(State::InsideDoctypeMarkupDeclaration)
+            },
+
+            _ => None,
+        }
+    }
+
+    pub fn inside_doctype_markup_declaration(&mut self, t: Token) -> Option<Result> {
+        match t {
+            Token::TagEnd => {
+                self.into_state_continue(State::InsideDoctype)
+            }
+
             _ => None,
         }
     }

programming_language: Rust
filename: inside_processing_instruction.rs
change_type: ModificationType.MODIFY
diff: @@ -68,7 +68,6 @@ impl PullParser {
 
             ProcessingInstructionSubstate::PIInsideData => match t {
                 Token::ProcessingInstructionEnd => {
-                    self.lexer.enable_errors();
                     let name = self.data.take_name();
                     let data = self.take_buf();
                     self.into_state_emit(

programming_language: Rust
filename: mod.rs
change_type: ModificationType.MODIFY
diff: @@ -139,6 +139,7 @@ pub enum State {
     InsideCData,
     InsideDeclaration(DeclarationSubstate),
     InsideDoctype,
+    InsideDoctypeMarkupDeclaration,
     InsideReference(Box<State>),
 }
 
@@ -337,6 +338,7 @@ impl PullParser {
             State::InsideProcessingInstruction(s) => self.inside_processing_instruction(t, s),
             State::InsideDeclaration(s)           => self.inside_declaration(t, s),
             State::InsideDoctype                  => self.inside_doctype(t),
+            State::InsideDoctypeMarkupDeclaration => self.inside_doctype_markup_declaration(t),
             State::InsideOpeningTag(s)            => self.inside_opening_tag(t, s),
             State::InsideClosingTag(s)            => self.inside_closing_tag_name(t, s),
             State::InsideComment                  => self.inside_comment(t),

programming_language: Rust
filename: outside_tag.rs
change_type: ModificationType.MODIFY
diff: @@ -81,7 +81,6 @@ impl PullParser {
                         // We don't have a doctype event so skip this position
                         // FIXME: update when we have a doctype event
                         self.next_pos();
-                        self.lexer.disable_errors();
                         self.into_state(State::InsideDoctype, next_event)
                     }
 

programming_language: Rust
filename: xmlconf.rs
change_type: ModificationType.MODIFY
diff: @@ -116,7 +116,6 @@ fn expect_ill_formed(xml_path: &Path, msg: &str, id: &str) -> Result<(), Box<dyn
         "rmt-e2e-18", // External entity containing start of entity declaration is base URI for system identifier
         "rmt-e2e-19", // Parameter entities and character references are included-in-literal, but general entities are bypassed.
         "rmt-e2e-22", // UTF-8 entities may start with a BOM
-        "rmt-e2e-24", // Either the built-in entity or a character reference can be used to represent greater-than after two close-square-brackets
         "rmt-e2e-34", // A non-deterministic content model is an error even if the element type is not used.
         "rmt-e2e-50", // All line-ends are normalized, even those not passed to the application. NB this can only be tested effectively in XML 1.1, since CR is in the S production; in 1.1 we can use NEL which isn't.
         "rmt-e2e-55", // A reference to an unparsed entity in an entity value is an error rather than forbidden (unless the entity is referenced, of course)
@@ -278,23 +277,20 @@ fn expect_ill_formed(xml_path: &Path, msg: &str, id: &str) -> Result<(), Box<dyn
 
 #[test] fn oasis() {
     run_suite("oasis/oasis.xml", &[
-        "o-p43pass1", // Valid use of character data, comments, processing instructions and CDATA sections within the start and end tag.
-        "o-p68pass1", // Valid entity references.  Also ensures that a charref to           '&' isn't interpreted as an entity reference open delimiter
-        "o-p04pass1", // names with all valid ASCII characters, and one from each               other class in NameChar
-        "o-p05pass1", // various valid Name constructions
         "o-p01fail1", // S cannot occur before the prolog
         "o-p01fail2", // comments cannot occur before the prolog
         "o-p01fail3", // only one document element
+        "o-p04pass1", // names with all valid ASCII characters, and one from each               other class in NameChar
+        "o-p05pass1", // various valid Name constructions
         "o-p09fail1", // EntityValue excludes '%'
         "o-p09fail2", // EntityValue excludes '&'
         "o-p09fail3", // incomplete character reference
-        "o-p09fail4", // quote types must match
-        "o-p09fail5", // quote types must match
-        "o-p11fail1", // quote types must match
-        "o-p11fail2", // cannot contain delimiting quotes
-        "o-p12fail1", // '"' excluded
+        "o-p11pass1", // p11pass1.xml       system literals may not contain     URI fragments
+        "o-p12fail1", // p12fail1.xml       '"' excluded
         "o-p12fail2", // '\' excluded
         "o-p12fail3", // entity references excluded
+        "o-p12fail4", // p12fail4.xml       '>' excluded
+        "o-p12fail5", // p12fail5.xml       '<' excluded
         "o-p12fail6", // built-in entity refs excluded
         "o-p12fail7", // The public ID has a tab character, which is disallowed
         "o-p14fail3", // "]]>" excluded
@@ -303,13 +299,12 @@ fn expect_ill_formed(xml_path: &Path, msg: &str, id: &str) -> Result<(), Box<dyn
         "o-p22fail2", // prolog must start with XML decl
         "o-p23fail1", // "xml" must be lower-case
         "o-p27fail1", // References aren't allowed in Misc,     even if they would resolve to valid Misc.
-        "o-p29fail1", // A processor must not pass unknown declaration types.
         "o-p30fail1", // An XML declaration is not the same as a TextDecl
         "o-p31fail1", // external subset excludes doctypedecl
         "o-p32fail3", // initial S is required
         "o-p40fail1", // S is required between attributes
+        "o-p43pass1", // Valid use of character data, comments, processing instructions and CDATA sections within the start and end tag.
         "o-p44fail4", // Whitespace required between attributes.
-        "o-p45fail1", // ELEMENT must be upper case.
         "o-p45fail2", // S before contentspec is required.
         "o-p45fail3", // only one content spec
         "o-p45fail4", // no comments in declarations (contrast with SGML)
@@ -371,17 +366,18 @@ fn expect_ill_formed(xml_path: &Path, msg: &str, id: &str) -> Result<(), Box<dyn
         "o-p64fail1", // section delimiters must balance
         "o-p64fail2", // section delimiters must balance
         "o-p66fail5", // no references to non-characters
+        "o-p68pass1", // Valid entity references.  Also ensures that a charref to           '&' isn't interpreted as an entity reference open delimiter
         "o-p69fail1", // terminating ';' is required
         "o-p69fail2", // no S after '%'
         "o-p69fail3", // no S before ';'
         "o-p70fail1", // This is neither
         "o-p71fail1", // S is required before EntityDef
         "o-p71fail2", // Entity name is a Name, not an NMToken
-        "o-p71fail3", // no S after "<!"
         "o-p71fail4", // S is required after "<!ENTITY"
         "o-p72fail1", // S is required after "<!ENTITY"
         "o-p72fail2", // S is required after '%'
         "o-p72fail3", // S is required after name
+        "o-p76fail4", // p76fail4.xml       notation names are Names
         "o-p72fail4", // Entity name is a name, not an NMToken
         "o-p73fail1", // No typed replacement text
         "o-p73fail2", // Only one replacement value
@@ -438,8 +434,6 @@ fn expect_ill_formed(xml_path: &Path, msg: &str, id: &str) -> Result<(), Box<dyn
         "content02", // No whitespace before "*" in content model
         "content03", // No whitespace before "+" in content model
         "decl01", // External entities may not have standalone decls.
-        "nwf-dtd00", // Comma mandatory in content model
-        "nwf-dtd01", // Can't mix comma and vertical bar in content models
         "dtd02", // PE name immediately after "%"
         "dtd03", // PE name immediately followed by ";"
         "dtd04", // PUBLIC literal must be quoted
@@ -451,6 +445,9 @@ fn expect_ill_formed(xml_path: &Path, msg: &str, id: &str) -> Result<(), Box<dyn
         "encoding04", // Illegal character ":" in encoding name
         "encoding05", // Illegal character "@" in encoding name
         "encoding06", // Illegal character "+" in encoding name
+        "nwf-dtd00", // Comma mandatory in content model
+        "nwf-dtd01", // Can't mix comma and vertical bar in content models
+        "pi", // pi.xml      No space between PI target name and data
         "pubid01", // Illegal entity ref in public ID
         "pubid02", // Illegal characters in public ID
         "pubid03", // Illegal characters in public ID
@@ -502,7 +499,6 @@ fn expect_ill_formed(xml_path: &Path, msg: &str, id: &str) -> Result<(), Box<dyn
         "not-wf-sa-060", // Invalid type NAME defined in ATTLIST.
         "not-wf-sa-061", // External entity declarations require whitespace between public     and system IDs.
         "not-wf-sa-062", // Entity declarations need space after the entity name.
-        "not-wf-sa-063", // Conditional sections may only appear in the external     DTD subset.
         "not-wf-sa-064", // Space is required between attribute type and default values     in <!ATTLIST...> declarations.
         "not-wf-sa-065", // Space is required between attribute name and type     in <!ATTLIST...> declarations.
         "not-wf-sa-066", // Required whitespace is missing.
@@ -523,7 +519,6 @@ fn expect_ill_formed(xml_path: &Path, msg: &str, id: &str) -> Result<(), Box<dyn
         "not-wf-sa-101", // Space is not permitted in an encoding name.
         "not-wf-sa-105", // Invalid placement of CDATA section.
         "not-wf-sa-106", // Invalid placement of entity declaration.
-        "not-wf-sa-107", // Invalid document type declaration.  CDATA alone is invalid.
         "not-wf-sa-113", // Parameter entity values must use valid reference syntax;     this reference is malformed.
         "not-wf-sa-114", // General entity values must use valid reference syntax;     this reference is malformed.
         "not-wf-sa-121", // A name of an ENTITY was started with an invalid character.
@@ -566,7 +561,6 @@ fn expect_ill_formed(xml_path: &Path, msg: &str, id: &str) -> Result<(), Box<dyn
         "not-wf-sa-174", // Character FFFF is not legal anywhere in an XML document.
         "not-wf-sa-175", // Character FFFF is not legal anywhere in an XML document.
         "not-wf-sa-177", // Character FFFF is not legal anywhere in an XML document.
-        "not-wf-sa-179", // Invalid syntax matching double quote is missing.
         "not-wf-sa-180", // The Entity Declared WFC requires entities to be declared     before they are used in an attribute list declaration.
         "not-wf-sa-183", // Mixed content declarations may not include content particles.
         "not-wf-sa-184", // In mixed content models, element names must not be     parenthesized.

programming_language: Rust