Package: mmdebstrap
Version: 1.5.7-3
Severity: normal
Tags: upstream fixed-upstream
X-Debbugs-Cc: [email protected]

Hi Josch,

During use of Debusine, I was faced with a very strange piuparts error.  
The relevant workrequest sadly is private, but the gist is that debsums 
was complaining about a checksum error for start-stop-daemon. After a 
while, I investigated the base image passed to piuparts and ran dpkg 
--verify there. It was also unhappy about start-stop-daemon. This image 
was created using mmdebstrap and this is where we get into the problem I 
am trying to report.

The distribution at hand was Ubuntu xenial. It is a bit special in that 
it presently has a dpkg security update. What happened here is roughly 
this:
 * All essential packages are extracted (without the security update).
 * mmdebstrap replaces start-stop-daemon.
 * Remaining packages are installed and this includes the dpkg security 
   update.
 * mmdebstrap moves back the replaced start-stop-daemon.

A relatively simple way to reproduce this is simulating a dist-upgrade.

mmdebstrap --variant=apt bookworm /tmp/upgraded.tar 
--chrooted-customize-hook='sed -i -e s/bookworm/trixie/ /etc/apt/sources.list 
&& apt-get update && apt-get dist-upgrade'

The problem happens after the customization stage, so we actually need 
to emit some image and run dpkg --verify inside to see the problem.

Beyond this, I don't think the replacement of start-stop-daemon ever 
worked as intended, because start-stop-daemon is being replaced before 
the essential packages unpacked, so dpkg overwrites it.

| mmdebstrap --variant=essential sid /dev/null --chrooted-customize-hook='ls 
-la /sbin/start-stop-daemon*'
...
| I: running --chrooted-customize-hook in shell: sh -c 'ls -la 
/sbin/start-stop-daemon*'
| -rwxr-xr-x 1 root root 44464 Jun 30 23:32 /sbin/start-stop-daemon
| -rwxr-xr-x 1 root root 44464 Jun 30 23:32 /sbin/start-stop-daemon.REAL

As far as I can see, the proper solution here is using dpkg-divert and 
creating local diversions, no?

Of course this code is cargo culted from debootstrap and likely exists 
to maximize compatibility with it. deboostrap cannot easily use 
diversions, so there is that.

Replacing start-stop-daemon is also something we haven't needed in a 
long time. Since moving to systemd, we aren't using start-stop-daemon 
much at all and even before, sysvinit was already using invoke-rc.d and 
policy-rc.d for a long time. Is this code actually needed still?

My impression is that you would like to retain compatibility with very 
old releases where this is still needed and diversions should work 
there.

We've discussed this on IRC now and you've already applied the attached
patch to your develop upstream branch despite it failing the salsa-ci
pipeline for bsdutils having become non-essential. Thank you. I'm
attaching a copy for reference.

Helmut
>From de180f97e354fdda3ead4657eb82d8ce611da485 Mon Sep 17 00:00:00 2001
From: Helmut Grohne <[email protected]>
Date: Sat, 22 Nov 2025 08:09:37 +0100
Subject: [PATCH] Divert start-stop-daemon

If we just replace it with something else, a later unpack operation of
dpkg may overwrite our replacement. Our cleanup may then destroy
intended changes.
---
 mmdebstrap | 59 +++++++++++++++++++++++++++++++++++++++++-------------
 1 file changed, 45 insertions(+), 14 deletions(-)

diff --git a/mmdebstrap b/mmdebstrap
index 075582e..8fe5874 100755
--- a/mmdebstrap
+++ b/mmdebstrap
@@ -2601,34 +2601,64 @@ sub setup_mounts {
         # the file might not exist if it was removed in a hook
         if (any { $_ eq 'chroot/start-stop-daemon' } @{ $options->{skip} }) {
             info "skipping chroot/start-stop-daemon as requested";
+        } elsif ($options->{dryrun}) {
+            info "skipping start-stop-daemon because of --dryrun";
         } else {
             # $options->{root} must not be part of $ssdloc but must instead be
             # evaluated at the time the cleanup is run or otherwise, when
             # performing a pivot-root, the ssd location will still be prefixed
             # with the chroot path even though we changed root
+            #
+            # On merged-/usr, it can exist in both places. We still want the
+            # location according to the dpkg-database. At this time, neither
+            # usrmerge.postinst nor the merged-usr hook's post-merging
+            # extract hook have been run, so /sbin can only be a link if it is
+            # a file in base-files at that point, ssd must be below /usr.
             my $ssdloc;
-            if (-f "$options->{root}/sbin/start-stop-daemon") {
+            if (-f "$options->{root}/sbin/start-stop-daemon"
+                && !-l "$options->{root}/sbin") {
                 $ssdloc = "/sbin/start-stop-daemon";
             } elsif (-f "$options->{root}/usr/sbin/start-stop-daemon") {
                 $ssdloc = "/usr/sbin/start-stop-daemon";
             }
+            my @ssd_diversions = ([
+                    "/usr/sbin/start-stop-daemon",
+                    "/usr/sbin/start-stop-daemon.REAL"
+                ],
+                [
+                    "/sbin/start-stop-daemon",
+                    "/sbin/start-stop-daemon.REAL.usr-is-merged",
+                ],
+            );
+            if ($ssdloc eq "/sbin/start-stop-daemon") {
+                @ssd_diversions = reverse @ssd_diversions;
+            }
+            for my $pair (@ssd_diversions) {
+                my ($diverted, $divertto) = @$pair;
+                0 == system(
+                    "dpkg-divert", "--root",   $options->{root},
+                    "--local",     "--rename", "--divert",
+                    $divertto,     "--add",    $diverted
+                ) or die "failed to divert $diverted: $?";
+            }
+            # Remove diversions in reverse order;
+            @ssd_diversions = reverse @ssd_diversions;
+
             push @cleanup_tasks, sub {
-                return unless length $ssdloc;
-                if (-e "$options->{root}/$ssdloc.REAL") {
-                    move(
-                        "$options->{root}/$ssdloc.REAL",
-                        "$options->{root}/$ssdloc"
-                    ) or error "cannot move start-stop-daemon: $!";
+                if (length $ssdloc and -e "$options->{root}/$ssdloc") {
+                    unlink "$options->{root}/$ssdloc"
+                      or error "cannot remove start-stop-daemon: $!";
+                }
+                for my $pair (@ssd_diversions) {
+                    my ($diverted, $divertto) = @$pair;
+                    0 == system(
+                        "dpkg-divert", "--root",   $options->{root},
+                        "--local",     "--rename", "--divert",
+                        $divertto,     "--remove", $diverted
+                    ) or die "failed to divert $diverted: $?";
                 }
             };
             if (length $ssdloc) {
-                if (-e "$options->{root}/$ssdloc.REAL") {
-                    error "$options->{root}/$ssdloc.REAL already exists";
-                }
-                move(
-                    "$options->{root}/$ssdloc",
-                    "$options->{root}/$ssdloc.REAL"
-                ) or error "cannot move start-stop-daemon: $!";
                 open my $fh, '>', "$options->{root}/$ssdloc"
                   or error "cannot open start-stop-daemon: $!";
                 print $fh "#!/bin/sh\n";
-- 
2.50.1

Reply via email to