[Debconf-devel] [security] debconf: format module eval injection via debconf database configuration
Jeremy Erazo
mendozayt13 at gmail.com
Fri May 8 00:28:03 BST 2026
Hello Debian Security Team,
I'd like to report an input-validation issue in debconf 1.5.92
(the current version in sid as of 2026-05-07) that I have
runtime-confirmed in a fresh `debian:sid` container.
I am sending privately first per Debian's security policy; if you
determine that no embargo is needed, I'm happy to refile the
hardening fix as a normal BTS bug. I am not requesting a CVE
directly in this initial report; I will leave CVE handling to the
Debian security process.
============================================================
Summary
============================================================
Several Debconf::DbDriver init() functions, and Debconf::Db::makedriver(),
splice an attacker-influenced string directly into a Perl
`eval STRING`:
Debconf/DbDriver/File.pm:75 eval "use Debconf::Format::$this->{format}";
Debconf/DbDriver/Directory.pm:71 eval "use Debconf::Format::$this->{format}";
Debconf/DbDriver/Pipe.pm:66 eval "use Debconf::Format::$this->{format}";
Debconf/Db.pm:70 eval qq{use Debconf::DbDriver::$type};
The interpolated string is the `format:` (or driver `Driver:`) value
parsed out of the debconf database configuration, which can be set
via several env-var and config-file routes (DEBCONF_DB_OVERRIDE /
DEBCONF_DB_FALLBACK / DEBCONF_DB_REPLACE; DEBCONF_SYSTEMRC; DPKG_ROOT;
${VAR} expansion in Config.pm:_hashify). No character-class
validation is performed before the eval, so a value such as
format:822;BEGIN{qx(touch /tmp/dconf_repro)};1
makes the eval text `use Debconf::Format::822;BEGIN{qx(touch
/tmp/dconf_repro)};1`, and the second BEGIN{} runs at compile time
in the perl process.
============================================================
Affected
============================================================
Package : debconf
Version : 1.5.92 (current in sid; upstream tag 1.5.92)
Files : Debconf/DbDriver/File.pm
Debconf/DbDriver/Directory.pm
Debconf/DbDriver/Pipe.pm
Debconf/Db.pm
============================================================
Threat model
============================================================
Confirmed (runtime, debian:sid container, debconf 1.5.92):
Arbitrary code execution in debconf-using processes through
unvalidated format/driver module names. The BEGIN{qx(...)}
side-effect runs as the UID/EUID of the perl process that loads
Debconf::Config.
Local privilege escalation is possible only when attacker-controlled
debconf environment or configuration crosses a privilege boundary
into a privileged perl process -- for example sudo with `setenv` or
`-E` granted by a sudoers entry, an env-preserving wrapper, a
maintainer script invoked with attacker-influenced env, or an
attacker-writable /etc/debconf.conf (or attacker-controlled
DEBCONF_SYSTEMRC / DPKG_ROOT path).
Explicit non-claims:
- This is not a remote vulnerability.
- This does not amount to a privilege escalation on a default
Debian system: with sudo's default env_reset, the trigger env
vars are dropped before the privileged perl runs.
- This is not an `apt` root exploit by default. apt installs are
already root by design; the issue here is the input-validation
hole, not the privilege model of dpkg-preconfigure.
============================================================
Variants runtime-confirmed
============================================================
Nine distinct trigger combinations all reach the same primitive
and were confirmed in a debian:sid container:
A DEBCONF_DB_OVERRIDE -> File.pm:75
B DEBCONF_DB_OVERRIDE -> Directory.pm:71
C DEBCONF_DB_OVERRIDE -> Pipe.pm:66
D DEBCONF_DB_FALLBACK -> File.pm:75
E DEBCONF_DB_REPLACE -> File.pm:75
F DEBCONF_SYSTEMRC=/path with malicious Format:
-> File.pm:75
G DPKG_ROOT=/path with attacker-written /etc/debconf.conf
-> File.pm:75
H DEBCONF_SYSTEMRC + Format: ${EVIL_FORMAT} +
EVIL_FORMAT=822;BEGIN{qx(...)};1
-> Config.pm:31 -> File.pm:75
(the existing ${VAR}
substitution carries the
payload into the parsed
stanza body)
I DEBCONF_DB_OVERRIDE with attacker driver name + custom @INC
-> Db.pm:70
(driver-name eval; needs
PERL5LIB-controlled @INC)
A standalone benign reproducer (creates /tmp/dconf_repro and
nothing else) is available; I am happy to attach it on request.
============================================================
Proposed fix
============================================================
Attached: 0001-debconf-validate-format-and-driver-names-before-eval.patch
Summary of the diff (4 files, ~14 added lines):
- Add `\A[A-Za-z0-9_]+\z` validation on $this->{format} before
the eval in each of the three DbDriver init() functions
(File.pm, Directory.pm, Pipe.pm).
- Add the same validation on $type in Debconf::Db::makedriver()
before `eval qq{use Debconf::DbDriver::$type}`.
- Debconf/Config.pm is intentionally NOT modified.
The ${VAR} substitution path in Config.pm:_hashify remains
documented as a transport for the malicious value, but the patch
blocks it at the eval sinks because the substituted result cannot
pass `\A[A-Za-z0-9_]+\z`. I can prepare a separate hardening
patch for Config.pm (restricting or removing the substitution)
if maintainers prefer.
The patch keeps the legitimate `Format: 822` driver working
(verified post-patch in the sid container), only rejects values
outside [A-Za-z0-9_]+, and adds clear error messages
("Invalid Format value: must match [A-Za-z0-9_]+" / "Invalid
DbDriver name '...': must match [A-Za-z0-9_]+").
Compatibility: a legitimate config file using ${SAFE_NAME} for a
non-Format field (e.g. `Filename: /tmp/${SAFE_NAME}.dat` with
`SAFE_NAME=mydb` in the env) loads identically before and after
the patch -- the substitution is left untouched.
============================================================
Validation
============================================================
In a fresh `debian:sid` container with debconf 1.5.92:
BEFORE patch:
DEBCONF_DB_OVERRIDE='File{filename:/tmp/d format:822;BEGIN{qx(...
-> /tmp/before_v2 created (owned by perl process UID).
AFTER patch (PERL5LIB overlay of patched tree):
same env -> debconf: DbDriver "_ENV_OVERRIDE":
Invalid Format value: must match [A-Za-z0-9_]+
-> /tmp/after_pwn NOT created.
All 9 attacker paths (A through I) blocked, no /tmp/V*.pwn file
created. Legitimate `Format: 822` config still loads OK.
Legitimate `${VAR}` substitution in Config.pm still works.
============================================================
What I'd appreciate guidance on
============================================================
- Whether you'd like an embargo or whether I can file the BTS bug
publicly.
- Whether the Maintainer line on the package
(debconf-devel at lists.alioth.debian.org) is still the right
forwarding address; alioth was retired in 2018 and I'd like to
make sure my Cc actually reaches a human.
- Whether you'd prefer BTS severity `important` (with `security`
tag) vs `grave`; my own read is that "important" matches the
"input-validation hole that becomes LPE only via env/config
crossing a privilege boundary" framing, but I defer to your
judgement.
- Whether Debian Security wants the Config.pm hardening (restrict
or remove the ${VAR} substitution) as a follow-up patch, or
whether sink-side validation alone is considered sufficient.
I have an offline copy of the runtime traces and the variant
matrix; happy to share on request.
Thank you for your time.
Best regards,
Jeremy Erazo
mendozayt13 at gmail.com
============================================================
Inline patch (also attached)
============================================================
[ patch body --- attached as
0001-debconf-validate-format-and-driver-names-before-eval.patch ]
-------------- next part --------------
A non-text attachment was scrubbed...
Name: 0001-debconf-validate-format-and-driver-names-before-eval.patch
Type: text/x-patch
Size: 4293 bytes
Desc: not available
URL: <http://alioth-lists.debian.net/pipermail/debconf-devel/attachments/20260507/e0e83b3d/attachment-0001.bin>
-------------- next part --------------
debconf format-module-name eval injection — runtime validation summary (v2)
============================================================================
Patch under test: 0001-debconf-validate-format-and-driver-names-before-eval.patch
v2 differs from v1 by NOT modifying Debconf/Config.pm — the eval sinks alone
are validated. This avoids any compatibility discussion around the
${VAR} substitution feature in stanza bodies.
Environment
-----------
- Image: docker.io/library/debian:sid (pulled 2026-05-07)
- Package: debconf
- Version: 1.5.92
- Maintainer (per apt-cache show): Debconf Developers
<debconf-devel at lists.alioth.debian.org>
- Source: apt source debconf -> debconf_1.5.92.dsc / debconf_1.5.92.tar.xz
- Perl: distro default (5.x)
Sinks identified in fresh source
--------------------------------
$ grep -n 'eval "use Debconf::Format' Debconf/DbDriver/{File,Directory,Pipe}.pm
Debconf/DbDriver/File.pm:75: eval "use Debconf::Format::$this->{format}";
Debconf/DbDriver/Directory.pm:71: eval "use Debconf::Format::$this->{format}";
Debconf/DbDriver/Pipe.pm:66: eval "use Debconf::Format::$this->{format}";
$ grep -n 'eval qq{use Debconf::DbDriver' Debconf/Db.pm
Debconf/Db.pm:70: eval qq{use Debconf::DbDriver::$type};
Patch dry-run + apply (clean, 4 files)
--------------------------------------
$ patch -p1 --dry-run < 0001-debconf-validate-format-and-driver-names-before-eval.patch
checking file Debconf/DbDriver/Directory.pm
checking file Debconf/DbDriver/File.pm
checking file Debconf/DbDriver/Pipe.pm
checking file Debconf/Db.pm
$ patch -p1 < 0001-debconf-validate-format-and-driver-names-before-eval.patch
patching file Debconf/DbDriver/Directory.pm
patching file Debconf/DbDriver/File.pm
patching file Debconf/DbDriver/Pipe.pm
patching file Debconf/Db.pm
Files NOT modified by v2:
Debconf/Config.pm (intentionally untouched; the ${VAR} expansion
is left in place to preserve compatibility for
legitimate uses such as ${HOME}, ${USER}, etc.)
perl -c on modified files
-------------------------
Debconf/DbDriver/File.pm syntax OK
Debconf/DbDriver/Directory.pm syntax OK
Debconf/DbDriver/Pipe.pm syntax OK
Debconf/Db.pm syntax OK
Debconf/Config.pm syntax OK (untouched, sanity)
BEFORE patch — bug confirmed reachable in fresh sid
---------------------------------------------------
$ DEBCONF_DB_OVERRIDE='File{filename:/tmp/d format:822;BEGIN{qx(touch\x20/tmp/before_v2)};1}' \
perl -MDebconf::Config -e 'Debconf::Config->load'
Can't locate object method "new" via package
"Debconf::Format::822;BEGIN{qx(touch\\x20/tmp/before_v2)};1"
... at /usr/share/perl5/Debconf/DbDriver/File.pm line 36.
$ ls -la /tmp/before_v2
-rw-r--r-- 1 root root 0 May 7 16:38 /tmp/before_v2
[BEFORE] PoC SUCCEEDED — bug confirmed in 1.5.92.
AFTER patch — re-run all 9 attacker paths with PERL5LIB=/tmp/patchroot
----------------------------------------------------------------------
A: DEBCONF_DB_OVERRIDE + File -> blocked: "Invalid Format value: must match [A-Za-z0-9_]+"
B: DEBCONF_DB_OVERRIDE + Directory -> blocked
C: DEBCONF_DB_OVERRIDE + Pipe -> blocked
D: DEBCONF_DB_FALLBACK + File -> blocked
E: DEBCONF_DB_REPLACE + File -> blocked
F: DEBCONF_SYSTEMRC=/tmp/sysrc_F -> blocked
(config-file with malicious Format:)
G: DPKG_ROOT=/tmp/myroot -> blocked
(attacker-written /tmp/myroot/etc/debconf.conf)
H: DEBCONF_SYSTEMRC + Format:${EVIL_FORMAT}
+ EVIL_FORMAT=822;BEGIN{qx(...)};1
-> blocked at sink validation
(the ${VAR} substitution in Config.pm:31 still happens, but the
substituted value cannot pass the per-driver
\A[A-Za-z0-9_]+\z gate)
I: Db.pm driver-name eval chain -> blocked: "Invalid DbDriver name
'...': must match
[A-Za-z0-9_]+"
For all of A-I: NO file under /tmp/V*.pwn was created.
Legitimate Format: 822 still loads
----------------------------------
$ PERL5LIB=/tmp/patchroot perl -MDebconf::Config -e '
$ENV{DEBCONF_DB_OVERRIDE} =
"File{filename:/tmp/legit/db format:822}";
Debconf::Config->load;
print "[legit File+822] loaded OK\n";'
[legit File+822] loaded OK
Compatibility — Config.pm ${VAR} substitution still functional
--------------------------------------------------------------
A legitimate config file using ${SAFE_NAME} for a non-Format field:
Name: configdb
Driver: File
Filename: /tmp/${SAFE_NAME}.dat
Format: 822
with `SAFE_NAME=mydb` in the env loads correctly:
$ SAFE_NAME=mydb DEBCONF_SYSTEMRC=/tmp/sysrc_compat PERL5LIB=/tmp/patchroot \
perl -MDebconf::Db -MDebconf::Config -e '...'
[compat] config driver: Debconf::DbDriver::File filename=/tmp/mydb.dat
[compat] OK substitution preserved
This proves v2 does NOT break legitimate ${VAR} usage.
Result
------
- Bug runtime-confirmed in debconf 1.5.92 (fresh sid) before patch.
- All 9 attacker paths neutralised after patch (4 files modified).
- Legitimate "822" Format still loads.
- Legitimate ${VAR} substitution in Config.pm still works.
- No PoC artefact larger than `touch /tmp/<file>` was used.
-------------- next part --------------
debconf format-module-name eval injection — runtime variant matrix (v2)
========================================================================
Eight original transport routes plus the Db.pm driver-name eval all
reach the same primitive (eval STRING with attacker-influenced module
name). All were observed creating a file as the perl process UID via
the BEGIN{qx(...)} side-effect of the malicious module-name string.
Source bug — the eval that interpolates an attacker string:
Debconf/DbDriver/File.pm:75 eval "use Debconf::Format::$this->{format}";
Debconf/DbDriver/Directory.pm:71 eval "use Debconf::Format::$this->{format}";
Debconf/DbDriver/Pipe.pm:66 eval "use Debconf::Format::$this->{format}";
Debconf/Db.pm:70 eval qq{use Debconf::DbDriver::$type};
How the attacker string reaches each sink:
Env vars routed via Debconf::Config::_env_to_driver (Config.pm:62-79):
DEBCONF_DB_OVERRIDE -> finalstack[0] (drives the *first* driver)
DEBCONF_DB_FALLBACK -> finalstack[-1] (drives the fallback)
DEBCONF_DB_REPLACE -> $config->{config}
Config-file path routes:
DEBCONF_SYSTEMRC -> @config_files[0] (Config.pm:20-21);
attacker-controlled file with a Database
stanza whose Format: is malicious.
DPKG_ROOT -> path prefix (Config.pm:80);
prepends to the system config-file lookup
so /tmp/myroot/etc/debconf.conf is read
instead of /etc/debconf.conf.
Body-of-stanza route:
${VAR} substitution -> Config.pm:31; the parser substitutes any
${X} in the stanza body with $ENV{X}, so
one env var named in the file becomes the
malicious Format: value.
v2 does NOT remove this substitution; the
substituted value is rejected at the eval
sink because it cannot pass
\A[A-Za-z0-9_]+\z.
Driver-name eval:
Db.pm:70 -> attacker-controlled DbDriver type name
feeds eval qq{use Debconf::DbDriver::$type}.
Variants (all confirmed reachable in Debian sid container, debconf 1.5.92,
and all blocked by v2 patch):
Variant Trigger Sink Result
---------------------------------------------------------------------------
A DEBCONF_DB_OVERRIDE File.pm:75 blocked
B DEBCONF_DB_OVERRIDE Directory.pm:71 blocked
C DEBCONF_DB_OVERRIDE Pipe.pm:66 blocked
D DEBCONF_DB_FALLBACK File.pm:75 blocked
E DEBCONF_DB_REPLACE File.pm:75 blocked
F DEBCONF_SYSTEMRC=/tmp/evil.conf
with malicious Format: File.pm:75 blocked
G DPKG_ROOT=/tmp/myroot +
/tmp/myroot/etc/debconf.conf
with malicious Format: File.pm:75 blocked
H DEBCONF_SYSTEMRC + Format:
${EVIL_FORMAT} +
EVIL_FORMAT=822;BEGIN{...} Config.pm:31->75 blocked
(substitution carries
the payload, but the
sink-side validation
rejects it)
I DEBCONF_DB_OVERRIDE with
attacker driver name
+ custom @INC Db.pm:70 blocked
PoC payload (kept benign):
format:822;BEGIN{qx(touch\x20/tmp/<name>)};1
Patch behaviour against the matrix:
Variants A, B, C, D, E, F, G, H:
caught at the per-driver `\A[A-Za-z0-9_]+\z` validation that
precedes the eval at File.pm:75 / Directory.pm:71 / Pipe.pm:66.
Variant H still has the substitution active in Config.pm but the
substituted result fails the sink-side regex.
Variant I:
caught at the same regex on $type in Db.pm before
`eval qq{use Debconf::DbDriver::$type}`.
Threat model — single-line statement:
Arbitrary code execution in debconf-using processes through unvalidated
format/driver module names. Local privilege escalation is possible
when attacker-controlled debconf environment/configuration is
preserved across a privilege boundary (e.g. sudo with `setenv` or `-E`,
maintainer scripts running with attacker-influenced env, or any
privileged wrapper that does not reset the environment).
More information about the Debconf-devel
mailing list