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.
131 # XXX: This is set to undef so that we can cope with the brokenness
132 # of gcc managing this feature builtin.
135 stackprotectorstrong
=> 1,
143 my %builtin_feature = (
149 $self->init_build_features(\
%use_feature, \
%builtin_feature);
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});
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');
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(
215 if ($abi_bits != 32 or
216 not exists $time32_arch{$arch} or
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
}) {
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
245 if ($build_path =~ m/[^-+:.0-9a-zA-Z~\/_
]/) {
246 $use_feature{reproducible
}{fixfilepath
} = 0;
247 $use_feature{reproducible
}{fixdebugpath
} = 0;
253 if ($opts_build->has('noopt')) {
254 $flags->set_option_value('optimize-level', 0);
256 $flags->set_option_value('optimize-level', 2);
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;
274 # Mask builtin features that are not enabled by default in the compiler.
275 my %builtin_pie_arch = map { $_ => 1 } qw(
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
312 # Disabled on non-(linux/kfreebsd/knetbsd/hurd).
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;
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(
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';
385 $default_d_flags = '-frelease';
388 $flags->append($_, $default_flags) foreach @compile_flags;
389 $flags->append('DFLAGS', $default_d_flags);
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');
404 # Warnings that detect actual bugs.
405 if ($flags->use_feature('qa', 'bug')) {
408 implicit-function-declaration
410 foreach my $warnflag (@cflags) {
411 $flags->append('CFLAGS', "-Werror=$warnflag");
415 my @cfamilyflags = qw(
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')) {
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');
450 # -ffile-prefix-map is a superset of -fdebug-prefix-map, prefer it
452 if ($flags->use_feature('reproducible', 'fixfilepath')) {
453 $map = '-ffile-prefix-map=' . $build_path . '=.';
455 $map = '-fdebug-prefix-map=' . $build_path . '=.';
458 $flags->append($_, $map) foreach @compile_flags;
463 if ($flags->use_feature('optimize', 'lto')) {
464 my $flag = '-flto=auto -ffat-lto-objects';
465 $flags->append($_, $flag) foreach (@compile_flags, 'LDFLAGS');
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);
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");
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;
520 if ($flags->use_feature('hardening', 'fortify')) {
521 $flags->append('CPPFLAGS', '-D_FORTIFY_SOURCE=2');
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');
539 if ($flags->use_feature('hardening', 'bindnow')) {
540 $flags->append('LDFLAGS', '-Wl,-z,now');
544 sub _build_tainted_by
{
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;
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) {
567 wanted
=> sub { $tainted{"usr-local-has-$type"} = 1 if -f
},
569 }, grep { -d
} map { "/usr/local/$_" } @
{$usr_local_types{$type}});
572 my @tainted = sort keys %tainted;
580 This is a private module.