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/>.
25 Dpkg::Vendor::Debian - Debian vendor class
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.
36 package Dpkg
::Vendor
::Debian
0.01;
41 use List
::Util
qw(any none);
45 use Dpkg
::ErrorHandling
;
46 use Dpkg
::Control
::Types
;
48 use parent
qw(Dpkg::Vendor::Default);
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') {
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') {
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.
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)/;
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.
109 # XXX: Should start a deprecation cycle at some point.
113 # XXX: This is set to undef so that we can handle the alias from
114 # the future feature area.
137 # XXX: This is set to undef so that we can cope with the brokenness
138 # of gcc managing this feature builtin.
141 stackprotectorstrong
=> 1,
151 my %builtin_feature = (
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(
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(
248 if ($abi_bits != 32 or
249 not exists $time32_arch{$arch} or
251 $builtin_feature{abi
}{time64
} = 1;
254 $self->init_build_features(\
%use_feature, \
%builtin_feature);
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});
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
}) {
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
296 if ($build_path =~ m/[^-+:.0-9a-zA-Z~\/_
]/) {
297 $use_feature{reproducible
}{fixfilepath
} = 0;
298 $use_feature{reproducible
}{fixdebugpath
} = 0;
304 if ($opts_build->has('noopt')) {
305 $flags->set_option_value('optimize-level', 0);
307 $flags->set_option_value('optimize-level', 2);
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;
325 # Mask features that are not available on certain architectures.
326 if (none
{ $os eq $_ } qw(linux kfreebsd knetbsd hurd) or
328 # Disabled on non-(linux/kfreebsd/knetbsd/hurd).
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;
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(
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';
411 $default_d_flags = '-frelease';
414 $flags->append($_, $default_flags) foreach @compile_flags;
415 $flags->append('DFLAGS', $default_d_flags);
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');
431 # Warnings that detect actual bugs.
432 if ($flags->use_feature('qa', 'bug')) {
435 implicit-function-declaration
437 foreach my $warnflag (@cflags) {
438 $flags->append('CFLAGS', "-Werror=$warnflag");
442 my @cfamilyflags = qw(
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')) {
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');
477 # -ffile-prefix-map is a superset of -fdebug-prefix-map, prefer it
479 if ($flags->use_feature('reproducible', 'fixfilepath')) {
480 $map = '-ffile-prefix-map=' . $build_path . '=.';
482 $map = '-fdebug-prefix-map=' . $build_path . '=.';
485 $flags->append($_, $map) foreach @compile_flags;
490 if ($flags->use_feature('optimize', 'lto')) {
491 my $flag = '-flto=auto -ffat-lto-objects';
492 $flags->append($_, $flag) foreach (@compile_flags, 'LDFLAGS');
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);
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");
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;
547 if ($flags->use_feature('hardening', 'stackclash')) {
548 my $flag = '-fstack-clash-protection';
549 $flags->append($_, $flag) foreach @compile_flags;
553 if ($flags->use_feature('hardening', 'fortify')) {
554 $flags->append('CPPFLAGS', '-D_FORTIFY_SOURCE=2');
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');
572 if ($flags->use_feature('hardening', 'bindnow')) {
573 $flags->append('LDFLAGS', '-Wl,-z,now');
577 if ($flags->use_feature('hardening', 'branch')) {
578 my $cpu = $flags->get_option_value('hardening-branch-cpu');
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
{
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;
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) {
612 wanted
=> sub { $tainted{"usr-local-has-$type"} = 1 if -f
},
614 }, grep { -d
} map { "/usr/local/$_" } @
{$usr_local_types{$type}});
617 my @tainted = sort keys %tainted;
625 This is a private module.