From 98c623c8d6814ae46a3b30ca22e584c77d47d86b Mon Sep 17 00:00:00 2001
From: Guillem Jover <guillem@debian.org>
Date: Sat, 7 Jun 2025 14:17:07 +0200
Subject: [PATCH] dpkg-deb: Fix cleanup for control member with restricted
 directories
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

When extracting a control member into a temporary directory, which is
documented as being a safe operation even on untrusted data, the code
in charge of the temporary directory cleanup does not sanitize the
directory permissions, which is then unable to perform the «rm -rf»
when running as a non-root user, leaving temporary files behind.

Given automated and repeated execution of dpkg-deb commands on
adversarial .deb packages or with well compressible files, placed
inside a directory with permissions not allowing removal by a non-root
user, this can end up with a DoS scenario due to causing disk quota
exhaustion or disk full conditions.

This is considered a minor issue, given the required conditions to
trigger a problem with it, but an issue non the less given the
documented security guarantees of the command. This has been an
issue since the initial commit introducing dpkg-deb in C.

We use an existing string for the error message to avoid new strings
needing translation for stable branches, which make the error message
less descriptive than what would be ideal. This will be improved in
git HEAD.

Reported-by: zhutyra on HackerOne
Fixes: CVE-2025-6297
Stable-Candidate: 1.20.x 1.21.x 1.22.x
(cherry picked from commit ed6bbd445dd8800308c67236ba35d08004c98e82)
(cherry picked from commit 02ad0532bd490cbc95b344f670e622a38eecfbf6)
(cherry picked from commit d8a76551e22abe76eefd7fef5c7f51f4118eb40e)

CVE: CVE-2025-6297
Upstream-Status: Backport [https://git.dpkg.org/cgit/dpkg/dpkg.git/commit/?id=98c623c8d6814ae46a3b30ca22e584c77d47d86b]
Signed-off-by: Peter Marko <peter.marko@siemens.com>
---
 src/at/deb-content.at | 32 ++++++++++++++++++++++++++++++++
 src/deb/info.c        | 20 ++++++++++++++++++++
 2 files changed, 52 insertions(+)

diff --git a/src/at/deb-content.at b/src/at/deb-content.at
index a192c9493..d48eed72b 100644
--- a/src/at/deb-content.at
+++ b/src/at/deb-content.at
@@ -127,3 +127,35 @@ newline'
 ])
 
 AT_CLEANUP
+
+AT_SETUP([dpkg-deb .deb extraction cleanup])
+AT_KEYWORDS([dpkg-deb deb extraction])
+
+DPKG_GEN_CONTROL([pkg-ctrl-dir-perms])
+AT_CHECK([
+dpkg-deb --root-owner-group -Znone -b pkg-ctrl-dir-perms
+DPKG_AR_EXTRACT([pkg-ctrl-dir-perms.deb])
+dpkg-deb -R pkg-ctrl-dir-perms.deb pkg-ctrl-dir-perms-bad
+mkdir -p pkg-ctrl-dir-perms-bad/DEBIAN/rx-subdir/inner
+touch pkg-ctrl-dir-perms-bad/DEBIAN/rx-subdir/inner/file
+chmod 0555 pkg-ctrl-dir-perms-bad/DEBIAN
+chmod 0555 pkg-ctrl-dir-perms-bad/DEBIAN/rx-subdir
+chmod 0555 pkg-ctrl-dir-perms-bad/DEBIAN/rx-subdir/inner
+$TAR cf control.tar --format=gnu --sort=name --mtime @0 --clamp-mtime --owner root:0 --group root:0 -C pkg-ctrl-dir-perms-bad/DEBIAN .
+DPKG_AR_GEN([pkg-ctrl-dir-perms.deb], [debian-binary control.tar data.tar])
+], [0], [dpkg-deb: building package 'pkg-ctrl-dir-perms' in 'pkg-ctrl-dir-perms.deb'.
+])
+AT_CHECK([
+dpkg-deb --ctrl-tarfile pkg-ctrl-dir-perms.deb | $TAR tvf -
+], [0], [dr-xr-xr-x root/root         0 1970-01-01 00:00 ./
+-rw-r--r-- root/root       176 1970-01-01 00:00 ./control
+dr-xr-xr-x root/root         0 1970-01-01 00:00 ./rx-subdir/
+dr-xr-xr-x root/root         0 1970-01-01 00:00 ./rx-subdir/inner/
+-rw-r--r-- root/root         0 1970-01-01 00:00 ./rx-subdir/inner/file
+])
+# Check that we can cleanup the temporarily extracted control.tar member.
+AT_CHECK([
+dpkg-deb -I pkg-ctrl-dir-perms.deb
+], [0], [ignore])
+
+AT_CLEANUP
diff --git a/src/deb/info.c b/src/deb/info.c
index f3d57e2ce..396ea4d14 100644
--- a/src/deb/info.c
+++ b/src/deb/info.c
@@ -45,14 +45,34 @@
 #include <dpkg/pkg-format.h>
 #include <dpkg/buffer.h>
 #include <dpkg/path.h>
+#include <dpkg/treewalk.h>
 #include <dpkg/options.h>
 
 #include "dpkg-deb.h"
 
+static int
+cu_info_treewalk_fixup_dir(struct treenode *node)
+{
+  const char *nodename;
+
+  if (!S_ISDIR(treenode_get_mode(node)))
+    return 0;
+
+  nodename = treenode_get_pathname(node);
+  if (chmod(nodename, 0755) < 0)
+    ohshite(_("error setting permissions of '%.255s'"), nodename);
+
+  return 0;
+}
+
 static void cu_info_prepare(int argc, void **argv) {
   char *dir;
+  struct treewalk_funcs cu_info_treewalk_funcs = {
+    .visit = cu_info_treewalk_fixup_dir,
+  };
 
   dir = argv[0];
+  treewalk(dir, TREEWALK_NONE, &cu_info_treewalk_funcs);
   path_remove_tree(dir);
   free(dir);
 }
