github domainaware/parsedmarc 11.0.4

4 hours ago

Security

  • Fixed catastrophic regex backtracking in the plain-text failure report fallback (GHSA-7grx-mj6g-qxmc, affects 8.2.0 through 11.0.3). A text/plain message containing A message claiming to be from you has failed is parsed as an Exim/cPanel failure notice by text_report_regex, \s*([a-zA-Z\s]+):\s(.+). Its leading \s* and the \s inside the field-name class can each consume the same whitespace, and the pattern is unanchored, so a run of whitespace is retried from every starting position and every way of splitting it: the cost grows cubically, about 8x per doubling — on CPython 3.12 (x86-64), 400 spaces took 0.06 s and 1600 spaces took 3.3 s. Anyone who can send mail to a monitored mailbox controls the body, so a message of about 100 KB would stall the mailbox watch loop for days, and since the message is never marked processed it would be re-parsed after every restart. The pattern is now anchored to the start of a line and its classes no longer overlap, so it runs in linear time. The pattern now requires a field name to start a line, with its words separated by single spaces, which is what Exim emits; notices with a tab, doubled space, or line break inside a field name were already rejected, because such names never produced the keys the parser looks up. Reported by mjatin2007.
  • Fixed quadratic regex backtracking when stripping XML schema tags from aggregate reports (GHSA-4h9r-c435-j834, affects 4.1.7 through 11.0.3). parse_aggregate_report_xml() removed <xs:schema> tags with </??xs:schema.*>, whose unanchored greedy .*> scanned to the end of the line for every <xs:schema it found, so a line of repeated <xs:schema took quadratic time — 1 MB took 19 s. The pattern runs on report attachments after gzip and zip are inflated, up to the 100 MiB decompression cap, and about 194 KB of gzip inflates to that cap. The pattern now stops at the first > (</?xs:schema[^<>]*>) and runs in linear time. This also fixes a correctness bug: on a minified report with the whole document on one line, the greedy .*> deleted everything from the schema tag to the last > on the line, discarding the report.

Bug fixes

  • Parsing a timestamp no longer takes quadratic time on whitespace. human_timestamp_to_datetime() stripped parenthesized comments with the regex \s*\(.*\)\s*, whose leading \s* retried every whitespace run from each starting position. The function runs on every message's Date header, so a long folded header was enough: 200,000 spaces took 18.6 s. This is the same weakness as the two entries above, but a lesser one: the input is a header that mail servers cap in size (Postfix's default is 100 KB, about 5 s) rather than an attachment inflated to the 100 MiB decompression cap, so it is filed as a bug fix without an advisory. Comments are now removed by a single depth-counting pass, which is linear and also handles the nested comments and quoted-pairs that RFC 5322 section 3.2.2 allows and that the old regex only stripped by accident.
  • The Grafana dashboard's "Message sources by name and type" table no longer comes up empty when a message source has a name but no type. Whenever a source's reverse DNS base domain is not in base_reverse_dns_map.csv, get_service_from_reverse_dns_base_domain() names the source after that base domain and leaves its type None; a source with no PTR record whose AS domain is not in the map is likewise named after its AS name with no type. For every such source the saved aggregate record has a source_name but no source_type. The panel nests a terms aggregation on source_type.keyword under one on source_name.keyword, without a missing value, so such a source produced a source_name bucket with no source_type sub-bucket, and Grafana's Elasticsearch data source then failed to build the table from the response. With Grafana 13.2.4 (bundled Elasticsearch data source 12.9.2) and the reports in samples/aggregate/, 12 sources had a name but no type and the table showed "No data"; with missing set, it lists all 27 name/type rows. The source_type.keyword aggregation now sets missing to -, the placeholder most of the dashboard's other tables already use for absent values, so those sources are listed with - as their type.

Don't miss a new parsedmarc release

NewReleases is sending notifications on new releases.