Dpkg::Vendor::Debian: Sync builtin pie arches with gcc-13
[dpkg.git] / scripts / Dpkg / Vendor / Debian.pm
blob2acda6b81fa90d298ae2c710b57a32fa3b30f669
1 # Copyright © 2009-2011 Raphaël Hertzog <hertzog@debian.org>
2 # Copyright © 2009, 2011-2017 Guillem Jover <guillem@debian.org>
4 # Hardening build flags handling derived from work of:
5 # Copyright © 2009-2011 Kees Cook <kees@debian.org>
6 # Copyright © 2007-2008 Canonical, Ltd.
8 # This program is free software; you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 2 of the License, or
11 # (at your option) any later version.
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with this program. If not, see <https://www.gnu.org/licenses/>.
21 =encoding utf8
23 =head1 NAME
25 Dpkg::Vendor::Debian - Debian vendor class
27 =head1 DESCRIPTION
29 This vendor class customizes the behavior of dpkg scripts for Debian
30 specific behavior and policies.
32 B<Note>: This is a private module, its API can change at any time.
34 =cut
36 package Dpkg::Vendor::Debian 0.01;
38 use strict;
39 use warnings;
41 use List::Util qw(any none);
43 use Dpkg;
44 use Dpkg::Gettext;
45 use Dpkg::ErrorHandling;
46 use Dpkg::Control::Types;
48 use parent qw(Dpkg::Vendor::Default);
50 sub run_hook {
51 my ($self, $hook, @params) = @_;
53 if ($hook eq 'package-keyrings') {
54 return ('/usr/share/keyrings/debian-keyring.gpg',
55 '/usr/share/keyrings/debian-nonupload.gpg',
56 '/usr/share/keyrings/debian-maintainers.gpg');
57 } elsif ($hook eq 'archive-keyrings') {
58 return ('/usr/share/keyrings/debian-archive-keyring.gpg');
59 } elsif ($hook eq 'archive-keyrings-historic') {
60 return ('/usr/share/keyrings/debian-archive-removed-keys.gpg');
61 } elsif ($hook eq 'builtin-build-depends') {
62 return qw(build-essential:native);
63 } elsif ($hook eq 'builtin-build-conflicts') {
64 return ();
65 } elsif ($hook eq 'register-custom-fields') {
66 } elsif ($hook eq 'extend-patch-header') {
67 my ($textref, $ch_info) = @params;
68 if ($ch_info->{'Closes'}) {
69 foreach my $bug (split(/\s+/, $ch_info->{'Closes'})) {
70 $$textref .= "Bug-Debian: https://bugs.debian.org/$bug\n";
74 # XXX: Layer violation...
75 require Dpkg::Vendor::Ubuntu;
76 my $b = Dpkg::Vendor::Ubuntu::find_launchpad_closes($ch_info->{'Changes'});
77 foreach my $bug (@$b) {
78 $$textref .= "Bug-Ubuntu: https://bugs.launchpad.net/bugs/$bug\n";
80 } elsif ($hook eq 'update-buildflags') {
81 $self->set_build_features(@params);
82 $self->_add_build_flags(@params);
83 } elsif ($hook eq 'builtin-system-build-paths') {
84 return qw(/build/);
85 } elsif ($hook eq 'build-tainted-by') {
86 return $self->_build_tainted_by();
87 } elsif ($hook eq 'sanitize-environment') {
88 # Reset umask to a sane default.
89 umask 0022;
90 # Reset locale to a sane default.
91 $ENV{LC_COLLATE} = 'C.UTF-8';
92 } elsif ($hook eq 'backport-version-regex') {
93 return qr/~(bpo|deb)/;
94 } else {
95 return $self->SUPER::run_hook($hook, @params);
99 sub init_build_features {
100 my ($self, $use_feature, $builtin_feature) = @_;
103 sub set_build_features {
104 my ($self, $flags) = @_;
106 # Default feature states.
107 my %use_feature = (
108 future => {
109 lfs => 0,
110 time64 => 0,
112 qa => {
113 bug => 0,
114 canary => 0,
116 reproducible => {
117 timeless => 1,
118 fixfilepath => 1,
119 fixdebugpath => 1,
121 optimize => {
122 lto => 0,
124 sanitize => {
125 address => 0,
126 thread => 0,
127 leak => 0,
128 undefined => 0,
130 hardening => {
131 # XXX: This is set to undef so that we can cope with the brokenness
132 # of gcc managing this feature builtin.
133 pie => undef,
134 stackprotector => 1,
135 stackprotectorstrong => 1,
136 fortify => 1,
137 format => 1,
138 relro => 1,
139 bindnow => 0,
143 my %builtin_feature = (
144 hardening => {
145 pie => 1,
149 $self->init_build_features(\%use_feature, \%builtin_feature);
151 ## Setup
153 require Dpkg::BuildOptions;
155 # Adjust features based on user or maintainer's desires.
156 my $opts_build = Dpkg::BuildOptions->new(envvar => 'DEB_BUILD_OPTIONS');
157 my $opts_maint = Dpkg::BuildOptions->new(envvar => 'DEB_BUILD_MAINT_OPTIONS');
159 foreach my $area (sort keys %use_feature) {
160 $opts_build->parse_features($area, $use_feature{$area});
161 $opts_maint->parse_features($area, $use_feature{$area});
164 require Dpkg::Arch;
166 my $arch = Dpkg::Arch::get_host_arch();
167 my ($abi, $libc, $os, $cpu) = Dpkg::Arch::debarch_to_debtuple($arch);
168 my ($abi_bits, $abi_endian) = Dpkg::Arch::debarch_to_abiattrs($arch);
170 unless (defined $abi and defined $libc and defined $os and defined $cpu) {
171 warning(g_("unknown host architecture '%s'"), $arch);
172 ($abi, $os, $cpu) = ('', '', '');
174 unless (defined $abi_bits and defined $abi_endian) {
175 warning(g_("unknown abi attributes for architecture '%s'"), $arch);
176 ($abi_bits, $abi_endian) = (0, 'unknown');
179 ## Area: future
181 if ($use_feature{future}{time64}) {
182 # On glibc, new ports default to time64, old ports currently default
183 # to time32, so we track the latter as that is a list that is not
184 # going to grow further, and might shrink.
185 # On musl libc based systems all ports use time64.
186 my %time32_arch = map { $_ => 1 } qw(
188 armeb
189 armel
190 armhf
191 hppa
192 i386
193 hurd-i386
194 kfreebsd-i386
195 m68k
196 mips
197 mipsel
198 mipsn32
199 mipsn32el
200 mipsn32r6
201 mipsn32r6el
202 mipsr6
203 mipsr6el
204 nios2
205 powerpc
206 powerpcel
207 powerpcspe
208 s390
210 sh3eb
212 sh4eb
213 sparc
215 if ($abi_bits != 32 or
216 not exists $time32_arch{$arch} or
217 $libc eq 'musl') {
218 $use_feature{future}{time64} = 0;
219 } elsif ($libc eq 'gnu') {
220 # On glibc 64-bit time_t support requires LFS.
221 $use_feature{future}{lfs} = 1;
225 if ($use_feature{future}{lfs}) {
226 if ($abi_bits != 32) {
227 $use_feature{future}{lfs} = 0;
231 ## Area: reproducible
233 # Mask features that might have an unsafe usage.
234 if ($use_feature{reproducible}{fixfilepath} or
235 $use_feature{reproducible}{fixdebugpath}) {
236 require Cwd;
238 my $build_path =$ENV{DEB_BUILD_PATH} || Cwd::getcwd();
240 $flags->set_option_value('build-path', $build_path);
242 # If we have any unsafe character in the path, disable the flag,
243 # so that we do not need to worry about escaping the characters
244 # on output.
245 if ($build_path =~ m/[^-+:.0-9a-zA-Z~\/_]/) {
246 $use_feature{reproducible}{fixfilepath} = 0;
247 $use_feature{reproducible}{fixdebugpath} = 0;
251 ## Area: optimize
253 if ($opts_build->has('noopt')) {
254 $flags->set_option_value('optimize-level', 0);
255 } else {
256 $flags->set_option_value('optimize-level', 2);
259 ## Area: sanitize
261 # Handle logical feature interactions.
262 if ($use_feature{sanitize}{address} and $use_feature{sanitize}{thread}) {
263 # Disable the thread sanitizer when the address one is active, they
264 # are mutually incompatible.
265 $use_feature{sanitize}{thread} = 0;
267 if ($use_feature{sanitize}{address} or $use_feature{sanitize}{thread}) {
268 # Disable leak sanitizer, it is implied by the address or thread ones.
269 $use_feature{sanitize}{leak} = 0;
272 ## Area: hardening
274 # Mask builtin features that are not enabled by default in the compiler.
275 my %builtin_pie_arch = map { $_ => 1 } qw(
276 amd64
277 arm64
278 armel
279 armhf
280 hurd-amd64
281 hurd-i386
282 i386
283 kfreebsd-amd64
284 kfreebsd-i386
285 mips
286 mips64
287 mips64el
288 mips64r6
289 mips64r6el
290 mipsel
291 mipsn32
292 mipsn32el
293 mipsn32r6
294 mipsn32r6el
295 mipsr6
296 mipsr6el
297 powerpc
298 ppc64
299 ppc64el
300 riscv64
301 s390x
302 sparc
303 sparc64
305 if (not exists $builtin_pie_arch{$arch}) {
306 $builtin_feature{hardening}{pie} = 0;
309 # Mask features that are not available on certain architectures.
310 if (none { $os eq $_ } qw(linux kfreebsd knetbsd hurd) or
311 $cpu eq 'hppa') {
312 # Disabled on non-(linux/kfreebsd/knetbsd/hurd).
313 # Disabled on hppa.
314 $use_feature{hardening}{pie} = 0;
316 if (any { $cpu eq $_ } qw(ia64 alpha hppa nios2) or $arch eq 'arm') {
317 # Stack protector disabled on ia64, alpha, hppa, nios2.
318 # "warning: -fstack-protector not supported for this target"
319 # Stack protector disabled on arm (ok on armel).
320 # compiler supports it incorrectly (leads to SEGV)
321 $use_feature{hardening}{stackprotector} = 0;
323 if (any { $cpu eq $_ } qw(ia64 hppa)) {
324 # relro not implemented on ia64, hppa.
325 $use_feature{hardening}{relro} = 0;
328 # Mask features that might be influenced by other flags.
329 if ($flags->get_option_value('optimize-level') == 0) {
330 # glibc 2.16 and later warn when using -O0 and _FORTIFY_SOURCE.
331 $use_feature{hardening}{fortify} = 0;
334 # Handle logical feature interactions.
335 if ($use_feature{hardening}{relro} == 0) {
336 # Disable bindnow if relro is not enabled, since it has no
337 # hardening ability without relro and may incur load penalties.
338 $use_feature{hardening}{bindnow} = 0;
340 if ($use_feature{hardening}{stackprotector} == 0) {
341 # Disable stackprotectorstrong if stackprotector is disabled.
342 $use_feature{hardening}{stackprotectorstrong} = 0;
345 ## Commit
347 # Set used features to their builtin setting if unset.
348 foreach my $area (sort keys %builtin_feature) {
349 while (my ($feature, $enabled) = each %{$builtin_feature{$area}}) {
350 $flags->set_builtin($area, $feature, $enabled);
354 # Store the feature usage.
355 foreach my $area (sort keys %use_feature) {
356 while (my ($feature, $enabled) = each %{$use_feature{$area}}) {
357 $flags->set_feature($area, $feature, $enabled);
362 sub _add_build_flags {
363 my ($self, $flags) = @_;
365 ## Global default flags
367 my @compile_flags = qw(
368 CFLAGS
369 CXXFLAGS
370 OBJCFLAGS
371 OBJCXXFLAGS
372 FFLAGS
373 FCFLAGS
374 GCJFLAGS
377 my $default_flags;
378 my $default_d_flags;
380 my $optimize_level = $flags->get_option_value('optimize-level');
381 $default_flags = "-g -O$optimize_level";
382 if ($optimize_level == 0) {
383 $default_d_flags = '-fdebug';
384 } else {
385 $default_d_flags = '-frelease';
388 $flags->append($_, $default_flags) foreach @compile_flags;
389 $flags->append('DFLAGS', $default_d_flags);
391 ## Area: future
393 if ($flags->use_feature('future', 'lfs')) {
394 $flags->append('CPPFLAGS',
395 '-D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64');
398 if ($flags->use_feature('future', 'time64')) {
399 $flags->append('CPPFLAGS', '-D_TIME_BITS=64');
402 ## Area: qa
404 # Warnings that detect actual bugs.
405 if ($flags->use_feature('qa', 'bug')) {
406 # C flags
407 my @cflags = qw(
408 implicit-function-declaration
410 foreach my $warnflag (@cflags) {
411 $flags->append('CFLAGS', "-Werror=$warnflag");
414 # C/C++ flags
415 my @cfamilyflags = qw(
416 array-bounds
417 clobbered
418 volatile-register-var
420 foreach my $warnflag (@cfamilyflags) {
421 $flags->append('CFLAGS', "-Werror=$warnflag");
422 $flags->append('CXXFLAGS', "-Werror=$warnflag");
426 # Inject dummy canary options to detect issues with build flag propagation.
427 if ($flags->use_feature('qa', 'canary')) {
428 require Digest::MD5;
429 my $id = Digest::MD5::md5_hex(int rand 4096);
431 foreach my $flag (qw(CPPFLAGS CFLAGS OBJCFLAGS CXXFLAGS OBJCXXFLAGS)) {
432 $flags->append($flag, "-D__DEB_CANARY_${flag}_${id}__");
434 $flags->append('LDFLAGS', "-Wl,-z,deb-canary-${id}");
437 ## Area: reproducible
439 # Warn when the __TIME__, __DATE__ and __TIMESTAMP__ macros are used.
440 if ($flags->use_feature('reproducible', 'timeless')) {
441 $flags->append('CPPFLAGS', '-Wdate-time');
444 # Avoid storing the build path in the binaries.
445 if ($flags->use_feature('reproducible', 'fixfilepath') or
446 $flags->use_feature('reproducible', 'fixdebugpath')) {
447 my $build_path = $flags->get_option_value('build-path');
448 my $map;
450 # -ffile-prefix-map is a superset of -fdebug-prefix-map, prefer it
451 # if both are set.
452 if ($flags->use_feature('reproducible', 'fixfilepath')) {
453 $map = '-ffile-prefix-map=' . $build_path . '=.';
454 } else {
455 $map = '-fdebug-prefix-map=' . $build_path . '=.';
458 $flags->append($_, $map) foreach @compile_flags;
461 ## Area: optimize
463 if ($flags->use_feature('optimize', 'lto')) {
464 my $flag = '-flto=auto -ffat-lto-objects';
465 $flags->append($_, $flag) foreach (@compile_flags, 'LDFLAGS');
468 ## Area: sanitize
470 if ($flags->use_feature('sanitize', 'address')) {
471 my $flag = '-fsanitize=address -fno-omit-frame-pointer';
472 $flags->append('CFLAGS', $flag);
473 $flags->append('CXXFLAGS', $flag);
474 $flags->append('LDFLAGS', '-fsanitize=address');
477 if ($flags->use_feature('sanitize', 'thread')) {
478 my $flag = '-fsanitize=thread';
479 $flags->append('CFLAGS', $flag);
480 $flags->append('CXXFLAGS', $flag);
481 $flags->append('LDFLAGS', $flag);
484 if ($flags->use_feature('sanitize', 'leak')) {
485 $flags->append('LDFLAGS', '-fsanitize=leak');
488 if ($flags->use_feature('sanitize', 'undefined')) {
489 my $flag = '-fsanitize=undefined';
490 $flags->append('CFLAGS', $flag);
491 $flags->append('CXXFLAGS', $flag);
492 $flags->append('LDFLAGS', $flag);
495 ## Area: hardening
497 # PIE
498 my $use_pie = $flags->get_feature('hardening', 'pie');
499 my %hardening_builtins = $flags->get_builtins('hardening');
500 if (defined $use_pie && $use_pie && ! $hardening_builtins{pie}) {
501 my $flag = "-specs=$Dpkg::DATADIR/pie-compile.specs";
502 $flags->append($_, $flag) foreach @compile_flags;
503 $flags->append('LDFLAGS', "-specs=$Dpkg::DATADIR/pie-link.specs");
504 } elsif (defined $use_pie && ! $use_pie && $hardening_builtins{pie}) {
505 my $flag = "-specs=$Dpkg::DATADIR/no-pie-compile.specs";
506 $flags->append($_, $flag) foreach @compile_flags;
507 $flags->append('LDFLAGS', "-specs=$Dpkg::DATADIR/no-pie-link.specs");
510 # Stack protector
511 if ($flags->use_feature('hardening', 'stackprotectorstrong')) {
512 my $flag = '-fstack-protector-strong';
513 $flags->append($_, $flag) foreach @compile_flags;
514 } elsif ($flags->use_feature('hardening', 'stackprotector')) {
515 my $flag = '-fstack-protector --param=ssp-buffer-size=4';
516 $flags->append($_, $flag) foreach @compile_flags;
519 # Fortify Source
520 if ($flags->use_feature('hardening', 'fortify')) {
521 $flags->append('CPPFLAGS', '-D_FORTIFY_SOURCE=2');
524 # Format Security
525 if ($flags->use_feature('hardening', 'format')) {
526 my $flag = '-Wformat -Werror=format-security';
527 $flags->append('CFLAGS', $flag);
528 $flags->append('CXXFLAGS', $flag);
529 $flags->append('OBJCFLAGS', $flag);
530 $flags->append('OBJCXXFLAGS', $flag);
533 # Read-only Relocations
534 if ($flags->use_feature('hardening', 'relro')) {
535 $flags->append('LDFLAGS', '-Wl,-z,relro');
538 # Bindnow
539 if ($flags->use_feature('hardening', 'bindnow')) {
540 $flags->append('LDFLAGS', '-Wl,-z,now');
544 sub _build_tainted_by {
545 my $self = shift;
546 my %tainted;
548 foreach my $pathname (qw(/bin /sbin /lib /lib32 /libo32 /libx32 /lib64)) {
549 next unless -l $pathname;
551 my $linkname = readlink $pathname;
552 if ($linkname eq "usr$pathname" or $linkname eq "/usr$pathname") {
553 $tainted{'merged-usr-via-aliased-dirs'} = 1;
554 last;
558 require File::Find;
559 my %usr_local_types = (
560 configs => [ qw(etc) ],
561 includes => [ qw(include) ],
562 programs => [ qw(bin sbin) ],
563 libraries => [ qw(lib) ],
565 foreach my $type (keys %usr_local_types) {
566 File::Find::find({
567 wanted => sub { $tainted{"usr-local-has-$type"} = 1 if -f },
568 no_chdir => 1,
569 }, grep { -d } map { "/usr/local/$_" } @{$usr_local_types{$type}});
572 my @tainted = sort keys %tainted;
573 return @tainted;
576 =head1 CHANGES
578 =head2 Version 0.xx
580 This is a private module.
582 =cut