From 244073ac87fc934cd299be76b7da950b9817f725 Mon Sep 17 00:00:00 2001
From: Benjamin Drung <benjamin.drung@canonical.com>
Date: Mon, 13 Oct 2025 15:38:22 +0200
Subject: [PATCH] dpkg-buildflags: enable ELF package note metadata

Following the https://systemd.io/ELF_PACKAGE_METADATA/ add a new qa
flag, enable by default, that adds this package metadata ELF note to
every binary being built, to ease identification, especially in the
case of a coredump.

Since the ELF package note metadata changes the build ID, include only
"static" data and exclude the package version (as agreed at DebConf 25).
---
 man/dpkg-buildflags.pod       |  8 +++++
 scripts/Dpkg/Vendor/Debian.pm | 64 +++++++++++++++++++++++++++++++++++
 scripts/t/Dpkg_BuildFlags.t   | 12 ++++++-
 3 files changed, 83 insertions(+), 1 deletion(-)

diff --git a/man/dpkg-buildflags.pod b/man/dpkg-buildflags.pod
index 7a759eb7a..33be2555b 100644
--- a/man/dpkg-buildflags.pod
+++ b/man/dpkg-buildflags.pod
@@ -545,6 +545,14 @@ B<OBJCFLAGS>, B<CXXFLAGS> and B<OBJCXXFLAGS> with flags set
 to B<-D__DEB_CANARY_>I<flag>_I<random-id>B<__>, and
 B<LDFLAGS> set to B<-Wl,-z,deb-canary->I<random-id>.
 
+=item B<elfpackagemetadata>
+
+This setting (since dpkg 1.23.0; enabled by default) adds an ELF
+note containing package metadata to every ELF binary being built. If the
+$DEB_BUILD_DEBUG_INFO_URL environment variable is defined, it will be
+used to include the value as a debuginfod URL. This follows the specification
+defined at: https://systemd.io/ELF_PACKAGE_METADATA/
+
 =back
 
 =head2 optimize
diff --git a/scripts/Dpkg/Vendor/Debian.pm b/scripts/Dpkg/Vendor/Debian.pm
index f7272ad61..11d1da413 100644
--- a/scripts/Dpkg/Vendor/Debian.pm
+++ b/scripts/Dpkg/Vendor/Debian.pm
@@ -136,6 +136,7 @@ sub set_build_features {
             bug => undef,
             'bug-implicit-func' => undef,
             canary => 0,
+            elfpackagemetadata => 1,
         },
         reproducible => {
             timeless => 1,
@@ -516,6 +517,10 @@ sub add_build_flags {
         $flags->append('LDFLAGS', "-Wl,-z,deb-canary-${id}");
     }
 
+    if ($flags->use_feature('qa', 'elfpackagemetadata')) {
+        add_elf_package_metadata($flags);
+    }
+
     ## Area: reproducible
 
     # Warn when the __TIME__, __DATE__ and __TIMESTAMP__ macros are used.
@@ -681,6 +686,65 @@ sub add_build_flags {
     }
 }
 
+sub add_elf_package_metadata {
+    my ($flags) = @_;
+    my $arch;
+    my $pkgsrc;
+
+    if (!defined $ENV{DEB_HOST_ARCH}) {
+        require Dpkg::Arch;
+        $arch = Dpkg::Arch::get_host_arch();
+    } else {
+        $arch = $ENV{DEB_HOST_ARCH};
+    }
+
+    if (!defined $ENV{DEB_SOURCE}) {
+        if (! -r 'debian/changelog') {
+            warning(g_('debian/changelog not found. Not setting ELF package metadata parameter.'));
+            return;
+        }
+
+        require Dpkg::Changelog::Debian;
+        my $pkgchangelog = Dpkg::Changelog::Debian->new(range => { "count" => 1 });
+        $pkgchangelog->load('debian/changelog');
+        my $chgentry = @{$pkgchangelog}[0];
+        $pkgsrc = $chgentry->get_source();
+    } else {
+        $pkgsrc = $ENV{DEB_SOURCE};
+    }
+
+    if (! -f '/usr/lib/os-release') {
+        warning(g_('/usr/lib/os-release not found. Not setting ELF package metadata parameter.'));
+        return;
+    }
+    my $os_id = '';
+    open my $os_release, '<', '/usr/lib/os-release'
+        or syserr(g_('failed to read /usr/lib/os-release'));
+    while (<$os_release>) {
+        chomp;
+        # Ensure any quotes are removed, as we don't want to embed them in JSON
+        if (m/^ID=(.*)/) {
+            $os_id = $1;
+            $os_id =~ tr/"//d;
+        }
+    }
+    close $os_release or syserr(g_('failed to close /usr/lib/os-release'));
+    if ($os_id eq '') {
+        warning(g_('/usr/lib/os-release does not set ID. Not setting ELF package metadata parameter.'));
+        return;
+    }
+
+    # Per https://systemd.io/ELF_PACKAGE_METADATA/
+    my $package_metadata = "{%22type%22:%22deb%22%2C%22os%22:%22$os_id%22%2C%22name%22:%22$pkgsrc%22%2C%22architecture%22:%22$arch%22";
+    # The debuginfod URL is optional, skip it if not available.
+    if ($ENV{DEB_BUILD_DEBUG_INFO_URL}) {
+        $package_metadata .= "%2C%22debugInfoUrl%22:%22$ENV{DEB_BUILD_DEBUG_INFO_URL}%22}";
+    }
+    $package_metadata .= "}";
+    $flags->append('LDFLAGS', "-Wl,--package-metadata=$package_metadata");
+    return;
+}
+
 sub _build_tainted_by {
     my $self = shift;
     my %tainted;
diff --git a/scripts/t/Dpkg_BuildFlags.t b/scripts/t/Dpkg_BuildFlags.t
index 011a50717..07d8a15d2 100644
--- a/scripts/t/Dpkg_BuildFlags.t
+++ b/scripts/t/Dpkg_BuildFlags.t
@@ -15,7 +15,7 @@
 
 use v5.36;
 
-use Test::More tests => 118;
+use Test::More tests => 119;
 
 BEGIN {
     $ENV{DEB_BUILD_ARCH} = 'amd64';
@@ -121,6 +121,7 @@ my %known_features = (
         bug
         bug-implicit-func
         canary
+        elfpackagemetadata
     ) ],
     reproducible => [ qw(
         fixdebugpath
@@ -316,4 +317,13 @@ test_has_noflag($bf, 'CPPFLAGS', '-D_TIME_BITS=64');
 test_has_noflag($bf, 'CPPFLAGS', '-U_TIME_BITS');
 test_has_flag($bf, 'CFLAGS', '-Werror=implicit-function-declaration');
 
+# ELF package metadata
+$bf = Dpkg::BuildFlags->new();
+my %qa_features = $bf->get_features('qa');
+if ($qa_features{'elfpackagemetadata'} && -r 'debian/changelog') {
+    test_has_flag($bf, 'LDFLAGS', '-Wl,--package-metadata=\{*');
+} else {
+    test_has_noflag($bf, 'LDFLAGS', '-Wl,--package-metadata*');
+}
+
 # TODO: Add more test cases.
-- 
2.51.0

