Hi!

On Wed, 2022-05-18 at 07:26:02 +0200, Johannes Schauer Marin Rodrigues wrote:
> Package: dpkg
> Version: 1.21.7
> Severity: wishlist
> Tags: patch
> X-Debbugs-Cc: jo...@debian.org

> when cross compiling, one property of the build system that can
> influence the contents of the generate binary packages is whether or not
> the host architecture can be executed. While some platforms can natively
> execute others (like amd64 can execute i386), other combinations are
> more surprising. When installing the qemu-user-static package on a
> system with binfmt-support, then foreign architecture binaries for all
> architectures qemu supports will suddenly become executable. This is
> especially tricky because this will also transparently affect chroot
> builds with sbuild and neither schroot nor unshare isolation can prevent
> the emulation from happening. The only ways to stop automatic emulation
> are uninstalling qemu-user-static on the outside of the build chroot,
> writing 0 to /proc/sys/fs/binfmt_misc/qemu-$arch or running the build
> with QEMU_VERSION=1 (unreliable). Since transparent foreign architecture
> emulation is easily present on a developer's machine and thus
> influencing the build (even when done inside a chroot) it would be
> useful to record whether or not foreign architecture binaries can be
> executed in the buildinfo file.

Hmm right. To me it feels more like a taint flag though. The
compilation and execution of the host program feels a bit meh, but
there's certainly no other way to fetch that information otherwise.

> I attached a proof-of-concept patch that does exactly that. Since we
> cannot rely on arch-test being installed in the build environment, this
> approach cross compiles a small true.c executable for the host
> architecture. This should always work because gcc is build-essential.
> The binary outputs a small string instead of just relying on the exit
> code to guard against QEMU_VERSION=1 "disabling" of emulation. The field
> 'Can-Execute-Host-Architecture is only added when cross-compiling, i.e
> when host and build architectures mismatch.

I'm attaching the slightly revised version with few fixes/changes.

> >From 62179358b57d09fc8c6bb7a59deb128c67cbe522 Mon Sep 17 00:00:00 2001
> From: Johannes Schauer Marin Rodrigues <jo...@mister-muffin.de>
> Date: Wed, 18 May 2022 07:11:39 +0200
> Subject: [PATCH] dpkg-genbuildinfo: when cross-compiling add
>  Can-Execute-Host-Architecture field

> +use File::Temp qw(tmpnam);

This function is marked as obsolete by POSIX, the File::Temp object
provides a nice interface that can be used instead, perhaps you used
it but were hit by ETXTBSY errors? (If so closing the descriptor fixes
the issue, which is what I've done now.) This also means we do not
need to cleanup the file as the object will do it on its destructor
when going out of scope.

> +    spawn(exec => [ debarch_to_gnutriplet(get_host_arch()) . '-gcc', '-x', 
> 'c', '-o', $tmpname, '-' ],

I added honoring the CC envvar, but can potentially result in building
for the build instead of host arch, as unfortunately we cannot rely on
an "external build driver" setting a coherent build environment. So
should probably go back to hardcoding it, but I'm thinking I should
move all gcc hardcoding into a new vendor-hook that gets the default
compiler name.

> +    if ($? == 0 && $stdout eq "ok") {
> +       $fields->{'Can-Execute-Host-Architecture'} = "true";
> +    } else {
> +       $fields->{'Can-Execute-Host-Architecture'} = "false";

I think a taint flag makes more sense. But it has the problem that
then you might need to check the dpkg version used to see whether
the check might have been performed, but I'm not sure whether that'd
be a concern at all? (If its own field would be strongly favored then
I think it should use the usual "yes"/"no" values used elsewhere.)

Thanks,
Guillem
From cd5f2c47f8aa60e19a7906d3e38b6e53b899a51d Mon Sep 17 00:00:00 2001
From: Johannes Schauer Marin Rodrigues <jo...@mister-muffin.de>
Date: Fri, 27 May 2022 01:33:19 +0200
Subject: [PATCH] dpkg-genbuildinfo: Add new can-execute-cross-built-programs
 tainted flag

[guil...@debian.org:
 - Use File::Temp instead of tmpnam() and push_exit_handler().
 - Set a taint flag instead of a new field.
 - Refactor into a function.
 - Honor CC environment variable.
 - Style fixes. ]

Closes: #1011191
---
 scripts/dpkg-genbuildinfo.pl | 48 ++++++++++++++++++++++++++++++++++--
 1 file changed, 46 insertions(+), 2 deletions(-)

diff --git a/scripts/dpkg-genbuildinfo.pl b/scripts/dpkg-genbuildinfo.pl
index e05fce048..81e4636e5 100755
--- a/scripts/dpkg-genbuildinfo.pl
+++ b/scripts/dpkg-genbuildinfo.pl
@@ -28,13 +28,15 @@ use warnings;
 use List::Util qw(any);
 use Cwd;
 use File::Basename;
+use File::Temp;
 use POSIX qw(:fcntl_h :locale_h strftime);
 
 use Dpkg ();
 use Dpkg::Gettext;
 use Dpkg::Checksums;
 use Dpkg::ErrorHandling;
-use Dpkg::Arch qw(get_build_arch get_host_arch debarch_eq);
+use Dpkg::IPC;
+use Dpkg::Arch qw(get_build_arch get_host_arch debarch_eq debarch_to_gnutriplet);
 use Dpkg::Build::Types;
 use Dpkg::Build::Info qw(get_build_env_allowed);
 use Dpkg::BuildOptions;
@@ -247,6 +249,48 @@ sub collect_installed_builddeps {
     return $installed_deps;
 }
 
+sub get_tainted_by {
+    my @tainted = run_vendor_hook('build-tainted-by');
+
+    my $host_arch = get_host_arch();
+    my $build_arch = get_build_arch();
+    if ($host_arch ne $build_arch) {
+        # If we are cross-compiling, record whether it was possible to execute
+        # the host architecture by cross-compiling and executing a small
+        # host-arch binary.
+        my $CC = $ENV{CC} || debarch_to_gnutriplet($host_arch) . '-gcc';
+        my $crossprog = 'int main() { write(1, "ok", 2); return 0; }';
+        my ($stdout, $stderr) = ('', '');
+        my $tmpfh = File::Temp->new();
+        spawn(
+            exec => [ $CC, '-x', 'c', '-o', $tmpfh->filename, '-' ],
+            from_string => \$crossprog,
+            to_string => \$stdout,
+            error_to_string => \$stderr,
+            wait_child => 1,
+            nocheck => 1,
+        );
+        if ($?) {
+           print { *STDOUT } $stdout;
+           print { *STDERR } $stderr;
+           subprocerr("$CC -x c -");
+        }
+        close $tmpfh;
+        spawn(
+            exec => [ $tmpfh->filename ],
+            error_to_file => '/dev/null',
+            to_string => \$stdout,
+            wait_child => 1,
+            nocheck => 1,
+        );
+        if ($? == 0 && $stdout eq 'ok') {
+            push @tainted, 'can-execute-cross-built-programs';
+        }
+    }
+
+    return @tainted;
+}
+
 sub cleansed_environment {
     # Consider only allowed variables which are not supposed to leak
     # local user information.
@@ -447,7 +491,7 @@ if ($use_feature{path}) {
     }
 }
 
-$fields->{'Build-Tainted-By'} = "\n" . join "\n", run_vendor_hook('build-tainted-by');
+$fields->{'Build-Tainted-By'} = "\n" . join "\n", get_tainted_by();
 
 $checksums->export_to_control($fields);
 
-- 
2.36.1

Reply via email to