Package: release.debian.org
Severity: normal
X-Debbugs-Cc: autopkgt...@packages.debian.org, Debian CI team 
<team...@tracker.debian.org>
Control: affects -1 + src:autopkgtest
User: release.debian....@packages.debian.org
Usertags: unblock

Hi.  (I'm not sure "unblock" is the right request type.)


unblock autopkgtest/5.49


Summary:

Please would you arrange that src:autopkgtest 5.49 will migrate
(when the 10 days are up) despite the apparent regression in the
autopkgtest for src:surf?

I have looked at the logs for the test, and I am confident that the
apparent regression is not due to any change in the autopkgtest
package.  It looks like the tests are flaky (and the surf package is
also possibly just broken).

I hope it's right for me to file this request now rather than waiting
for the 10 days to be up.


Analysing the "regression" in more detail:

Firstly, note that two versions of autopkgtest are involved in this
scenario, in totally different roles.  One is the autopkgtest that's
used as a test runner in ci.debian.net.  That autopkgtest is *not
being updated* here.  Presumably it will be updated to 5.49 in due
course if 5.49 updates to testing.  The other is the autopkgtest from
sid, the migration candidate, which is pulled in as a dependency
because it's listed in the dependencies of one of surf's test cases.
It is this latter autopkgtest which is impugned by the "regression".

The surf package has three test cases, which I will call "command1",
"command2" and "command3", in the order from d/t/control, as ci.d.n's
autopkgtest calls thme.

"command1" passes, is marked superficial, and is a simple `cmp`.

"command3" Depends on autopkgtest.  This is because it uses
autopkgtest's README.package-tests as test data.  The test fails with
an error message which seems to indicate that surf, or webkit, or
webdriver, or something, didn't start up at all.  The test is marked
flaky, so this is not classed as a "regression".

"command2" is *not* marked flaky, and fails in the britney-requested
migration runs.  But it does *not* Depend on autopkgtest.  Indeed,
looking at the test logs, autopkgtest isn't even installed!  The error
message is similar to the failure from "command3".

Or to put it another way, if it weren't for the flaky "command3" test
case, nothing would even have worried that a new autopkgtest might
break surf - the test wouldn't have been triggered.

The "regression" appears only on riscv64.  All of the changes in
autopkgtest are arch-neutral.

I have grepped the surf source code and it doesn't invoke autopkgtest.
This is discussed some more in the bug report where Paul Gevers asked
for the "command3" test to be marked flaky:

  https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1055639

I did ask ci.d.n to retry the baseline, and to retry the failed test,
but this didn't change the results.  Even so, it seems impossible 
that this failure can be blamed on the new autopkgtest.

I filed a bug about the "command2" test being flaky too
  https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1103356
There hasn't been a response from the maintainer yet.  Outside the
freeze, this would eventually result in surf being removed from
testing, but that will come too slowly now.

Additionally, there is another bug against surf saying that it doesn't
work at all:
  https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1054150

I think there is probably something inherently flaky inside surf :-/.

So, in summary: it seems clear that this "regression" is not real.


What is in autopkgtest 5.49

The autopkgtest that is currently in testing, 5.47, has (like most
previous versions) some serious security problems.  It is only
suitable for use with highly-isolating virtualisation environments.

This is mostly because it makes a number of important directories
world-writeable.  It also plays fast and loose with ownership and
user-vs-root, and the autopkgtest-virt-null backend (which does not
offer any isolation whatsoever) falsely advertises
isolation-container.

These things are all fixed in 5.48, with a NEWS entry added in 5.49.

I think it is important to get this security update into trixie.

There is some risk that the changes will break something.  The design
intent is to avoid operational breakage in existing containerised or
virtualised deployments - the code looks for the isolation
capabilities, and uses the world-writeable permissions and a relaxed
attitude, when those capabilities are present.

This is tested in the extensive test suite - the salsa CI tests are
quite comprehensive.  There are autopkgtests too.

So I'm hoping that there won't be any significant fallout.
Nevertheless, if there is, I think we want to detect it early.

The alternatives to migrating this to trixie now are to delay these
critical security fixes for another ~two years, or to try to do them
as a stable release update or security update.  Both of these are
quite unpalatable.

I say "critical security fixes", because right now anyone who runs
autopkgtest from their own normal development environment, and is
using anything other than full container-based virtualisation, is
exposing their main account to all other uids on their system (for
example, uids used for privilege separation, or maybe, container uid
namespaces).


There is additionally one small bugfix, to install a missing file.

The changes in question are in the changelog in detail, and summarised
in NEWS.Debian.


Thanks for your attention,
Ian.


diff --git a/Makefile b/Makefile
index 13ef15b6..ba9ab010 100644
--- a/Makefile
+++ b/Makefile
@@ -97,6 +97,7 @@ install:
        $(INSTALL_DATA) $(rstfiles) $(htmlfiles) $(docdir)
        $(INSTALL_PROG) lib/*.sh $(datadir)/lib/
        $(INSTALL_PROG) lib/in-testbed/*.sh $(datadir)/lib/in-testbed/
+       $(INSTALL_PROG) lib/arch-is-concerned.pl $(datadir)/lib/
        $(INSTALL_PROG) lib/parse-deps.pl $(datadir)/lib/
        $(INSTALL_PROG) lib/unshare-helper $(datadir)/lib/
        $(INSTALL_PROG) setup-commands/*[!~] $(datadir)/setup-commands
diff --git a/debian/NEWS b/debian/NEWS
index 2fe8d5ed..920b4a37 100644
--- a/debian/NEWS
+++ b/debian/NEWS
@@ -1,3 +1,25 @@
+autopkgtest (5.48) unstable; urgency=medium
+
+  Security fixes involving many permissiosn changes:
+  * Several directories are nowworld-writeable only when used with
+    suitably isolating virtualisation servers.  Previously they were
+    world-writeable in circumstances where this wasn't safe.
+  * autopkgtest-virt-null no longer advertises isolation-machine.
+    This was a dangerous lie.  For environments where the caller provides
+    isolation, there's a new --fake-capability option to restore the
+    previous behaviour.
+  * autopkgtest-virt-unshare no longer offers the `downtmp-host`
+    capability, since it is not safe for host processes to directly copy
+    to and from the testbed which has a different security context.
+  * There's a new --insecure option to autopkgtest which enables some old,
+    risky, behaviours, in some circumstances.
+  * autopkgtest-virt-ssh should not be provided with a way to get root on
+    the testbed, unless the whole testbed is fully isolated from any
+    untrusted code.  This security restriction was alwsays implied by the
+    behaviour, but has now been documented.
+
+ -- Ian Jackson <ijack...@chiark.greenend.org.uk>  Wed, 16 Apr 2025 19:38:57 
+0100
+
 autopkgtest (5.0) unstable; urgency=medium
 
   * autopkgtest 4.0 with its "autopkgtest" program has been around for a
diff --git a/debian/changelog b/debian/changelog
index 05f00b86..5a500fbc 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,33 @@
+autopkgtest (5.49) unstable; urgency=medium
+
+  * NEWS.Debian: Document the security fixes in 5.48.
+
+ -- Ian Jackson <ijack...@chiark.greenend.org.uk>  Wed, 16 Apr 2025 19:41:19 
+0100
+
+autopkgtest (5.48) unstable; urgency=medium
+
+  [ Ural Tunaboyu ]
+  * Makefile: install arch-is-concerned.pl.  Fixes testing on very
+    old distros with very old python3-debian.
+
+  [ Ian Jackson ]
+  * SECURITY: autopkgtest-virt-*: Don't make downtmp dangerously 
world-writeable
+  * SECURITY: Fix mode (permissions) of AUTOPKGTEST_ARTIFACTS
+  * SECURITY: autopkgtest-virt-null: Do not advertise isolation-machine
+  * SECURITY: Fix autopkgtest-virt-unshare to not advertise downtmp-host
+  * SECURITY: Only chmod copied-down things world-writeable with --insecure
+  * autopkgtest-virt-null: Add --fake-capabilities option
+  * Clarify that mangling config in $HOME is breaks-testbed
+  * No suggested-normal-user without root-on-testbed
+  * If necessary, chown the scratchspace to the normal user
+  * Introduce autopkgtest --insecure option
+  * d/t/control: Declare isolation-container for tests/autopkgtest
+  * Centralise downtmp and capabilities
+  * autopkgtest-virt-qemu: Move the base caps list to a global
+  * Make downtmp-host= cap filtering more careful
+
+ -- Ian Jackson <ijack...@chiark.greenend.org.uk>  Mon, 14 Apr 2025 00:31:53 
+0100
+
 autopkgtest (5.47) unstable; urgency=medium
 
   [ Ian Jackson ]
diff --git a/debian/tests/control b/debian/tests/control
index 0c02766f..08703f75 100644
--- a/debian/tests/control
+++ b/debian/tests/control
@@ -9,6 +9,7 @@ Depends:
  fakeroot,
  python3-cap-ng,
 Restrictions:
+ isolation-container,
  needs-sudo,
 
 Tests:
diff --git a/doc/README.package-tests.rst b/doc/README.package-tests.rst
index d71cd27e..7575f93f 100644
--- a/doc/README.package-tests.rst
+++ b/doc/README.package-tests.rst
@@ -42,6 +42,9 @@ there is no need for the test to clean up files left there).
 
 Tests can expect that the ``$HOME`` environment variable to be set
 to a directory that exists and is writeable by the user running the test.
+Tests should not make configuration changes in ``$HOME``, or other
+destructive changes, unless they declare ``breaks-testbed``,
+``isolation-machine``, or ``isolation-container``.
 
 If tests want to create artifacts which are useful to attach to test
 results, such as additional log files or screenshots, they can put them
@@ -49,6 +52,10 @@ into the directory specified by the 
``$AUTOPKGTEST_ARTIFACTS``
 environment variable. When using the ``--output-dir`` option, they will
 be copied into ``outputdir/artifacts/``.
 
+If the test case declares the ``isolation-machine`` or
+``isolation-container`` restriction, ``$AUTOPKGTEST_ARTIFACTS`` will
+be world-writeable.
+
 Since autopkgtest version 5.43, the architecture of the packages
 that are being tested is made available in the test environment via the
 ``AUTOPKGTEST_TEST_ARCH`` environment variable, while the testbed architecture
@@ -272,7 +279,7 @@ isolation-container
     The test wants to start services or open network TCP ports. This
     commonly fails in a simple chroot/schroot, so tests need to be run
     in their own container (e. g. autopkgtest-virt-lxc) or their own
-    machine/VM (e. g. autopkgtest-virt-qemu or autopkgtest-virt-null).
+    machine/VM (e. g. autopkgtest-virt-qemu).
     When running the test in a virtualization server which does not
     provide this (like autopkgtest-schroot) it will be skipped.
 
@@ -301,7 +308,7 @@ isolation-machine
     The test wants to interact with the kernel, reboot the machine, or
     other things which fail in a simple schroot and even a container.
     Those tests need to be run in their own machine/VM (e. g.
-    autopkgtest-virt-qemu or autopkgtest-virt-null). When running the
+    autopkgtest-virt-qemu). When running the
     test in a virtualization server which does not provide this it will
     be skipped.
 
diff --git a/doc/README.virtualisation-server.rst 
b/doc/README.virtualisation-server.rst
index 9e427bb8..ab571700 100644
--- a/doc/README.virtualisation-server.rst
+++ b/doc/README.virtualisation-server.rst
@@ -100,6 +100,14 @@ suggested-normal-user=\ *username*
     names) may be advertised in which case more than one such user is
     available.
 
+    In testbeds without ``isolation-machine`` or
+    ``isolation-container``, the the testbed promises that this user
+    does not run any untrusted code.  So it is OK for the root user
+    inside the testbed to complete trust this user (by, for example,
+    running code in files owned by this user).
+
+    This is only meaningful if ``root-on-testbed`` is also advertised.
+
 Command: open
 -------------
 
@@ -117,6 +125,22 @@ uses of the testbed to finish first, if necessary).
 freely by the test scripts and which is valid until the next ``close``,
 ``revert``, or ``quit``.
 
+In testbeds with ``isolation-machine`` or ``isolation-container``
+capability, the scratchspace directory is world-writeable.
+(This must be taken into account if the caller intends to impose
+security boundaries within the testbed; but, such a situation would be
+highly unusual for tests.)
+
+In testbeds without such isolation, the directory is mode 755,
+and owned by the testbed user; therefore, with the ``root-on-testbed``
+capability, it is not writeable by the ``suggested-normal-user``.
+If desired, the caller should use ``chown`` to change the ownership,
+or create a suitable subdirectory.
+
+(In autopkgtest 5.47 and earlier this directory was always mode 1777,
+like ``/tmp``.  This was a security vulnerability in non-isolating
+virt environments, and overly restrictive in isolating ones.)
+
 Command: revert
 ---------------
 
diff --git a/lib/VirtSubproc.py b/lib/VirtSubproc.py
index 1cc7ebd6..5fe5145a 100644
--- a/lib/VirtSubproc.py
+++ b/lib/VirtSubproc.py
@@ -359,27 +359,63 @@ def cmd_open(c, ce):
     return [downtmp]
 
 
-def downtmp_mktemp(path):
+def downtmp_mktemp(capabilities, path, downtmp_prefix):
     '''Generate a downtmp
 
     When a path is given, this is the downtmp that we created when opening the
     testbed the first time. We always want to keep the same path between
     resets, as built package trees sometimes refer to absolute paths and thus
     fail if they get moved around.
+
+    capabilities will be used to determine the appropriate permissions.
+
+    If downtmp_prefix is not None, updates capabilities to add an downtmp-host,
+    consisting of downtmp_prefix + the downtmp path.
     '''
+
+    # The caller is entitled to use the scratch directory normally, without 
taking
+    # the kind of extreme special care needed for a world-writeable directory.
+    if any(cap in ['isolation-container', 'isolation-machine'] for cap in 
capabilities):
+        # The testbed is fully isolated by the host, so the only things running
+        # in the testbed are part of the testbed or specified by the caller.
+        # So there is no untrusted code.
+        #
+        # Make the scratch directory world-writeable is convenient for use 
cases
+        # that have to deal with multiple users.
+        #
+        # If the downtmp is visible to the host, then the isolation is not 
enough.
+        # This assertion is a double-check, therefore, that we aren't exposing
+        # the caller's code (which is using the downtmp) to possible hostile 
actions.
+        assert downtmp_prefix is None
+        mode = '777'
+    else:
+        # Untrusted code might be running in the testbed - so we must protect 
it.
+        mode = '755'
+
     if path:
-        check_exec(['/bin/mkdir', '--mode=1777', '--parents', downtmp_open],
+        check_exec(['/bin/mkdir', f"--mode={mode}", '--parents', downtmp_open],
                    downp=True)
-        return path
+        d = path
     else:
         d = check_exec(['/bin/mktemp', '--directory', '--tmpdir', 
'autopkgtest.XXXXXX'],
                        downp=True, outp=True)
-        check_exec(['/bin/chmod', '1777', d], downp=True)
-        return d
+        check_exec(['/bin/chmod', mode, d], downp=True)
+    if downtmp_prefix is not None:
+        capabilities.append('downtmp-host=' + downtmp_prefix + d)
+    return d
+
 
+def downtmp_remove(capabilities):
+    '''Removes the downtmp
 
-def downtmp_remove():
+    Also removes any downtmp-host entry from capabilities.
+    '''
     global downtmp, auxverb
+
+    # Remove from capabilities first.  Some call sites catch exceptions and 
continue,
+    # and they also want to see downtmp removed.
+    capabilities[:] = [c for c in capabilities if not 
c.startswith('downtmp-host=')]
+
     if downtmp:
         execute_timeout(None, copy_timeout,
                         auxverb + ['rm', '-rf', '--', downtmp])
diff --git a/lib/adt_testbed.py b/lib/adt_testbed.py
index 33a51cf6..37407f80 100644
--- a/lib/adt_testbed.py
+++ b/lib/adt_testbed.py
@@ -106,6 +106,7 @@ class Testbed:
         enable_apt_fallback: bool = True,
         shell_fail: bool = False,
         needs_internet: str = 'run',
+        insecure: bool = False,
     ) -> None:
         self.sp = None
         self.lastsend = None
@@ -130,6 +131,7 @@ class Testbed:
         self.apt_upgrade = apt_upgrade
         self.copy_files = copy_files
         self.initial_kernel_version = None
+        self.insecure = insecure
         # tests might install a different kernel; [(testname, reboot_marker, 
kver)]
         self.test_kernel_versions: List[Tuple[str, str, str]] = []
         # used for tracking kernel version changes
@@ -354,6 +356,23 @@ class Testbed:
             ) as reader:
                 self.check_exec(['sh', '-euc', reader.read(), 'sh', self.user])
 
+        if self.su_user is not None and self.su_user != 'root' and not any(c 
in ['isolation-machine', 'isolation-container'] for c in self.caps):
+            # scratch directory is owned by root and not world-writeable.
+            # (See the rules in README.virtualisation-server.)
+            #
+            # But, we like to run things (particularly, tests) as a normal 
user.
+            # That normal user needs to be able to write to this directory.
+            #
+            # Once upon a time, this was possible because the scratch 
directory was 1777.
+            # Nowadays, we make it possible by making it owned by the normal 
user.
+            #
+            # This means that we make root on the testbed completely trust 
self.user.
+            # This is OK because either:
+            #   self.user came from the testbed capability 
suggested-normal-user=
+            #   self.user came from the --user command line option
+            # and in both cases this trust relationship is documented there.
+            self.check_exec(['chown', self.su_user, '--', self.scratch])
+
         # determine testbed architecture
         self.dpkg_arch = self.check_exec(['dpkg', '--print-architecture'], 
True).strip()
         adtlog.info('testbed dpkg architecture: ' + self.dpkg_arch)
@@ -1330,6 +1349,10 @@ class Testbed:
             # This should come first so that it affects all CLI options
             argv.append('--debug')
 
+        if any(r in ['isolation-machine', 'isolation-container'] for r in 
test.restrictions):
+            # Must come early, because it influences behaviour of other 
options eg --artifacts
+            argv.append('--isolation')
+
         argv.extend([
             '--artifacts={}'.format(test_artifacts),
             '--chdir={}'.format(tree.tb),
@@ -2155,6 +2178,11 @@ class TestbedPath:
             if rc != 0:
                 # chowning doesn't work on all shared downtmps, try to chmod
                 # instead
+
+                # I believe this is only needed for autopkgtest-virt-unshare, 
which
+                # no longer advertises a downtmp at all.
+                # But we allow it still, with --insecure, in case someone 
needs it.
+                assert self.insecure
                 self.testbed.check_exec(['chmod', '-R', 'go+rwX', '--', 
self.tb])
 
     def copyup(self, check_existing=False):
diff --git a/lib/autopkgtest_args.py b/lib/autopkgtest_args.py
index 91e383a2..80e8b633 100644
--- a/lib/autopkgtest_args.py
+++ b/lib/autopkgtest_args.py
@@ -268,6 +268,11 @@ for details.'''
                          action=AppendCommaSeparatedArg,
                          help='Run tests even if these restrictions would '
                          'normally prevent it')
+    g_setup.add_argument('--insecure', action='store_true', default=False,
+                         help='Run without caring about security. '
+                         'Tests might corrupt the testbed, or escape from it. '
+                         'Other processes on the host or the testbed '
+                         'might be able to suborn the account running 
autopkgtest.')
 
     # privileges
     g_priv = parser.add_argument_group('user/privilege handling options')
diff --git a/lib/in-testbed/wrapper.sh b/lib/in-testbed/wrapper.sh
index d352177b..e2f57802 100755
--- a/lib/in-testbed/wrapper.sh
+++ b/lib/in-testbed/wrapper.sh
@@ -28,6 +28,7 @@ cleanup () {
 trap cleanup EXIT INT QUIT PIPE
 
 getopt_temp="debug"
+getopt_temp="$getopt_temp,isolation"
 getopt_temp="$getopt_temp,artifacts:"
 getopt_temp="$getopt_temp,chdir:"
 getopt_temp="$getopt_temp,env:"
@@ -42,12 +43,14 @@ getopt_temp="$(getopt -o '' --long "$getopt_temp" -n "$0" 
-- "$@")"
 eval "set -- $getopt_temp"
 unset getopt_temp
 
+artifacts_dir_mode=755
+
 while [ "$#" -gt 0 ]; do
     case "$1" in
         (--artifacts)
             debug "creating AUTOPKGTEST_ARTIFACTS: $2"
             # shellcheck disable=SC2174
-            mkdir -p -m 1777 -- "$2"
+            mkdir -p -m "$artifacts_dir_mode" -- "$2"
             export AUTOPKGTEST_ARTIFACTS="$2"
             export ADT_ARTIFACTS="$2"
             shift 2
@@ -80,6 +83,12 @@ while [ "$#" -gt 0 ]; do
             shift 2
             ;;
 
+        (--isolation)
+            debug "testbed provides isolation, using world-writeable artifacts 
directory"
+            artifacts_dir_mode=777
+            shift
+            ;;
+
         (--make-executable)
             debug "marking as executable: $2"
             chmod +x -- "$2"
diff --git a/runner/autopkgtest b/runner/autopkgtest
index df915a33..4d2c5fb9 100755
--- a/runner/autopkgtest
+++ b/runner/autopkgtest
@@ -909,6 +909,7 @@ def main():
             apt_default_release=opts.apt_default_release,
             apt_upgrade=opts.apt_upgrade,
             test_arch=opts.test_architecture,
+            insecure=opts.insecure,
         )
         testbed.start()
         testbed.open()
diff --git a/runner/autopkgtest.1 b/runner/autopkgtest.1
index eaa8f078..b0d92630 100644
--- a/runner/autopkgtest.1
+++ b/runner/autopkgtest.1
@@ -206,6 +206,10 @@ If the setup commands affect anything in boot directories 
(like /boot or
 rebooted after the setup commands. This can be suppressed by creating a file
 .BR /run/autopkgtest_no_reboot.stamp .
 
+.B Warning:
+The way that autopkgtest uses this user means it will be completely
+trusted by root inside the testbed.
+
 .TP
 .BI \-\-setup\-commands\-boot= commands
 Run
@@ -360,6 +364,20 @@ The default is not to use anything, except that if
 \fB\-\-user\fR is supplied or root on the testbed is not available the
 default is \fBfakeroot\fR.
 
+.TP
+.B \-\-insecure
+Relaxes file permissions etc., to try to make things not fail with
+permission errors.
+
+Passing this option might allow tests to corrupt the testbed (if it
+doesn't support resetting), or to escape from it, or for untrusted
+processes on the host or the testbed to take over the account running
+autopkgtest.
+
+It is safe to pass \-\-insecure on a dedicated throwaway host.
+
+This partially restores the behaviour of autopkgtest 5.47 and earlier,
+which had several world-writable directories.
 .SH DEBUGGING OPTIONS
 
 .TP
diff --git a/tests/autopkgtest b/tests/autopkgtest
index f2092970..21cfc2e7 100755
--- a/tests/autopkgtest
+++ b/tests/autopkgtest
@@ -1,5 +1,11 @@
 #!/usr/bin/python3
 
+# WARNING
+#
+# This script may not be safe to run, except inside a container.
+# Some of the test cases assume that there is no hostile code around.
+# So don't run it on a shared machine, or in your normal desktop session.
+
 # this testsuite is part of autopkgtest
 # autopkgtest is a tool for testing Debian binary packages
 #
@@ -323,7 +329,7 @@ class AdtTestCase(unittest.TestCase):
 
     @property
     def has_isolation_machine(self):
-        return self.virt_args[0] in ['qemu', 'null']
+        return self.virt_args[0] in ['qemu']
 
     @property
     def has_isolation_container(self):
@@ -333,6 +339,7 @@ class AdtTestCase(unittest.TestCase):
         return self.virt_args[0] not in [
             'chroot',
             'docker',
+            'null',
             'podman',
             'schroot',
             'unshare',
diff --git a/virt/autopkgtest-virt-chroot b/virt/autopkgtest-virt-chroot
index c7590ff7..ae700e93 100755
--- a/virt/autopkgtest-virt-chroot
+++ b/virt/autopkgtest-virt-chroot
@@ -68,16 +68,13 @@ def hook_open():
 
 def hook_downtmp(path):
     global capabilities
-    d = VirtSubproc.downtmp_mktemp(path)
-    if chroot_dir:
-        capabilities.append('downtmp-host=%s/%s' % (chroot_dir, d))
+    d = VirtSubproc.downtmp_mktemp(capabilities, path, chroot_dir)
     return d
 
 
 def hook_cleanup():
     global capabilities
-    VirtSubproc.downtmp_remove()
-    capabilities = [c for c in capabilities if not 
c.startswith('downtmp-host')]
+    VirtSubproc.downtmp_remove(capabilities)
 
 
 def hook_capabilities():
diff --git a/virt/autopkgtest-virt-docker b/virt/autopkgtest-virt-docker
index ae7371fc..ae6d9484 100755
--- a/virt/autopkgtest-virt-docker
+++ b/virt/autopkgtest-virt-docker
@@ -295,7 +295,7 @@ def hook_downtmp(path):
         VirtSubproc.check_exec(['mkdir', '-m', '777', d], downp=True)
         capabilities.append('downtmp-host=' + d)
     else:
-        d = VirtSubproc.downtmp_mktemp(path)
+        d = VirtSubproc.downtmp_mktemp(capabilities, path, None)
     return d
 
 
@@ -307,8 +307,7 @@ def hook_revert():
 def hook_cleanup():
     global capabilities, container_id, shared_dir
 
-    VirtSubproc.downtmp_remove()
-    capabilities = [c for c in capabilities if not 
c.startswith('downtmp-host')]
+    VirtSubproc.downtmp_remove(capabilities)
     if shared_dir:
         shutil.rmtree(shared_dir)
 
diff --git a/virt/autopkgtest-virt-lxc b/virt/autopkgtest-virt-lxc
index 7f114f27..a319a12a 100755
--- a/virt/autopkgtest-virt-lxc
+++ b/virt/autopkgtest-virt-lxc
@@ -279,7 +279,7 @@ def hook_downtmp(path):
         VirtSubproc.check_exec(['mkdir', '-m', '777', d], downp=True, 
timeout=30)
         capabilities.append('downtmp-host=' + d)
     else:
-        d = VirtSubproc.downtmp_mktemp(path)
+        d = VirtSubproc.downtmp_mktemp(path, capabilities, None)
     return d
 
 
@@ -299,8 +299,7 @@ def hook_wait_reboot(*func_args, **kwargs):
 def hook_cleanup():
     global capabilities, shared_dir, lxc_container_name
 
-    VirtSubproc.downtmp_remove()
-    capabilities = [c for c in capabilities if not 
c.startswith('downtmp-host')]
+    VirtSubproc.downtmp_remove(capabilities)
 
     if lxc_container_name:
         VirtSubproc.check_exec(
diff --git a/virt/autopkgtest-virt-lxd b/virt/autopkgtest-virt-lxd
index 95297aa1..856731c4 100755
--- a/virt/autopkgtest-virt-lxd
+++ b/virt/autopkgtest-virt-lxd
@@ -218,7 +218,7 @@ def hook_open():
 
 
 def hook_downtmp(path):
-    return VirtSubproc.downtmp_mktemp(path)
+    return VirtSubproc.downtmp_mktemp(capabilities, path, None)
 
 
 def hook_revert():
@@ -279,7 +279,7 @@ def hook_wait_reboot(*func_args, **kwargs):
 
 
 def hook_cleanup():
-    VirtSubproc.downtmp_remove()
+    VirtSubproc.downtmp_remove(capabilities)
     VirtSubproc.check_exec([args.command, 'delete', '--force', container_name],
                            timeout=600)
 
diff --git a/virt/autopkgtest-virt-null b/virt/autopkgtest-virt-null
index 0ab2f30b..514a34ca 100755
--- a/virt/autopkgtest-virt-null
+++ b/virt/autopkgtest-virt-null
@@ -34,25 +34,27 @@ import VirtSubproc
 import adtlog
 
 
-capabilities = [
-    'isolation-machine',
-]
+capabilities = []
 
 if os.getuid() == 0:
     capabilities.append('root-on-testbed')
 
 
 def parse_args():
-    global args
+    global args, capabilities
 
     parser = argparse.ArgumentParser()
     parser.add_argument('-d', '--debug', action='store_true',
                         help='Enable debugging output')
     parser.add_argument('--retain-tmp', action='store_true',
                         help='Retain temporary subdir (you should set TMPDIR)')
+    parser.add_argument('--fake-capability', action='append',
+                        metavar='CAPABILITY', default=[],
+                        help='Declare capability CAPABILITY')
     args = parser.parse_args()
     if args.debug:
         adtlog.verbosity = 2
+    capabilities += args.fake_capability
 
 
 def hook_open():
@@ -62,7 +64,7 @@ def hook_open():
 def hook_downtmp(path):
     global capabilities
 
-    d = VirtSubproc.downtmp_mktemp(path)
+    d = VirtSubproc.downtmp_mktemp(capabilities, path, '')
     capabilities.append('downtmp-host=' + d)
     return d
 
@@ -71,8 +73,7 @@ def hook_cleanup():
     global capabilities
 
     if not args.retain_tmp:
-        VirtSubproc.downtmp_remove()
-    capabilities = [c for c in capabilities if not 
c.startswith('downtmp-host')]
+        VirtSubproc.downtmp_remove(capabilities)
 
 
 def hook_capabilities():
diff --git a/virt/autopkgtest-virt-null.1 b/virt/autopkgtest-virt-null.1
index 142a6719..ef399be1 100644
--- a/virt/autopkgtest-virt-null.1
+++ b/virt/autopkgtest-virt-null.1
@@ -24,6 +24,21 @@ so as to reuse the same system as a testbed.
 .BR \-d " | " \-\-debug
 Enables debugging output.  Probably not hugely interesting.
 
+.TP
+.BI \-\-fake-capability " CAPABILITY"
+Advertise
+.I CAPABILITY
+in the list of capabilities,
+regardless of whether the capability is implemented.
+Note that this is hazardous -
+tests may malfunction, or even mess up the system.
+
+In autopkgtest 5.47 and earlier,
+autopkgtest\-virt\-null falsely advertises
+.BR isolation\-machine .
+This option can be used to restore that behaviour:
+.BR \-\-fake\-capability=isolation\-machine .
+
 .TP
 .BR \-\-retain-tmp
 Do not delete the temporary directory on exit.
diff --git a/virt/autopkgtest-virt-qemu b/virt/autopkgtest-virt-qemu
index 72799332..0a86f209 100755
--- a/virt/autopkgtest-virt-qemu
+++ b/virt/autopkgtest-virt-qemu
@@ -65,6 +65,13 @@ args = None
 factory: Optional[QemuFactory] = None
 qemu: Optional[QemuSession] = None
 normal_user = None
+capabilities = [
+    'isolation-machine',
+    'reboot',
+    'revert',
+    'revert-full-system',
+    'root-on-testbed',
+]
 
 
 def parse_args() -> None:
@@ -645,11 +652,11 @@ def hook_downtmp(path: str) -> None:
     # trees
     # downtmp = '/run/autopkgtest/shared/tmp'
     # VirtSubproc.check_exec(['mkdir', '-m', '1777', downtmp], downp=True)
-    return VirtSubproc.downtmp_mktemp(path)
+    return VirtSubproc.downtmp_mktemp(capabilities, path, None)
 
 
 def hook_revert() -> None:
-    VirtSubproc.downtmp_remove()
+    VirtSubproc.downtmp_remove(capabilities)
     hook_cleanup()
     hook_open()
 
@@ -699,16 +706,10 @@ def hook_wait_reboot(*func_args: Any, **kwargs: Any) -> 
None:
 
 def hook_capabilities() -> List[str]:
     global normal_user
-    caps = [
-        'isolation-machine',
-        'reboot',
-        'revert',
-        'revert-full-system',
-        'root-on-testbed',
-    ]
-    # disabled, see hook_downtmp()
-    # caps.append('downtmp-host=%s' % os.path.join(qemu.workdir, 'shared', 
'tmp'))
+    caps = list(capabilities)
     if normal_user:
+        # Adding this here, each time, rather than putting it in 
`capabilities`,
+        # avoids worrying about whether it will change, or trying to filter it 
out and re-add it.
         caps.append('suggested-normal-user=' + normal_user)
     return caps
 
diff --git a/virt/autopkgtest-virt-schroot b/virt/autopkgtest-virt-schroot
index 59faa841..bf334d2a 100755
--- a/virt/autopkgtest-virt-schroot
+++ b/virt/autopkgtest-virt-schroot
@@ -110,9 +110,13 @@ def parse_args():
         adtlog.debug('have "root-on-testbed" capability')
         capabilities.append('root-on-testbed')
 
-    if os.geteuid() != 0:
-        username = pwd.getpwuid(os.geteuid()).pw_name
-        capabilities.append('suggested-normal-user=' + username)
+        if os.geteuid() != 0:
+            username = pwd.getpwuid(os.geteuid()).pw_name
+            # Passing this capability promises that it is OK for root on the 
testbed
+            # to completely trust this user.  That is correct, because this 
user
+            # is in schroot's `root-users` configuration - so that trust 
relationship
+            # is already defined by the schroot administrator.
+            capabilities.append('suggested-normal-user=' + username)
 
 
 def hook_open():
@@ -131,7 +135,7 @@ def hook_open():
 def hook_downtmp(path):
     global capabilities
 
-    d = VirtSubproc.downtmp_mktemp(path)
+    d = VirtSubproc.downtmp_mktemp(capabilities, path, None)
 
     # determine mount location
     location = VirtSubproc.check_exec(['schroot', '--location', '--chroot',
@@ -162,7 +166,7 @@ def hook_revert():
 
 def hook_cleanup():
     global schroot, sessid, capabilities
-    VirtSubproc.downtmp_remove()
+    VirtSubproc.downtmp_remove(capabilities)
     # sometimes fails on EBUSY
     retries = 10
     try:
@@ -180,7 +184,7 @@ def hook_cleanup():
         adtlog.warning('schroot --end-session timed out after %d seconds;'
                        'please clean up manually', e.timeout_secs)
 
-    capabilities = [c for c in capabilities if not 
c.startswith('downtmp-host')]
+    capabilities = [c for c in capabilities if not 
c.startswith('downtmp-host=')]
 
 
 def hook_capabilities():
diff --git a/virt/autopkgtest-virt-ssh b/virt/autopkgtest-virt-ssh
index 21c7d492..a1fa36bf 100755
--- a/virt/autopkgtest-virt-ssh
+++ b/virt/autopkgtest-virt-ssh
@@ -426,7 +426,7 @@ def hook_open():
 
 
 def hook_downtmp(path):
-    return VirtSubproc.downtmp_mktemp(path)
+    return VirtSubproc.downtmp_mktemp(capabilities, path, None)
 
 
 def hook_revert():
@@ -492,7 +492,7 @@ def hook_cleanup():
     global capabilities, workdir, cleanup_paths, sshcmd
 
     try:
-        VirtSubproc.downtmp_remove()
+        VirtSubproc.downtmp_remove(capabilities)
         if cleanup_paths:
             VirtSubproc.check_exec(['rm', '-rf'] + cleanup_paths, downp=True,
                                    timeout=VirtSubproc.copy_timeout)
@@ -502,8 +502,6 @@ def hook_cleanup():
 
     execute_setup_script('cleanup')
 
-    capabilities = [c for c in capabilities if not 
c.startswith('downtmp-host')]
-
     # terminate ssh connection muxer; it inherits our stderr (which causes an
     # eternal hang of tee processes), and we are going to remove the socket dir
     # anyway
diff --git a/virt/autopkgtest-virt-ssh.1 b/virt/autopkgtest-virt-ssh.1
index b815e0b5..723fb4f4 100644
--- a/virt/autopkgtest-virt-ssh.1
+++ b/virt/autopkgtest-virt-ssh.1
@@ -159,6 +159,21 @@ The behaviour of
 is as described by the AutomatedTesting virtualisation regime
 specification.
 
+.SH SECURITY WARNING - USE OF ROOT
+
+If autopkgtest\-virt\-ssh is used in a way that means it has root
+access on the testbed, the testbed environment
+must be an isolated one which
+.IR "does not ever run any untrusted code" ,
+or is itself not trusted by anything.
+
+This promise is implied by user-switching capabilities that
+autopkgtest\-virt\-ssh may advertise, depending in a complex way on
+the provided configuration and command line options.
+
+If autopkgtest\-virt\-ssh only has access to a single user on the
+remote host, this problem does not arise.
+
 .SH NOTES
 
 \fBautopkgtest\fR does not run \fBapt\-get update\fR at the start of a package
diff --git a/virt/autopkgtest-virt-unshare b/virt/autopkgtest-virt-unshare
index 243e0bb1..a5432ffd 100755
--- a/virt/autopkgtest-virt-unshare
+++ b/virt/autopkgtest-virt-unshare
@@ -191,12 +191,7 @@ def hook_open():
 
 
 def hook_downtmp(path):
-    global capabilities
-
-    tmp = VirtSubproc.downtmp_mktemp(path)
-
-    capabilities.append(f'downtmp-host={rootdir}/{tmp}')
-
+    tmp = VirtSubproc.downtmp_mktemp(capabilities, path, None)
     return tmp
 
 
@@ -211,7 +206,7 @@ def hook_cleanup(revert=False):
     VirtSubproc.check_exec(['unshare', '--map-auto', '--map-root-user', 'rm',
                             '-rf', rootdir])
 
-    capabilities = [c for c in capabilities if not 
c.startswith('downtmp-host')]
+    capabilities = [c for c in capabilities if not 
c.startswith('downtmp-host=')]
 
     if not revert and temp_tarball:
         path = pathlib.Path(tarball)

Reply via email to