scripts/mk: On dpkg-build-api >= 1 include buildtools.mk in default.mk
[dpkg.git] / scripts / Dpkg / Vendor / Debian.pm
blob1cc2393d663bc1e4f87e132e992651cf223ffefc
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 # XXX: Should start a deprecation cycle at some point.
110 lfs => 0,
112 abi => {
113 # XXX: This is set to undef so that we can handle the alias from
114 # the future feature area.
115 lfs => undef,
116 time64 => 0,
118 qa => {
119 bug => 0,
120 canary => 0,
122 reproducible => {
123 timeless => 1,
124 fixfilepath => 1,
125 fixdebugpath => 1,
127 optimize => {
128 lto => 0,
130 sanitize => {
131 address => 0,
132 thread => 0,
133 leak => 0,
134 undefined => 0,
136 hardening => {
137 # XXX: This is set to undef so that we can cope with the brokenness
138 # of gcc managing this feature builtin.
139 pie => undef,
140 stackprotector => 1,
141 stackprotectorstrong => 1,
142 stackclash => 1,
143 fortify => 1,
144 format => 1,
145 relro => 1,
146 bindnow => 0,
147 branch => 1,
151 my %builtin_feature = (
152 abi => {
153 lfs => 0,
154 time64 => 0,
156 hardening => {
157 pie => 1,
161 require Dpkg::Arch;
163 my $arch = Dpkg::Arch::get_host_arch();
164 my ($abi, $libc, $os, $cpu) = Dpkg::Arch::debarch_to_debtuple($arch);
165 my ($abi_bits, $abi_endian) = Dpkg::Arch::debarch_to_abiattrs($arch);
167 unless (defined $abi and defined $libc and defined $os and defined $cpu) {
168 warning(g_("unknown host architecture '%s'"), $arch);
169 ($abi, $os, $cpu) = ('', '', '');
171 unless (defined $abi_bits and defined $abi_endian) {
172 warning(g_("unknown abi attributes for architecture '%s'"), $arch);
173 ($abi_bits, $abi_endian) = (0, 'unknown');
176 # Mask builtin features that are not enabled by default in the compiler.
177 my %builtin_pie_arch = map { $_ => 1 } qw(
178 amd64
179 arm64
180 armel
181 armhf
182 hurd-amd64
183 hurd-i386
184 i386
185 kfreebsd-amd64
186 kfreebsd-i386
187 mips
188 mips64
189 mips64el
190 mips64r6
191 mips64r6el
192 mipsel
193 mipsn32
194 mipsn32el
195 mipsn32r6
196 mipsn32r6el
197 mipsr6
198 mipsr6el
199 powerpc
200 ppc64
201 ppc64el
202 riscv64
203 s390x
204 sparc
205 sparc64
207 if (not exists $builtin_pie_arch{$arch}) {
208 $builtin_feature{hardening}{pie} = 0;
211 if ($abi_bits != 32) {
212 $builtin_feature{abi}{lfs} = 1;
215 # On glibc, new ports default to time64, old ports currently default
216 # to time32, so we track the latter as that is a list that is not
217 # going to grow further, and might shrink.
218 # On musl libc based systems all ports use time64.
219 my %time32_arch = map { $_ => 1 } qw(
221 armeb
222 armel
223 armhf
224 hppa
225 i386
226 hurd-i386
227 kfreebsd-i386
228 m68k
229 mips
230 mipsel
231 mipsn32
232 mipsn32el
233 mipsn32r6
234 mipsn32r6el
235 mipsr6
236 mipsr6el
237 nios2
238 powerpc
239 powerpcel
240 powerpcspe
241 s390
243 sh3eb
245 sh4eb
246 sparc
248 if ($abi_bits != 32 or
249 not exists $time32_arch{$arch} or
250 $libc eq 'musl') {
251 $builtin_feature{abi}{time64} = 1;
254 $self->init_build_features(\%use_feature, \%builtin_feature);
256 ## Setup
258 require Dpkg::BuildOptions;
260 # Adjust features based on user or maintainer's desires.
261 my $opts_build = Dpkg::BuildOptions->new(envvar => 'DEB_BUILD_OPTIONS');
262 my $opts_maint = Dpkg::BuildOptions->new(envvar => 'DEB_BUILD_MAINT_OPTIONS');
264 foreach my $area (sort keys %use_feature) {
265 $opts_build->parse_features($area, $use_feature{$area});
266 $opts_maint->parse_features($area, $use_feature{$area});
269 ## Area: abi
271 if ($use_feature{abi}{time64} && ! $builtin_feature{abi}{time64}) {
272 # On glibc 64-bit time_t support requires LFS.
273 $use_feature{abi}{lfs} = 1 if $libc eq 'gnu';
276 # XXX: Handle lfs alias from future abi feature area.
277 $use_feature{abi}{lfs} //= $use_feature{future}{lfs};
278 # XXX: Once the feature is set in the abi area, we always override the
279 # one in the future area.
280 $use_feature{future}{lfs} = $use_feature{abi}{lfs};
282 ## Area: reproducible
284 # Mask features that might have an unsafe usage.
285 if ($use_feature{reproducible}{fixfilepath} or
286 $use_feature{reproducible}{fixdebugpath}) {
287 require Cwd;
289 my $build_path =$ENV{DEB_BUILD_PATH} || Cwd::getcwd();
291 $flags->set_option_value('build-path', $build_path);
293 # If we have any unsafe character in the path, disable the flag,
294 # so that we do not need to worry about escaping the characters
295 # on output.
296 if ($build_path =~ m/[^-+:.0-9a-zA-Z~\/_]/) {
297 $use_feature{reproducible}{fixfilepath} = 0;
298 $use_feature{reproducible}{fixdebugpath} = 0;
302 ## Area: optimize
304 if ($opts_build->has('noopt')) {
305 $flags->set_option_value('optimize-level', 0);
306 } else {
307 $flags->set_option_value('optimize-level', 2);
310 ## Area: sanitize
312 # Handle logical feature interactions.
313 if ($use_feature{sanitize}{address} and $use_feature{sanitize}{thread}) {
314 # Disable the thread sanitizer when the address one is active, they
315 # are mutually incompatible.
316 $use_feature{sanitize}{thread} = 0;
318 if ($use_feature{sanitize}{address} or $use_feature{sanitize}{thread}) {
319 # Disable leak sanitizer, it is implied by the address or thread ones.
320 $use_feature{sanitize}{leak} = 0;
323 ## Area: hardening
325 # Mask features that are not available on certain architectures.
326 if (none { $os eq $_ } qw(linux kfreebsd knetbsd hurd) or
327 $cpu eq 'hppa') {
328 # Disabled on non-(linux/kfreebsd/knetbsd/hurd).
329 # Disabled on hppa.
330 $use_feature{hardening}{pie} = 0;
332 if (any { $cpu eq $_ } qw(ia64 alpha hppa nios2) or $arch eq 'arm') {
333 # Stack protector disabled on ia64, alpha, hppa, nios2.
334 # "warning: -fstack-protector not supported for this target"
335 # Stack protector disabled on arm (ok on armel).
336 # compiler supports it incorrectly (leads to SEGV)
337 $use_feature{hardening}{stackprotector} = 0;
339 if (none { $cpu eq $_ } qw(amd64 arm64 armhf armel)) {
340 # Stack clash protector only available on amd64 and arm.
341 $use_feature{hardening}{stackclash} = 0;
343 if (any { $cpu eq $_ } qw(ia64 hppa)) {
344 # relro not implemented on ia64, hppa.
345 $use_feature{hardening}{relro} = 0;
347 if (none { $cpu eq $_ } qw(amd64 arm64)) {
348 # On amd64 use -fcf-protection.
349 # On arm64 use -mbranch-protection=standard.
350 $use_feature{hardening}{branch} = 0;
352 $flags->set_option_value('hardening-branch-cpu', $cpu);
354 # Mask features that might be influenced by other flags.
355 if ($flags->get_option_value('optimize-level') == 0) {
356 # glibc 2.16 and later warn when using -O0 and _FORTIFY_SOURCE.
357 $use_feature{hardening}{fortify} = 0;
360 # Handle logical feature interactions.
361 if ($use_feature{hardening}{relro} == 0) {
362 # Disable bindnow if relro is not enabled, since it has no
363 # hardening ability without relro and may incur load penalties.
364 $use_feature{hardening}{bindnow} = 0;
366 if ($use_feature{hardening}{stackprotector} == 0) {
367 # Disable stackprotectorstrong if stackprotector is disabled.
368 $use_feature{hardening}{stackprotectorstrong} = 0;
371 ## Commit
373 # Set used features to their builtin setting if unset.
374 foreach my $area (sort keys %builtin_feature) {
375 while (my ($feature, $enabled) = each %{$builtin_feature{$area}}) {
376 $flags->set_builtin($area, $feature, $enabled);
380 # Store the feature usage.
381 foreach my $area (sort keys %use_feature) {
382 while (my ($feature, $enabled) = each %{$use_feature{$area}}) {
383 $flags->set_feature($area, $feature, $enabled);
388 sub _add_build_flags {
389 my ($self, $flags) = @_;
391 ## Global default flags
393 my @compile_flags = qw(
394 CFLAGS
395 CXXFLAGS
396 OBJCFLAGS
397 OBJCXXFLAGS
398 FFLAGS
399 FCFLAGS
400 GCJFLAGS
403 my $default_flags;
404 my $default_d_flags;
406 my $optimize_level = $flags->get_option_value('optimize-level');
407 $default_flags = "-g -O$optimize_level";
408 if ($optimize_level == 0) {
409 $default_d_flags = '-fdebug';
410 } else {
411 $default_d_flags = '-frelease';
414 $flags->append($_, $default_flags) foreach @compile_flags;
415 $flags->append('DFLAGS', $default_d_flags);
417 ## Area: abi
419 my %abi_builtins = $flags->get_builtins('abi');
420 if ($flags->use_feature('abi', 'lfs') && ! $abi_builtins{lfs}) {
421 $flags->append('CPPFLAGS',
422 '-D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64');
425 if ($flags->use_feature('abi', 'time64') && ! $abi_builtins{time64}) {
426 $flags->append('CPPFLAGS', '-D_TIME_BITS=64');
429 ## Area: qa
431 # Warnings that detect actual bugs.
432 if ($flags->use_feature('qa', 'bug')) {
433 # C flags
434 my @cflags = qw(
435 implicit-function-declaration
437 foreach my $warnflag (@cflags) {
438 $flags->append('CFLAGS', "-Werror=$warnflag");
441 # C/C++ flags
442 my @cfamilyflags = qw(
443 array-bounds
444 clobbered
445 volatile-register-var
447 foreach my $warnflag (@cfamilyflags) {
448 $flags->append('CFLAGS', "-Werror=$warnflag");
449 $flags->append('CXXFLAGS', "-Werror=$warnflag");
453 # Inject dummy canary options to detect issues with build flag propagation.
454 if ($flags->use_feature('qa', 'canary')) {
455 require Digest::MD5;
456 my $id = Digest::MD5::md5_hex(int rand 4096);
458 foreach my $flag (qw(CPPFLAGS CFLAGS OBJCFLAGS CXXFLAGS OBJCXXFLAGS)) {
459 $flags->append($flag, "-D__DEB_CANARY_${flag}_${id}__");
461 $flags->append('LDFLAGS', "-Wl,-z,deb-canary-${id}");
464 ## Area: reproducible
466 # Warn when the __TIME__, __DATE__ and __TIMESTAMP__ macros are used.
467 if ($flags->use_feature('reproducible', 'timeless')) {
468 $flags->append('CPPFLAGS', '-Wdate-time');
471 # Avoid storing the build path in the binaries.
472 if ($flags->use_feature('reproducible', 'fixfilepath') or
473 $flags->use_feature('reproducible', 'fixdebugpath')) {
474 my $build_path = $flags->get_option_value('build-path');
475 my $map;
477 # -ffile-prefix-map is a superset of -fdebug-prefix-map, prefer it
478 # if both are set.
479 if ($flags->use_feature('reproducible', 'fixfilepath')) {
480 $map = '-ffile-prefix-map=' . $build_path . '=.';
481 } else {
482 $map = '-fdebug-prefix-map=' . $build_path . '=.';
485 $flags->append($_, $map) foreach @compile_flags;
488 ## Area: optimize
490 if ($flags->use_feature('optimize', 'lto')) {
491 my $flag = '-flto=auto -ffat-lto-objects';
492 $flags->append($_, $flag) foreach (@compile_flags, 'LDFLAGS');
495 ## Area: sanitize
497 if ($flags->use_feature('sanitize', 'address')) {
498 my $flag = '-fsanitize=address -fno-omit-frame-pointer';
499 $flags->append('CFLAGS', $flag);
500 $flags->append('CXXFLAGS', $flag);
501 $flags->append('LDFLAGS', '-fsanitize=address');
504 if ($flags->use_feature('sanitize', 'thread')) {
505 my $flag = '-fsanitize=thread';
506 $flags->append('CFLAGS', $flag);
507 $flags->append('CXXFLAGS', $flag);
508 $flags->append('LDFLAGS', $flag);
511 if ($flags->use_feature('sanitize', 'leak')) {
512 $flags->append('LDFLAGS', '-fsanitize=leak');
515 if ($flags->use_feature('sanitize', 'undefined')) {
516 my $flag = '-fsanitize=undefined';
517 $flags->append('CFLAGS', $flag);
518 $flags->append('CXXFLAGS', $flag);
519 $flags->append('LDFLAGS', $flag);
522 ## Area: hardening
524 # PIE
525 my $use_pie = $flags->get_feature('hardening', 'pie');
526 my %hardening_builtins = $flags->get_builtins('hardening');
527 if (defined $use_pie && $use_pie && ! $hardening_builtins{pie}) {
528 my $flag = "-specs=$Dpkg::DATADIR/pie-compile.specs";
529 $flags->append($_, $flag) foreach @compile_flags;
530 $flags->append('LDFLAGS', "-specs=$Dpkg::DATADIR/pie-link.specs");
531 } elsif (defined $use_pie && ! $use_pie && $hardening_builtins{pie}) {
532 my $flag = "-specs=$Dpkg::DATADIR/no-pie-compile.specs";
533 $flags->append($_, $flag) foreach @compile_flags;
534 $flags->append('LDFLAGS', "-specs=$Dpkg::DATADIR/no-pie-link.specs");
537 # Stack protector
538 if ($flags->use_feature('hardening', 'stackprotectorstrong')) {
539 my $flag = '-fstack-protector-strong';
540 $flags->append($_, $flag) foreach @compile_flags;
541 } elsif ($flags->use_feature('hardening', 'stackprotector')) {
542 my $flag = '-fstack-protector --param=ssp-buffer-size=4';
543 $flags->append($_, $flag) foreach @compile_flags;
546 # Stack clash
547 if ($flags->use_feature('hardening', 'stackclash')) {
548 my $flag = '-fstack-clash-protection';
549 $flags->append($_, $flag) foreach @compile_flags;
552 # Fortify Source
553 if ($flags->use_feature('hardening', 'fortify')) {
554 $flags->append('CPPFLAGS', '-D_FORTIFY_SOURCE=2');
557 # Format Security
558 if ($flags->use_feature('hardening', 'format')) {
559 my $flag = '-Wformat -Werror=format-security';
560 $flags->append('CFLAGS', $flag);
561 $flags->append('CXXFLAGS', $flag);
562 $flags->append('OBJCFLAGS', $flag);
563 $flags->append('OBJCXXFLAGS', $flag);
566 # Read-only Relocations
567 if ($flags->use_feature('hardening', 'relro')) {
568 $flags->append('LDFLAGS', '-Wl,-z,relro');
571 # Bindnow
572 if ($flags->use_feature('hardening', 'bindnow')) {
573 $flags->append('LDFLAGS', '-Wl,-z,now');
576 # Branch protection
577 if ($flags->use_feature('hardening', 'branch')) {
578 my $cpu = $flags->get_option_value('hardening-branch-cpu');
579 my $flag;
580 if ($cpu eq 'arm64') {
581 $flag = '-mbranch-protection=standard';
582 } elsif ($cpu eq 'amd64') {
583 $flag = '-fcf-protection';
585 $flags->append($_, $flag) foreach @compile_flags;
589 sub _build_tainted_by {
590 my $self = shift;
591 my %tainted;
593 foreach my $pathname (qw(/bin /sbin /lib /lib32 /libo32 /libx32 /lib64)) {
594 next unless -l $pathname;
596 my $linkname = readlink $pathname;
597 if ($linkname eq "usr$pathname" or $linkname eq "/usr$pathname") {
598 $tainted{'merged-usr-via-aliased-dirs'} = 1;
599 last;
603 require File::Find;
604 my %usr_local_types = (
605 configs => [ qw(etc) ],
606 includes => [ qw(include) ],
607 programs => [ qw(bin sbin) ],
608 libraries => [ qw(lib) ],
610 foreach my $type (keys %usr_local_types) {
611 File::Find::find({
612 wanted => sub { $tainted{"usr-local-has-$type"} = 1 if -f },
613 no_chdir => 1,
614 }, grep { -d } map { "/usr/local/$_" } @{$usr_local_types{$type}});
617 my @tainted = sort keys %tainted;
618 return @tainted;
621 =head1 CHANGES
623 =head2 Version 0.xx
625 This is a private module.
627 =cut