File Coverage

blib/lib/Dpkg/Vendor/Debian.pm
Criterion Covered Total %
statement 113 219 51.6
branch 42 94 44.6
condition 14 51 27.4
subroutine 9 11 81.8
pod 1 1 100.0
total 179 376 47.6


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   1035 use strict;
  11         24  
  11         320  
24 11     11   55 use warnings;
  11         18  
  11         460  
25              
26             our $VERSION = '0.01';
27              
28 11     11   62 use Dpkg;
  11         22  
  11         524  
29 11     11   69 use Dpkg::Gettext;
  11         41  
  11         636  
30 11     11   101 use Dpkg::ErrorHandling;
  11         25  
  11         841  
31 11     11   74 use Dpkg::Control::Types;
  11         23  
  11         975  
32              
33 11     11   89 use parent qw(Dpkg::Vendor::Default);
  11         22  
  11         69  
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 572 my ($self, $hook, @params) = @_;
50              
51 261 50       1543 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         11 $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         693 return $self->SUPER::run_hook($hook, @params);
91             }
92             }
93              
94             sub _add_build_flags {
95 3     3   7 my ($self, $flags) = @_;
96              
97             # Default feature states.
98 3         42 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 => 0,
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         10 my %builtin_feature = (
131             hardening => {
132             pie => 1,
133             },
134             );
135              
136             ## Setup
137              
138 3         905 require Dpkg::BuildOptions;
139              
140             # Adjust features based on user or maintainer's desires.
141 3         21 my $opts_build = Dpkg::BuildOptions->new(envvar => 'DEB_BUILD_OPTIONS');
142 3         9 my $opts_maint = Dpkg::BuildOptions->new(envvar => 'DEB_BUILD_MAINT_OPTIONS');
143              
144 3         53 foreach my $area (sort keys %use_feature) {
145 15         44 $opts_build->parse_features($area, $use_feature{$area});
146 15         35 $opts_maint->parse_features($area, $use_feature{$area});
147             }
148              
149 3         941 require Dpkg::Arch;
150              
151 3         12 my $arch = Dpkg::Arch::get_host_arch();
152 3         11 my ($abi, $libc, $os, $cpu) = Dpkg::Arch::debarch_to_debtuple($arch);
153              
154 3 50 33     64 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         8 my $default_flags;
162 3 50       17 if ($opts_build->has('noopt')) {
163 0         0 $default_flags = '-g -O0';
164             } else {
165 3         8 $default_flags = '-g -O2';
166             }
167 3         29 $flags->append('CFLAGS', $default_flags);
168 3         11 $flags->append('CXXFLAGS', $default_flags);
169 3         9 $flags->append('OBJCFLAGS', $default_flags);
170 3         9 $flags->append('OBJCXXFLAGS', $default_flags);
171 3         10 $flags->append('FFLAGS', $default_flags);
172 3         8 $flags->append('FCFLAGS', $default_flags);
173 3         28 $flags->append('GCJFLAGS', $default_flags);
174              
175             ## Area: future
176              
177 3 50       11 if ($use_feature{future}{lfs}) {
178 0         0 my ($abi_bits, $abi_endian) = Dpkg::Arch::debarch_to_abiattrs($arch);
179 0         0 my $cpu_bits = Dpkg::Arch::debarch_to_cpubits($arch);
180              
181 0 0 0     0 if ($abi_bits == 32 and $cpu_bits == 32) {
182 0         0 $flags->append('CPPFLAGS',
183             '-D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64');
184             }
185             }
186              
187             ## Area: qa
188              
189             # Warnings that detect actual bugs.
190 3 50       17 if ($use_feature{qa}{bug}) {
191             # C flags
192 0         0 my @cflags = qw(
193             implicit-function-declaration
194             );
195 0         0 foreach my $warnflag (@cflags) {
196 0         0 $flags->append('CFLAGS', "-Werror=$warnflag");
197             }
198              
199             # C/C++ flags
200 0         0 my @cfamilyflags = qw(
201             array-bounds
202             clobbered
203             volatile-register-var
204             );
205 0         0 foreach my $warnflag (@cfamilyflags) {
206 0         0 $flags->append('CFLAGS', "-Werror=$warnflag");
207 0         0 $flags->append('CXXFLAGS', "-Werror=$warnflag");
208             }
209             }
210              
211             # Inject dummy canary options to detect issues with build flag propagation.
212 3 50       19 if ($use_feature{qa}{canary}) {
213 0         0 require Digest::MD5;
214 0         0 my $id = Digest::MD5::md5_hex(int rand 4096);
215              
216 0         0 foreach my $flag (qw(CPPFLAGS CFLAGS OBJCFLAGS CXXFLAGS OBJCXXFLAGS)) {
217 0         0 $flags->append($flag, "-D__DEB_CANARY_${flag}_${id}__");
218             }
219 0         0 $flags->append('LDFLAGS', "-Wl,-z,deb-canary-${id}");
220             }
221              
222             ## Area: reproducible
223              
224 3         7 my $build_path;
225              
226             # Mask features that might have an unsafe usage.
227 3 50 33     27 if ($use_feature{reproducible}{fixfilepath} or
228             $use_feature{reproducible}{fixdebugpath}) {
229 3         26 require Cwd;
230              
231 3   33     53 $build_path = $ENV{DEB_BUILD_PATH} || Cwd::getcwd();
232              
233             # If we have any unsafe character in the path, disable the flag,
234             # so that we do not need to worry about escaping the characters
235             # on output.
236 3 50       24 if ($build_path =~ m/[^-+:.0-9a-zA-Z~\/_]/) {
237 0         0 $use_feature{reproducible}{fixfilepath} = 0;
238 0         0 $use_feature{reproducible}{fixdebugpath} = 0;
239             }
240             }
241              
242             # Warn when the __TIME__, __DATE__ and __TIMESTAMP__ macros are used.
243 3 50       9 if ($use_feature{reproducible}{timeless}) {
244 3         15 $flags->append('CPPFLAGS', '-Wdate-time');
245             }
246              
247             # Avoid storing the build path in the binaries.
248 3 50 33     30 if ($use_feature{reproducible}{fixfilepath} or
249             $use_feature{reproducible}{fixdebugpath}) {
250 3         6 my $map;
251              
252             # -ffile-prefix-map is a superset of -fdebug-prefix-map, prefer it
253             # if both are set.
254 3 50       7 if ($use_feature{reproducible}{fixfilepath}) {
255 0         0 $map = '-ffile-prefix-map=' . $build_path . '=.';
256             } else {
257 3         10 $map = '-fdebug-prefix-map=' . $build_path . '=.';
258             }
259              
260 3         10 $flags->append('CFLAGS', $map);
261 3         8 $flags->append('CXXFLAGS', $map);
262 3         17 $flags->append('OBJCFLAGS', $map);
263 3         8 $flags->append('OBJCXXFLAGS', $map);
264 3         9 $flags->append('FFLAGS', $map);
265 3         9 $flags->append('FCFLAGS', $map);
266 3         9 $flags->append('GCJFLAGS', $map);
267             }
268              
269             ## Area: sanitize
270              
271             # Handle logical feature interactions.
272 3 0 33     11 if ($use_feature{sanitize}{address} and $use_feature{sanitize}{thread}) {
273             # Disable the thread sanitizer when the address one is active, they
274             # are mutually incompatible.
275 0         0 $use_feature{sanitize}{thread} = 0;
276             }
277 3 50 33     20 if ($use_feature{sanitize}{address} or $use_feature{sanitize}{thread}) {
278             # Disable leak sanitizer, it is implied by the address or thread ones.
279 0         0 $use_feature{sanitize}{leak} = 0;
280             }
281              
282 3 50       10 if ($use_feature{sanitize}{address}) {
283 0         0 my $flag = '-fsanitize=address -fno-omit-frame-pointer';
284 0         0 $flags->append('CFLAGS', $flag);
285 0         0 $flags->append('CXXFLAGS', $flag);
286 0         0 $flags->append('LDFLAGS', '-fsanitize=address');
287             }
288              
289 3 50       8 if ($use_feature{sanitize}{thread}) {
290 0         0 my $flag = '-fsanitize=thread';
291 0         0 $flags->append('CFLAGS', $flag);
292 0         0 $flags->append('CXXFLAGS', $flag);
293 0         0 $flags->append('LDFLAGS', $flag);
294             }
295              
296 3 50       15 if ($use_feature{sanitize}{leak}) {
297 0         0 $flags->append('LDFLAGS', '-fsanitize=leak');
298             }
299              
300 3 50       24 if ($use_feature{sanitize}{undefined}) {
301 0         0 my $flag = '-fsanitize=undefined';
302 0         0 $flags->append('CFLAGS', $flag);
303 0         0 $flags->append('CXXFLAGS', $flag);
304 0         0 $flags->append('LDFLAGS', $flag);
305             }
306              
307             ## Area: hardening
308              
309             # Mask builtin features that are not enabled by default in the compiler.
310 3         19 my %builtin_pie_arch = map { $_ => 1 } qw(
  54         108  
311             amd64
312             arm64
313             armel
314             armhf
315             hurd-i386
316             i386
317             kfreebsd-amd64
318             kfreebsd-i386
319             mips
320             mipsel
321             mips64el
322             powerpc
323             ppc64
324             ppc64el
325             riscv64
326             s390x
327             sparc
328             sparc64
329             );
330 3 50       14 if (not exists $builtin_pie_arch{$arch}) {
331 0         0 $builtin_feature{hardening}{pie} = 0;
332             }
333              
334             # Mask features that are not available on certain architectures.
335 3 50 33     44 if ($os !~ /^(?:linux|kfreebsd|knetbsd|hurd)$/ or
336             $cpu =~ /^(?:hppa|avr32)$/) {
337             # Disabled on non-(linux/kfreebsd/knetbsd/hurd).
338             # Disabled on hppa, avr32
339             # (#574716).
340 0         0 $use_feature{hardening}{pie} = 0;
341             }
342 3 50 33     27 if ($cpu =~ /^(?:ia64|alpha|hppa|nios2)$/ or $arch eq 'arm') {
343             # Stack protector disabled on ia64, alpha, hppa, nios2.
344             # "warning: -fstack-protector not supported for this target"
345             # Stack protector disabled on arm (ok on armel).
346             # compiler supports it incorrectly (leads to SEGV)
347 0         0 $use_feature{hardening}{stackprotector} = 0;
348             }
349 3 50       12 if ($cpu =~ /^(?:ia64|hppa|avr32)$/) {
350             # relro not implemented on ia64, hppa, avr32.
351 0         0 $use_feature{hardening}{relro} = 0;
352             }
353              
354             # Mask features that might be influenced by other flags.
355 3 50       12 if ($opts_build->has('noopt')) {
356             # glibc 2.16 and later warn when using -O0 and _FORTIFY_SOURCE.
357 0         0 $use_feature{hardening}{fortify} = 0;
358             }
359              
360             # Handle logical feature interactions.
361 3 50       13 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 0         0 $use_feature{hardening}{bindnow} = 0;
365             }
366 3 50       9 if ($use_feature{hardening}{stackprotector} == 0) {
367             # Disable stackprotectorstrong if stackprotector is disabled.
368 0         0 $use_feature{hardening}{stackprotectorstrong} = 0;
369             }
370              
371             # PIE
372 3 50 33     30 if (defined $use_feature{hardening}{pie} and
    50 0        
      33        
      33        
373             $use_feature{hardening}{pie} and
374             not $builtin_feature{hardening}{pie}) {
375 0         0 my $flag = "-specs=$Dpkg::DATADIR/pie-compile.specs";
376 0         0 $flags->append('CFLAGS', $flag);
377 0         0 $flags->append('OBJCFLAGS', $flag);
378 0         0 $flags->append('OBJCXXFLAGS', $flag);
379 0         0 $flags->append('FFLAGS', $flag);
380 0         0 $flags->append('FCFLAGS', $flag);
381 0         0 $flags->append('CXXFLAGS', $flag);
382 0         0 $flags->append('GCJFLAGS', $flag);
383 0         0 $flags->append('LDFLAGS', "-specs=$Dpkg::DATADIR/pie-link.specs");
384             } elsif (defined $use_feature{hardening}{pie} and
385             not $use_feature{hardening}{pie} and
386             $builtin_feature{hardening}{pie}) {
387 0         0 my $flag = "-specs=$Dpkg::DATADIR/no-pie-compile.specs";
388 0         0 $flags->append('CFLAGS', $flag);
389 0         0 $flags->append('OBJCFLAGS', $flag);
390 0         0 $flags->append('OBJCXXFLAGS', $flag);
391 0         0 $flags->append('FFLAGS', $flag);
392 0         0 $flags->append('FCFLAGS', $flag);
393 0         0 $flags->append('CXXFLAGS', $flag);
394 0         0 $flags->append('GCJFLAGS', $flag);
395 0         0 $flags->append('LDFLAGS', "-specs=$Dpkg::DATADIR/no-pie-link.specs");
396             }
397              
398             # Stack protector
399 3 50       10 if ($use_feature{hardening}{stackprotectorstrong}) {
    0          
400 3         11 my $flag = '-fstack-protector-strong';
401 3         12 $flags->append('CFLAGS', $flag);
402 3         10 $flags->append('OBJCFLAGS', $flag);
403 3         10 $flags->append('OBJCXXFLAGS', $flag);
404 3         11 $flags->append('FFLAGS', $flag);
405 3         7 $flags->append('FCFLAGS', $flag);
406 3         10 $flags->append('CXXFLAGS', $flag);
407 3         7 $flags->append('GCJFLAGS', $flag);
408             } elsif ($use_feature{hardening}{stackprotector}) {
409 0         0 my $flag = '-fstack-protector --param=ssp-buffer-size=4';
410 0         0 $flags->append('CFLAGS', $flag);
411 0         0 $flags->append('OBJCFLAGS', $flag);
412 0         0 $flags->append('OBJCXXFLAGS', $flag);
413 0         0 $flags->append('FFLAGS', $flag);
414 0         0 $flags->append('FCFLAGS', $flag);
415 0         0 $flags->append('CXXFLAGS', $flag);
416 0         0 $flags->append('GCJFLAGS', $flag);
417             }
418              
419             # Fortify Source
420 3 50       10 if ($use_feature{hardening}{fortify}) {
421 3         8 $flags->append('CPPFLAGS', '-D_FORTIFY_SOURCE=2');
422             }
423              
424             # Format Security
425 3 50       23 if ($use_feature{hardening}{format}) {
426 3         7 my $flag = '-Wformat -Werror=format-security';
427 3         11 $flags->append('CFLAGS', $flag);
428 3         8 $flags->append('CXXFLAGS', $flag);
429 3         8 $flags->append('OBJCFLAGS', $flag);
430 3         28 $flags->append('OBJCXXFLAGS', $flag);
431             }
432              
433             # Read-only Relocations
434 3 50       11 if ($use_feature{hardening}{relro}) {
435 3         9 $flags->append('LDFLAGS', '-Wl,-z,relro');
436             }
437              
438             # Bindnow
439 3 50       10 if ($use_feature{hardening}{bindnow}) {
440 0         0 $flags->append('LDFLAGS', '-Wl,-z,now');
441             }
442              
443             ## Commit
444              
445             # Set used features to their builtin setting if unset.
446 3         25 foreach my $area (sort keys %builtin_feature) {
447 3         6 foreach my $feature (keys %{$builtin_feature{$area}}) {
  3         14  
448 3   33     32 $use_feature{$area}{$feature} //= $builtin_feature{$area}{$feature};
449             }
450             }
451              
452             # Store the feature usage.
453 3         17 foreach my $area (sort keys %use_feature) {
454 15         22 while (my ($feature, $enabled) = each %{$use_feature{$area}}) {
  66         288  
455 51         109 $flags->set_feature($area, $feature, $enabled);
456             }
457             }
458             }
459              
460             sub _build_tainted_by {
461 0     0     my $self = shift;
462 0           my %tainted;
463              
464 0           foreach my $pathname (qw(/bin /sbin /lib /lib32 /libo32 /libx32 /lib64)) {
465 0 0         next unless -l $pathname;
466              
467 0           my $linkname = readlink $pathname;
468 0 0 0       if ($linkname eq "usr$pathname" or $linkname eq "/usr$pathname") {
469 0           $tainted{'merged-usr-via-symlinks'} = 1;
470 0           last;
471             }
472             }
473              
474 0           require File::Find;
475 0           my %usr_local_types = (
476             configs => [ qw(etc) ],
477             includes => [ qw(include) ],
478             programs => [ qw(bin sbin) ],
479             libraries => [ qw(lib) ],
480             );
481 0           foreach my $type (keys %usr_local_types) {
482             File::Find::find({
483 0 0   0     wanted => sub { $tainted{"usr-local-has-$type"} = 1 if -f },
484             no_chdir => 1,
485 0           }, grep { -d } map { "/usr/local/$_" } @{$usr_local_types{$type}});
  0            
  0            
  0            
486             }
487              
488 0           my @tainted = sort keys %tainted;
489 0           return @tainted;
490             }
491              
492             =head1 CHANGES
493              
494             =head2 Version 0.xx
495              
496             This is a private module.
497              
498             =cut
499              
500             1;