Package: lintian Version: 2.138.0 Severity: important Dear maintainers,
lintian hangs (runs into an infinite loop) on https://deb.debian.org/debian/pool/main/s/simplesamlphp/simplesamlphp_2.3.11-1.dsc I dug into this (diclaimer: using AI) and it sounds like this is due to insecure parsing of XML files in the dep5 check. (second disclaimer: I would not have been able to find this myself, I think). There's another potential occurence of the issue in the appstream check. This did not happen in previous versions of simplesamlphp because the affected XML file was not included in the source package. What follows is a AI-generated bug report. I reviewed it, but did not modify it. I did not check the claim that it could also be used for information disclosure. - Lucas ================================================================ The `debian/copyright/dep5` check (and the identically-written appstream check) parse arbitrary upstream-shipped XML files with `XML::LibXML`, hardening the parser only with `no_network => 1`. That option maps to libxml2's `XML_PARSE_NONET`, which blocks *network* (http/ftp) external entities and DTDs but does NOT block local `file://` external entities. Because the rest of the standard XXE hardening (`load_ext_dtd`, `expand_entities`, `no_ent`, `huge_schema`) is not applied, any source package that ships an attacker-controlled `.xml` file can make lintian: * hang indefinitely / exhaust CPU and RAM (denial of service), or * disclose locally-readable files (e.g. `/etc/passwd`) by injecting the result back into a tag that lintian inspects and reports. A real-world source package already triggers the hang: lintian on `simplesamlphp_2.3.11-1.dsc` never returns; CPU-bound with `/dev/urandom` held open by the lintian process. (The fixture is an upstream XXE test fixture pointing at `file:///dev/urandom`, which libxml2 tries to fully read into the entity, streaming forever.) Affected code paths (lintian 2.138.0) ------------------------------------- Two `XML::LibXML` callers, both only call `set_option('no_network', 1)`: 1. `lib/Lintian/Check/Debian/Copyright/Dep5.pm:805-811` In `check_dep5_copyright`, the appstream-metadata-license loop parses *every shipped `*.xml`* in the unpacked source tree whose path appears in `%license_identifiers_by_file` (i.e. any shipped `.xml` under a `Files:` wildcard in `d/copyright`). This is the exact site of the simplesamlphp hang. It runs on source processables. my $parser = XML::LibXML->new; $parser->set_option('no_network', 1); my $file = $self->processable->patched->resolve_path($name); my $doc; try { $doc = $parser->parse_file($file->unpacked_path); } catch { next; }; 2. `lib/Lintian/Check/AppstreamMetadata.pm:143-148` `check_modalias` parses each installed `*.metainfo.xml` / `usr/share/metainfo/*.xml` file (guarded only by an `is_open_ok` size/type check). Runs on binary (installable) processables. The XXE surface is smaller here in practice (appstream files in `/usr/share/metainfo` are usually small and well-formed), but the hardening gap is byte-identical to case 1 and exploitable by a malicious package author. Both call sites have been confirmed byte-for-byte equivalent across all lintian versions examined from 2.104.0 (Nov 2020) through 2.138.0 (Jul 2026); nothing in the XML hardening has ever been strengthened. Older versions were installed from snapshot.debian.org and behaviourally reproduced against the simplesamlphp 2.3.11-1 dsc (every runnable version ≥ 2.114.0 hangs within a 45s timeout). Reproduction ------------ Minimal XML::LibXML repro (mirrors the dep5 parser options): $ cat xxe.xml <?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE foo [<!ELEMENT foo ANY > <!ENTITY xxe SYSTEM "file:///dev/urandom" >]> <foo>&xxe;</foo> $ cat repro.pl use XML::LibXML; my $p = XML::LibXML->new; $p->set_option("no_network", 1); $doc = $p->parse_file($ARGV[0]); $ timeout 8 perl repro.pl xxe.xml; echo "exit=$?" exit=124 End-to-end on Debian (current trixie): $ wget http://deb.debian.org/.../simplesamlphp_2.3.11-1.dsc [...] $ lintian --verbose simplesamlphp_2.3.11-1.dsc ... Running check: debian/copyright/dep5 on source:simplesamlphp_2.3.11-1 ... [HANGS — CPU ~60-85%, /dev/urandom fd open] The fixture being ingested is `vendor/simplesamlphp/xml-common/resources/xml/domdocument_doctype.xml` shipped in the upstream `simplesamlphp/xml-common` composer dependency (included in Debian's 2.3.11-1 orig tarball). The previous Debian version `simplesamlphp_2.2.6-1` does *not* ship `xml-common`, so it does not trigger the bug; lintian finishes on it in ~12s, exit 0. Note that `file:///dev/urandom` is the *least* dangerous payload here. `file:///etc/passwd`, `file:///etc/shadow` (read as the invoking user), or `file:///proc/self/environ` would be silently read by libxml2 and inlined into the document; if anything in the new appstream loop inspects the text content of `&xxe;`-bearing nodes it could leak into lintian's output. Even without disclosure, a payload such as the "billion-laughs" exponential entity expansion hangs without touching the filesystem at all — `no_network` does nothing against purely internal entity bombs. Suggested fix (Dep5.pm and AppstreamMetadata.pm) ------------------------------------------------- Replace the parser construction in both call sites with a fully hardened one: my $parser = XML::LibXML->new; $parser->set_option('no_network', 1); $parser->set_option('expand_entities', 0); # block external entity expansion $parser->set_option('load_ext_dtd', 0); # do not even load the DOCTYPE subset $parser->set_option('huge_schema', 0); # reject pathologically large DTDs # equivalently: # $parser->set_option('recover', 2) and do NOT set XML_PARSE_NOENT # or use XML::LibXML->new->{expand_xinclude} = 0 as well Setting `expand_entities => 0` alone is sufficient to stop the `/dev/urandom` infinite read (verified with a `safe.pl` repro that returns in 0.048s, exit 0). The combination above additionally mitigates billion-laughs, XXE file disclosure, and XInclude expansion. For consistency it would be ideal to centralise the parser construction into a small helper in `Lintian::Util` (or a new `Lintian::XML`) so that both call sites — and any future XML consumer — share a single hardened instantiation.

