File Coverage

blib/lib/Dpkg/Vendor/Debian.pm
Criterion Covered Total %
statement 115 222 51.8
branch 42 94 44.6
condition 14 51 27.4
subroutine 9 11 81.8
pod 1 1 100.0
total 181 379 47.7


line stmt bran cond sub pod time code
1             # Copyright © 2009-2011 Raphaël Hertzog
2             # Copyright © 2009, 2011-2017 Guillem Jover
3             #
4             # Hardening build flags handling derived from work of:
5             # Copyright © 2009-2011 Kees Cook
6             # Copyright © 2007-2008 Canonical, Ltd.
7             #
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.
12             #
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.
17             #
18             # You should have received a copy of the GNU General Public License
19             # along with this program. If not, see .
20              
21             package Dpkg::Vendor::Debian;
22              
23 11     11   1047 use strict;
  11         22  
  11         353  
24 11     11   56 use warnings;
  11         19  
  11         485  
25              
26             our $VERSION = '0.01';
27              
28 11     11   62 use Dpkg;
  11         22  
  11         532  
29 11     11   77 use Dpkg::Gettext;
  11         36  
  11         699  
30 11     11   96 use Dpkg::ErrorHandling;
  11         23  
  11         875  
31 11     11   83 use Dpkg::Control::Types;
  11         29  
  11         1147  
32              
33 11     11   75 use parent qw(Dpkg::Vendor::Default);
  11         29  
  11         84  
34              
35             =encoding utf8
36              
37             =head1 NAME
38              
39             Dpkg::Vendor::Debian - Debian vendor class
40              
41             =head1 DESCRIPTION
42              
43             This vendor class customizes the behaviour of dpkg scripts for Debian
44             specific behavior and policies.
45              
46             =cut
47              
48             sub run_hook {
49 261     261 1 575 my ($self, $hook, @params) = @_;
50              
51 261 50       1641 if ($hook eq 'package-keyrings') {
    50          
    50          
    50          
    50          
    100          
    50          
    100          
    50          
    50          
    50          
52 0         0 return ('/usr/share/keyrings/debian-keyring.gpg',
53             '/usr/share/keyrings/debian-nonupload.gpg',
54             '/usr/share/keyrings/debian-maintainers.gpg');
55             } elsif ($hook eq 'archive-keyrings') {
56 0         0 return ('/usr/share/keyrings/debian-archive-keyring.gpg');
57             } elsif ($hook eq 'archive-keyrings-historic') {
58 0         0 return ('/usr/share/keyrings/debian-archive-removed-keys.gpg');
59             } elsif ($hook eq 'builtin-build-depends') {
60 0         0 return qw(build-essential:native);
61             } elsif ($hook eq 'builtin-build-conflicts') {
62 0         0 return ();
63             } elsif ($hook eq 'register-custom-fields') {
64             } elsif ($hook eq 'extend-patch-header') {
65 0         0 my ($textref, $ch_info) = @params;
66 0 0       0 if ($ch_info->{'Closes'}) {
67 0         0 foreach my $bug (split(/\s+/, $ch_info->{'Closes'})) {
68 0         0 $$textref .= "Bug-Debian: https://bugs.debian.org/$bug\n";
69             }
70             }
71              
72             # XXX: Layer violation...
73 0         0 require Dpkg::Vendor::Ubuntu;
74 0         0 my $b = Dpkg::Vendor::Ubuntu::find_launchpad_closes($ch_info->{'Changes'});
75 0         0 foreach my $bug (@$b) {
76 0         0 $$textref .= "Bug-Ubuntu: https://bugs.launchpad.net/bugs/$bug\n";
77             }
78             } elsif ($hook eq 'update-buildflags') {
79 3         10 $self->_add_build_flags(@params);
80             } elsif ($hook eq 'builtin-system-build-paths') {
81 0         0 return qw(/build/);
82             } elsif ($hook eq 'build-tainted-by') {
83 0         0 return $self->_build_tainted_by();
84             } elsif ($hook eq 'sanitize-environment') {
85             # Reset umask to a sane default.
86 0         0 umask 0022;
87             # Reset locale to a sane default.
88 0         0 $ENV{LC_COLLATE} = 'C.UTF-8';
89             } else {
90 250         720 return $self->SUPER::run_hook($hook, @params);
91             }
92             }
93              
94             sub _add_build_flags {
95 3     3   6 my ($self, $flags) = @_;
96              
97             # Default feature states.
98 3         30 my %use_feature = (
99             future => {
100             lfs => 0,
101             },
102             qa => {
103             bug => 0,
104             canary => 0,
105             },
106             reproducible => {
107             timeless => 1,
108             fixfilepath => 1,
109             fixdebugpath => 1,
110             },
111             sanitize => {
112             address => 0,
113             thread => 0,
114             leak => 0,
115             undefined => 0,
116             },
117             hardening => {
118             # XXX: This is set to undef so that we can cope with the brokenness
119             # of gcc managing this feature builtin.
120             pie => undef,
121             stackprotector => 1,
122             stackprotectorstrong => 1,
123             fortify => 1,
124             format => 1,
125             relro => 1,
126             bindnow => 0,
127             },
128             );
129              
130 3         19 my %builtin_feature = (
131             hardening => {
132             pie => 1,
133             },
134             );
135              
136             ## Setup
137              
138 3         930 require Dpkg::BuildOptions;
139              
140             # Adjust features based on user or maintainer's desires.
141 3         20 my $opts_build = Dpkg::BuildOptions->new(envvar => 'DEB_BUILD_OPTIONS');
142 3         11 my $opts_maint = Dpkg::BuildOptions->new(envvar => 'DEB_BUILD_MAINT_OPTIONS');
143              
144 3         25 foreach my $area (sort keys %use_feature) {
145 15         42 $opts_build->parse_features($area, $use_feature{$area});
146 15         35 $opts_maint->parse_features($area, $use_feature{$area});
147             }
148              
149 3         948 require Dpkg::Arch;
150              
151 3         69 my $arch = Dpkg::Arch::get_host_arch();
152 3         14 my ($abi, $libc, $os, $cpu) = Dpkg::Arch::debarch_to_debtuple($arch);
153              
154 3 50 33     32 unless (defined $abi and defined $libc and defined $os and defined $cpu) {
      33        
      33        
155 0         0 warning(g_("unknown host architecture '%s'"), $arch);
156 0         0 ($abi, $os, $cpu) = ('', '', '');
157             }
158              
159             ## Global defaults
160              
161 3         6 my $default_flags;
162             my $default_d_flags;
163 3 50       15 if ($opts_build->has('noopt')) {
164 0         0 $default_flags = '-g -O0';
165 0         0 $default_d_flags = '-fdebug';
166             } else {
167 3         8 $default_flags = '-g -O2';
168 3         11 $default_d_flags = '-frelease';
169             }
170 3         33 $flags->append('CFLAGS', $default_flags);
171 3         10 $flags->append('CXXFLAGS', $default_flags);
172 3         9 $flags->append('OBJCFLAGS', $default_flags);
173 3         11 $flags->append('OBJCXXFLAGS', $default_flags);
174 3         21 $flags->append('FFLAGS', $default_flags);
175 3         8 $flags->append('FCFLAGS', $default_flags);
176 3         20 $flags->append('GCJFLAGS', $default_flags);
177 3         9 $flags->append('DFLAGS', $default_d_flags);
178              
179             ## Area: future
180              
181 3 50       13 if ($use_feature{future}{lfs}) {
182 0         0 my ($abi_bits, $abi_endian) = Dpkg::Arch::debarch_to_abiattrs($arch);
183 0         0 my $cpu_bits = Dpkg::Arch::debarch_to_cpubits($arch);
184              
185 0 0 0     0 if ($abi_bits == 32 and $cpu_bits == 32) {
186 0         0 $flags->append('CPPFLAGS',
187             '-D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64');
188             }
189             }
190              
191             ## Area: qa
192              
193             # Warnings that detect actual bugs.
194 3 50       9 if ($use_feature{qa}{bug}) {
195             # C flags
196 0         0 my @cflags = qw(
197             implicit-function-declaration
198             );
199 0         0 foreach my $warnflag (@cflags) {
200 0         0 $flags->append('CFLAGS', "-Werror=$warnflag");
201             }
202              
203             # C/C++ flags
204 0         0 my @cfamilyflags = qw(
205             array-bounds
206             clobbered
207             volatile-register-var
208             );
209 0         0 foreach my $warnflag (@cfamilyflags) {
210 0         0 $flags->append('CFLAGS', "-Werror=$warnflag");
211 0         0 $flags->append('CXXFLAGS', "-Werror=$warnflag");
212             }
213             }
214              
215             # Inject dummy canary options to detect issues with build flag propagation.
216 3 50       8 if ($use_feature{qa}{canary}) {
217 0         0 require Digest::MD5;
218 0         0 my $id = Digest::MD5::md5_hex(int rand 4096);
219              
220 0         0 foreach my $flag (qw(CPPFLAGS CFLAGS OBJCFLAGS CXXFLAGS OBJCXXFLAGS)) {
221 0         0 $flags->append($flag, "-D__DEB_CANARY_${flag}_${id}__");
222             }
223 0         0 $flags->append('LDFLAGS', "-Wl,-z,deb-canary-${id}");
224             }
225              
226             ## Area: reproducible
227              
228 3         12 my $build_path;
229              
230             # Mask features that might have an unsafe usage.
231 3 50 33     12 if ($use_feature{reproducible}{fixfilepath} or
232             $use_feature{reproducible}{fixdebugpath}) {
233 3         30 require Cwd;
234              
235 3   33     51 $build_path = $ENV{DEB_BUILD_PATH} || Cwd::getcwd();
236              
237             # If we have any unsafe character in the path, disable the flag,
238             # so that we do not need to worry about escaping the characters
239             # on output.
240 3 50       29 if ($build_path =~ m/[^-+:.0-9a-zA-Z~\/_]/) {
241 0         0 $use_feature{reproducible}{fixfilepath} = 0;
242 0         0 $use_feature{reproducible}{fixdebugpath} = 0;
243             }
244             }
245              
246             # Warn when the __TIME__, __DATE__ and __TIMESTAMP__ macros are used.
247 3 50       10 if ($use_feature{reproducible}{timeless}) {
248 3         16 $flags->append('CPPFLAGS', '-Wdate-time');
249             }
250              
251             # Avoid storing the build path in the binaries.
252 3 50 33     11 if ($use_feature{reproducible}{fixfilepath} or
253             $use_feature{reproducible}{fixdebugpath}) {
254 3         5 my $map;
255              
256             # -ffile-prefix-map is a superset of -fdebug-prefix-map, prefer it
257             # if both are set.
258 3 50       8 if ($use_feature{reproducible}{fixfilepath}) {
259 3         10 $map = '-ffile-prefix-map=' . $build_path . '=.';
260             } else {
261 0         0 $map = '-fdebug-prefix-map=' . $build_path . '=.';
262             }
263              
264 3         10 $flags->append('CFLAGS', $map);
265 3         10 $flags->append('CXXFLAGS', $map);
266 3         8 $flags->append('OBJCFLAGS', $map);
267 3         9 $flags->append('OBJCXXFLAGS', $map);
268 3         7 $flags->append('FFLAGS', $map);
269 3         8 $flags->append('FCFLAGS', $map);
270 3         7 $flags->append('GCJFLAGS', $map);
271             }
272              
273             ## Area: sanitize
274              
275             # Handle logical feature interactions.
276 3 0 33     9 if ($use_feature{sanitize}{address} and $use_feature{sanitize}{thread}) {
277             # Disable the thread sanitizer when the address one is active, they
278             # are mutually incompatible.
279 0         0 $use_feature{sanitize}{thread} = 0;
280             }
281 3 50 33     27 if ($use_feature{sanitize}{address} or $use_feature{sanitize}{thread}) {
282             # Disable leak sanitizer, it is implied by the address or thread ones.
283 0         0 $use_feature{sanitize}{leak} = 0;
284             }
285              
286 3 50       10 if ($use_feature{sanitize}{address}) {
287 0         0 my $flag = '-fsanitize=address -fno-omit-frame-pointer';
288 0         0 $flags->append('CFLAGS', $flag);
289 0         0 $flags->append('CXXFLAGS', $flag);
290 0         0 $flags->append('LDFLAGS', '-fsanitize=address');
291             }
292              
293 3 50       8 if ($use_feature{sanitize}{thread}) {
294 0         0 my $flag = '-fsanitize=thread';
295 0         0 $flags->append('CFLAGS', $flag);
296 0         0 $flags->append('CXXFLAGS', $flag);
297 0         0 $flags->append('LDFLAGS', $flag);
298             }
299              
300 3 50       15 if ($use_feature{sanitize}{leak}) {
301 0         0 $flags->append('LDFLAGS', '-fsanitize=leak');
302             }
303              
304 3 50       16 if ($use_feature{sanitize}{undefined}) {
305 0         0 my $flag = '-fsanitize=undefined';
306 0         0 $flags->append('CFLAGS', $flag);
307 0         0 $flags->append('CXXFLAGS', $flag);
308 0         0 $flags->append('LDFLAGS', $flag);
309             }
310              
311             ## Area: hardening
312              
313             # Mask builtin features that are not enabled by default in the compiler.
314 3         12 my %builtin_pie_arch = map { $_ => 1 } qw(
  54         116  
315             amd64
316             arm64
317             armel
318             armhf
319             hurd-i386
320             i386
321             kfreebsd-amd64
322             kfreebsd-i386
323             mips
324             mipsel
325             mips64el
326             powerpc
327             ppc64
328             ppc64el
329             riscv64
330             s390x
331             sparc
332             sparc64
333             );
334 3 50       14 if (not exists $builtin_pie_arch{$arch}) {
335 0         0 $builtin_feature{hardening}{pie} = 0;
336             }
337              
338             # Mask features that are not available on certain architectures.
339 3 50 33     47 if ($os !~ /^(?:linux|kfreebsd|knetbsd|hurd)$/ or
340             $cpu =~ /^(?:hppa|avr32)$/) {
341             # Disabled on non-(linux/kfreebsd/knetbsd/hurd).
342             # Disabled on hppa, avr32
343             # (#574716).
344 0         0 $use_feature{hardening}{pie} = 0;
345             }
346 3 50 33     18 if ($cpu =~ /^(?:ia64|alpha|hppa|nios2)$/ or $arch eq 'arm') {
347             # Stack protector disabled on ia64, alpha, hppa, nios2.
348             # "warning: -fstack-protector not supported for this target"
349             # Stack protector disabled on arm (ok on armel).
350             # compiler supports it incorrectly (leads to SEGV)
351 0         0 $use_feature{hardening}{stackprotector} = 0;
352             }
353 3 50       10 if ($cpu =~ /^(?:ia64|hppa|avr32)$/) {
354             # relro not implemented on ia64, hppa, avr32.
355 0         0 $use_feature{hardening}{relro} = 0;
356             }
357              
358             # Mask features that might be influenced by other flags.
359 3 50       11 if ($opts_build->has('noopt')) {
360             # glibc 2.16 and later warn when using -O0 and _FORTIFY_SOURCE.
361 0         0 $use_feature{hardening}{fortify} = 0;
362             }
363              
364             # Handle logical feature interactions.
365 3 50       12 if ($use_feature{hardening}{relro} == 0) {
366             # Disable bindnow if relro is not enabled, since it has no
367             # hardening ability without relro and may incur load penalties.
368 0         0 $use_feature{hardening}{bindnow} = 0;
369             }
370 3 50       16 if ($use_feature{hardening}{stackprotector} == 0) {
371             # Disable stackprotectorstrong if stackprotector is disabled.
372 0         0 $use_feature{hardening}{stackprotectorstrong} = 0;
373             }
374              
375             # PIE
376 3 50 33     21 if (defined $use_feature{hardening}{pie} and
    50 0        
      33        
      33        
377             $use_feature{hardening}{pie} and
378             not $builtin_feature{hardening}{pie}) {
379 0         0 my $flag = "-specs=$Dpkg::DATADIR/pie-compile.specs";
380 0         0 $flags->append('CFLAGS', $flag);
381 0         0 $flags->append('OBJCFLAGS', $flag);
382 0         0 $flags->append('OBJCXXFLAGS', $flag);
383 0         0 $flags->append('FFLAGS', $flag);
384 0         0 $flags->append('FCFLAGS', $flag);
385 0         0 $flags->append('CXXFLAGS', $flag);
386 0         0 $flags->append('GCJFLAGS', $flag);
387 0         0 $flags->append('LDFLAGS', "-specs=$Dpkg::DATADIR/pie-link.specs");
388             } elsif (defined $use_feature{hardening}{pie} and
389             not $use_feature{hardening}{pie} and
390             $builtin_feature{hardening}{pie}) {
391 0         0 my $flag = "-specs=$Dpkg::DATADIR/no-pie-compile.specs";
392 0         0 $flags->append('CFLAGS', $flag);
393 0         0 $flags->append('OBJCFLAGS', $flag);
394 0         0 $flags->append('OBJCXXFLAGS', $flag);
395 0         0 $flags->append('FFLAGS', $flag);
396 0         0 $flags->append('FCFLAGS', $flag);
397 0         0 $flags->append('CXXFLAGS', $flag);
398 0         0 $flags->append('GCJFLAGS', $flag);
399 0         0 $flags->append('LDFLAGS', "-specs=$Dpkg::DATADIR/no-pie-link.specs");
400             }
401              
402             # Stack protector
403 3 50       8 if ($use_feature{hardening}{stackprotectorstrong}) {
    0          
404 3         7 my $flag = '-fstack-protector-strong';
405 3         9 $flags->append('CFLAGS', $flag);
406 3         8 $flags->append('OBJCFLAGS', $flag);
407 3         9 $flags->append('OBJCXXFLAGS', $flag);
408 3         10 $flags->append('FFLAGS', $flag);
409 3         8 $flags->append('FCFLAGS', $flag);
410 3         8 $flags->append('CXXFLAGS', $flag);
411 3         8 $flags->append('GCJFLAGS', $flag);
412             } elsif ($use_feature{hardening}{stackprotector}) {
413 0         0 my $flag = '-fstack-protector --param=ssp-buffer-size=4';
414 0         0 $flags->append('CFLAGS', $flag);
415 0         0 $flags->append('OBJCFLAGS', $flag);
416 0         0 $flags->append('OBJCXXFLAGS', $flag);
417 0         0 $flags->append('FFLAGS', $flag);
418 0         0 $flags->append('FCFLAGS', $flag);
419 0         0 $flags->append('CXXFLAGS', $flag);
420 0         0 $flags->append('GCJFLAGS', $flag);
421             }
422              
423             # Fortify Source
424 3 50       19 if ($use_feature{hardening}{fortify}) {
425 3         10 $flags->append('CPPFLAGS', '-D_FORTIFY_SOURCE=2');
426             }
427              
428             # Format Security
429 3 50       16 if ($use_feature{hardening}{format}) {
430 3         6 my $flag = '-Wformat -Werror=format-security';
431 3         18 $flags->append('CFLAGS', $flag);
432 3         17 $flags->append('CXXFLAGS', $flag);
433 3         9 $flags->append('OBJCFLAGS', $flag);
434 3         5 $flags->append('OBJCXXFLAGS', $flag);
435             }
436              
437             # Read-only Relocations
438 3 50       9 if ($use_feature{hardening}{relro}) {
439 3         11 $flags->append('LDFLAGS', '-Wl,-z,relro');
440             }
441              
442             # Bindnow
443 3 50       8 if ($use_feature{hardening}{bindnow}) {
444 0         0 $flags->append('LDFLAGS', '-Wl,-z,now');
445             }
446              
447             ## Commit
448              
449             # Set used features to their builtin setting if unset.
450 3         20 foreach my $area (sort keys %builtin_feature) {
451 3         6 foreach my $feature (keys %{$builtin_feature{$area}}) {
  3         18  
452 3   33     46 $use_feature{$area}{$feature} //= $builtin_feature{$area}{$feature};
453             }
454             }
455              
456             # Store the feature usage.
457 3         19 foreach my $area (sort keys %use_feature) {
458 15         19 while (my ($feature, $enabled) = each %{$use_feature{$area}}) {
  66         232  
459 51         103 $flags->set_feature($area, $feature, $enabled);
460             }
461             }
462             }
463              
464             sub _build_tainted_by {
465 0     0     my $self = shift;
466 0           my %tainted;
467              
468 0           foreach my $pathname (qw(/bin /sbin /lib /lib32 /libo32 /libx32 /lib64)) {
469 0 0         next unless -l $pathname;
470              
471 0           my $linkname = readlink $pathname;
472 0 0 0       if ($linkname eq "usr$pathname" or $linkname eq "/usr$pathname") {
473 0           $tainted{'merged-usr-via-aliased-dirs'} = 1;
474 0           last;
475             }
476             }
477              
478 0           require File::Find;
479 0           my %usr_local_types = (
480             configs => [ qw(etc) ],
481             includes => [ qw(include) ],
482             programs => [ qw(bin sbin) ],
483             libraries => [ qw(lib) ],
484             );
485 0           foreach my $type (keys %usr_local_types) {
486             File::Find::find({
487 0 0   0     wanted => sub { $tainted{"usr-local-has-$type"} = 1 if -f },
488             no_chdir => 1,
489 0           }, grep { -d } map { "/usr/local/$_" } @{$usr_local_types{$type}});
  0            
  0            
  0            
490             }
491              
492 0           my @tainted = sort keys %tainted;
493 0           return @tainted;
494             }
495              
496             =head1 CHANGES
497              
498             =head2 Version 0.xx
499              
500             This is a private module.
501              
502             =cut
503              
504             1;