Control: tag -1 patch Hi!
This was my reply with analysis and queued fix. ----- Forwarded message from Guillem Jover <[email protected]> ----- Date: Tue, 28 Jul 2026 14:34:37 +0200 From: Guillem Jover <[email protected]> To: zer0d4y5 <[email protected]> Cc: [email protected] Subject: Re: NULL pointer dereference in dpkg control/Packages parser (empty first field) Message-ID: <[email protected]> Hi! [ Sorry, only saw this yesterday as it was in my spam box. :/ ] I'm CCing the Debian Security Team, which get involved in security affairs related to dpkg. Mail only very minimally trimmed, to give context to them. On Sun, 2026-07-26 at 20:42:27 -0400, zer0d4y5 wrote: > I found a small but reproducible crash in dpkg's control-stanza parser and > wanted to > report it privately first so it can be fixed before any public writeup. > > Parsing a control or Packages stanza whose first field has an empty value, > when that > field is handled by a value-dereferencing parser (Version is the clean > case), crashes > dpkg with a NULL pointer dereference. It is a denial of service, not memory > corruption > or code execution, but it does take down dpkg on malformed input where a > clean error > would be expected. Thanks for finding and reporting this! Given that this also affects «dpkg-deb -f foo.deb Field» which then causes temporary files to linger around (although usually very small ones), and the interfaces is considered (in its man page) a security boundary, I'm inclined to consider this a DoS security issue (but probably low impact, affecting automated systems that might be processing untrusted binaries). I assume commit ae03bfd28afea2fdc10be9657d78a1fe29dfa58a introduced this regression, but need to verify, which might mean this is present since dpkg 1.22.7, affecting Debian trixi and later. I've tested on chroots, and this indeed does not affect Debian bookworm, and affects Debian trixie. I'm not sure whether the Debian Security Team, would consider this enough for a CVE and a security upload though. > Root cause: in lib/dpkg/parse.c, pkg_parse_field() passes fs->value.buf to > the field > handler. For an empty value, varbuf_set_buf() calls varbuf_add_buf(..., 0), > which returns > early without allocating, so when this is the first field parsed in a > stanza the varbuf > has never grown and fs->value.buf is still NULL (fs is memset to 0 at the > top of > parsedb_parse). f_version() then reaches parse_db_version() and > parseversion(), which > dereferences it at parsehelp.c:250 (while (*string ...)) with no NULL guard. Right. The underlying problem though is the varbuf rework which was in theory done to make the API usage safer, but has already caused some other segfaults and crashes along the way. I'm not happy yet with the current API, but was not really happy with the previous state of it either. :/ > Trigger (10 bytes): > > printf 'Version:\nz\n' > crash.pkgs > > Reproduced on the shipped Ubuntu 26.04 dpkg 1.23.7 and on current git HEAD > (1.23.7-203-g7004a048f), both segfault (exit 139, core dumped): > > dpkg --merge-avail crash.pkgs # also --update-avail > dpkg --install <a .deb whose control's first field is an empty Version:> > > apt is not affected (it has its own parser), and dpkg-deb --info/--field > only dump the > control text without semantic parsing, so they do not crash. The reachable > consumers are > libdpkg's parsedb callers. > > Suggested fix (one line, verified): pass a guaranteed C string to the field > handlers, for > example varbuf_str(&fs->value) instead of fs->value.buf in > pkg_parse_field(). varbuf_str() > returns "" for a NULL buffer, so parseversion cleanly reports "version > string is empty". I > rebuilt current git HEAD with this change and the same input now returns > > dpkg: error: parsing file 'crash.pkgs' near line 1: > 'Version' field value '': version string is empty > > with exit 2, instead of crashing. This one change covers every field > handler, not just > Version. While doing another quick check over the whole code base I also noticed a problem with dependency fields, with empty version strings, such as: # dpkg --update-avail <<CTRL Package: test Version: 1.0 Depends: pkgname (= ) Maintainer: Maint <[email protected]> Description: test package CTRL Which also segfaults, but would not be a security issue. So instead I've prepared the attached change, which should be more complete. But I'd like to give another review pass over the codebase to make sure, and check that the new semantic changes do not break expectations of existing code assuming a NULL for zero length strings. > I am happy to share the exact .deb reproducer and a patch in whatever form > is easiest. No worries, the description was enough to generate reproducers for this. > No rush on my end, and I will hold off on any public description until you > have had a > chance to look and a fix is available. Please let me know a good way to > coordinate. Thank you! Let's see what the Security Team have to say, and then we can decide on how to proceed, or whether a schedule is needed, etc. Also let me know whether the current attribution is fine or you'd want some other name or email address being used, to use once I write the commit message, etc. Regards, Guillem Reported-by: zer0d4y5 <[email protected]> diff --git i/lib/dpkg/t/t-varbuf-cpp.cc w/lib/dpkg/t/t-varbuf-cpp.cc index 14d127b37..378344a8f 100644 --- i/lib/dpkg/t/t-varbuf-cpp.cc +++ w/lib/dpkg/t/t-varbuf-cpp.cc @@ -509,8 +509,8 @@ test_varbuf_detach(void) vb.init(0); vb.add_buf(nullptr, 0); test_pass(vb.used == 0); - test_pass(vb.size == 0); - test_pass(vb.buf == nullptr); + test_pass(vb.size > 0); + test_pass(vb.buf != nullptr); str = vb.detach(); test_str(str, ==, ""); test_pass(vb.used == 0); diff --git i/lib/dpkg/t/t-varbuf.c w/lib/dpkg/t/t-varbuf.c index 67af373d9..a1b23e16f 100644 --- i/lib/dpkg/t/t-varbuf.c +++ w/lib/dpkg/t/t-varbuf.c @@ -581,8 +581,8 @@ test_varbuf_detach(void) varbuf_init(&vb, 0); varbuf_add_buf(&vb, NULL, 0); test_pass(vb.used == 0); - test_pass(vb.size == 0); - test_pass(vb.buf == NULL); + test_pass(vb.size > 0); + test_pass(vb.buf != NULL); str = varbuf_detach(&vb); test_str(str, ==, ""); test_pass(vb.used == 0); diff --git i/lib/dpkg/varbuf.c w/lib/dpkg/varbuf.c index 2a4434e94..6626d6d5f 100644 --- i/lib/dpkg/varbuf.c +++ w/lib/dpkg/varbuf.c @@ -178,12 +178,11 @@ varbuf_add_char(struct varbuf *v, int c) void varbuf_dup_char(struct varbuf *v, int c, size_t n) { - if (n == 0) - return; - varbuf_grow(v, n + 1); - memset(v->buf + v->used, c, n); - v->used += n; + if (n > 0) { + memset(v->buf + v->used, c, n); + v->used += n; + } v->buf[v->used] = '\0'; } @@ -208,11 +207,11 @@ varbuf_add_dir(struct varbuf *v, const char *dirname) void varbuf_add_buf(struct varbuf *v, const void *s, size_t size) { - if (size == 0) - return; varbuf_grow(v, size + 1); - memcpy(v->buf + v->used, s, size); - v->used += size; + if (size > 0) { + memcpy(v->buf + v->used, s, size); + v->used += size; + } v->buf[v->used] = '\0'; } ----- End forwarded message ----- Thanks, Guillem

