chiark / gitweb /
dpkg (1.18.25) stretch; urgency=medium
[dpkg] / scripts / Dpkg / Vendor / Debian.pm
1 # Copyright © 2009-2011 Raphaël Hertzog <hertzog@debian.org>
2 # Copyright © 2009, 2011-2017 Guillem Jover <guillem@debian.org>
3 #
4 # Hardening build flags handling derived from work of:
5 # Copyright © 2009-2011 Kees Cook <kees@debian.org>
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 <https://www.gnu.org/licenses/>.
20
21 package Dpkg::Vendor::Debian;
22
23 use strict;
24 use warnings;
25
26 our $VERSION = '0.01';
27
28 use Dpkg;
29 use Dpkg::Gettext;
30 use Dpkg::ErrorHandling;
31 use Dpkg::Control::Types;
32 use Dpkg::BuildOptions;
33 use Dpkg::Arch qw(get_host_arch debarch_to_debtuple);
34
35 use parent qw(Dpkg::Vendor::Default);
36
37 =encoding utf8
38
39 =head1 NAME
40
41 Dpkg::Vendor::Debian - Debian vendor object
42
43 =head1 DESCRIPTION
44
45 This vendor object customizes the behaviour of dpkg scripts for Debian
46 specific behavior and policies.
47
48 =cut
49
50 sub run_hook {
51     my ($self, $hook, @params) = @_;
52
53     if ($hook eq 'package-keyrings') {
54         return ('/usr/share/keyrings/debian-keyring.gpg',
55                 '/usr/share/keyrings/debian-maintainers.gpg');
56     } elsif ($hook eq 'keyrings') {
57         warnings::warnif('deprecated', 'deprecated keyrings vendor hook');
58         return $self->run_hook('package-keyrings', @params);
59     } elsif ($hook eq 'archive-keyrings') {
60         return ('/usr/share/keyrings/debian-archive-keyring.gpg');
61     } elsif ($hook eq 'archive-keyrings-historic') {
62         return ('/usr/share/keyrings/debian-archive-removed-keys.gpg');
63     } elsif ($hook eq 'builtin-build-depends') {
64         return qw(build-essential:native);
65     } elsif ($hook eq 'builtin-build-conflicts') {
66         return ();
67     } elsif ($hook eq 'register-custom-fields') {
68     } elsif ($hook eq 'extend-patch-header') {
69         my ($textref, $ch_info) = @params;
70         if ($ch_info->{'Closes'}) {
71             foreach my $bug (split(/\s+/, $ch_info->{'Closes'})) {
72                 $$textref .= "Bug-Debian: https://bugs.debian.org/$bug\n";
73             }
74         }
75
76         # XXX: Layer violation...
77         require Dpkg::Vendor::Ubuntu;
78         my $b = Dpkg::Vendor::Ubuntu::find_launchpad_closes($ch_info->{'Changes'});
79         foreach my $bug (@$b) {
80             $$textref .= "Bug-Ubuntu: https://bugs.launchpad.net/bugs/$bug\n";
81         }
82     } elsif ($hook eq 'update-buildflags') {
83         $self->_add_qa_flags(@params);
84         $self->_add_reproducible_flags(@params);
85         $self->_add_sanitize_flags(@params);
86         $self->_add_hardening_flags(@params);
87     } elsif ($hook eq 'builtin-system-build-paths') {
88         return qw(/build/);
89     } else {
90         return $self->SUPER::run_hook($hook, @params);
91     }
92 }
93
94 sub _parse_feature_area {
95     my ($self, $area, $use_feature) = @_;
96
97     # Adjust features based on user or maintainer's desires.
98     my $opts = Dpkg::BuildOptions->new(envvar => 'DEB_BUILD_OPTIONS');
99     $opts->parse_features($area, $use_feature);
100     $opts = Dpkg::BuildOptions->new(envvar => 'DEB_BUILD_MAINT_OPTIONS');
101     $opts->parse_features($area, $use_feature);
102 }
103
104 sub _add_qa_flags {
105     my ($self, $flags) = @_;
106
107     # Default feature states.
108     my %use_feature = (
109         bug => 0,
110         canary => 0,
111     );
112
113     # Adjust features based on user or maintainer's desires.
114     $self->_parse_feature_area('qa', \%use_feature);
115
116     # Warnings that detect actual bugs.
117     if ($use_feature{bug}) {
118         foreach my $warnflag (qw(array-bounds clobbered volatile-register-var
119                                  implicit-function-declaration)) {
120             $flags->append('CFLAGS', "-Werror=$warnflag");
121             $flags->append('CXXFLAGS', "-Werror=$warnflag");
122         }
123     }
124
125     # Inject dummy canary options to detect issues with build flag propagation.
126     if ($use_feature{canary}) {
127         require Digest::MD5;
128         my $id = Digest::MD5::md5_hex(int rand 4096);
129
130         foreach my $flag (qw(CPPFLAGS CFLAGS OBJCFLAGS CXXFLAGS OBJCXXFLAGS)) {
131             $flags->append($flag, "-D__DEB_CANARY_${flag}_${id}__");
132         }
133         $flags->append('LDFLAGS', "-Wl,-z,deb-canary-${id}");
134     }
135
136     # Store the feature usage.
137     while (my ($feature, $enabled) = each %use_feature) {
138         $flags->set_feature('qa', $feature, $enabled);
139     }
140 }
141
142 sub _add_reproducible_flags {
143     my ($self, $flags) = @_;
144
145     # Default feature states.
146     my %use_feature = (
147         timeless => 1,
148         fixdebugpath => 1,
149     );
150
151     my $build_path;
152
153     # Adjust features based on user or maintainer's desires.
154     $self->_parse_feature_area('reproducible', \%use_feature);
155
156     # Mask features that might have an unsafe usage.
157     if ($use_feature{fixdebugpath}) {
158         require Cwd;
159
160         $build_path = $ENV{DEB_BUILD_PATH} || Cwd::cwd();
161
162         # If we have any unsafe character in the path, disable the flag,
163         # so that we do not need to worry about escaping the characters
164         # on output.
165         if ($build_path =~ m/[^-+:.0-9a-zA-Z~\/_]/) {
166             $use_feature{fixdebugpath} = 0;
167         }
168     }
169
170     # Warn when the __TIME__, __DATE__ and __TIMESTAMP__ macros are used.
171     if ($use_feature{timeless}) {
172        $flags->append('CPPFLAGS', '-Wdate-time');
173     }
174
175     # Avoid storing the build path in the debug symbols.
176     if ($use_feature{fixdebugpath}) {
177         my $map = '-fdebug-prefix-map=' . $build_path . '=.';
178         $flags->append('CFLAGS', $map);
179         $flags->append('CXXFLAGS', $map);
180         $flags->append('OBJCFLAGS', $map);
181         $flags->append('OBJCXXFLAGS', $map);
182         $flags->append('FFLAGS', $map);
183         $flags->append('FCFLAGS', $map);
184         $flags->append('GCJFLAGS', $map);
185     }
186
187     # Store the feature usage.
188     while (my ($feature, $enabled) = each %use_feature) {
189        $flags->set_feature('reproducible', $feature, $enabled);
190     }
191 }
192
193 sub _add_sanitize_flags {
194     my ($self, $flags) = @_;
195
196     # Default feature states.
197     my %use_feature = (
198         address => 0,
199         thread => 0,
200         leak => 0,
201         undefined => 0,
202     );
203
204     # Adjust features based on user or maintainer's desires.
205     $self->_parse_feature_area('sanitize', \%use_feature);
206
207     # Handle logical feature interactions.
208     if ($use_feature{address} and $use_feature{thread}) {
209         # Disable the thread sanitizer when the address one is active, they
210         # are mutually incompatible.
211         $use_feature{thread} = 0;
212     }
213     if ($use_feature{address} or $use_feature{thread}) {
214         # Disable leak sanitizer, it is implied by the address or thread ones.
215         $use_feature{leak} = 0;
216     }
217
218     if ($use_feature{address}) {
219         my $flag = '-fsanitize=address -fno-omit-frame-pointer';
220         $flags->append('CFLAGS', $flag);
221         $flags->append('CXXFLAGS', $flag);
222         $flags->append('LDFLAGS', '-fsanitize=address');
223     }
224
225     if ($use_feature{thread}) {
226         my $flag = '-fsanitize=thread';
227         $flags->append('CFLAGS', $flag);
228         $flags->append('CXXFLAGS', $flag);
229         $flags->append('LDFLAGS', $flag);
230     }
231
232     if ($use_feature{leak}) {
233         $flags->append('LDFLAGS', '-fsanitize=leak');
234     }
235
236     if ($use_feature{undefined}) {
237         my $flag = '-fsanitize=undefined';
238         $flags->append('CFLAGS', $flag);
239         $flags->append('CXXFLAGS', $flag);
240         $flags->append('LDFLAGS', $flag);
241     }
242
243     # Store the feature usage.
244     while (my ($feature, $enabled) = each %use_feature) {
245        $flags->set_feature('sanitize', $feature, $enabled);
246     }
247 }
248
249 sub _add_hardening_flags {
250     my ($self, $flags) = @_;
251     my $arch = get_host_arch();
252     my ($abi, $libc, $os, $cpu) = debarch_to_debtuple($arch);
253
254     unless (defined $abi and defined $libc and defined $os and defined $cpu) {
255         warning(g_("unknown host architecture '%s'"), $arch);
256         ($abi, $os, $cpu) = ('', '', '');
257     }
258
259     # Default feature states.
260     my %use_feature = (
261         # XXX: This is set to undef so that we can cope with the brokenness
262         # of gcc managing this feature builtin.
263         pie => undef,
264         stackprotector => 1,
265         stackprotectorstrong => 1,
266         fortify => 1,
267         format => 1,
268         relro => 1,
269         bindnow => 0,
270     );
271     my %builtin_feature = (
272         pie => 1,
273     );
274
275     my %builtin_pie_arch = map { $_ => 1 } qw(
276         amd64 arm64 armel armhf i386 kfreebsd-amd64 kfreebsd-i386
277         mips mipsel mips64el ppc64el s390x sparc sparc64
278     );
279
280     # Mask builtin features that are not enabled by default in the compiler.
281     if (not exists $builtin_pie_arch{$arch}) {
282         $builtin_feature{pie} = 0;
283     }
284
285     # Adjust features based on user or maintainer's desires.
286     $self->_parse_feature_area('hardening', \%use_feature);
287
288     # Mask features that are not available on certain architectures.
289     if ($os !~ /^(?:linux|kfreebsd|knetbsd|hurd)$/ or
290         $cpu =~ /^(?:hppa|avr32)$/) {
291         # Disabled on non-(linux/kfreebsd/knetbsd/hurd).
292         # Disabled on hppa, avr32
293         #  (#574716).
294         $use_feature{pie} = 0;
295     }
296     if ($cpu =~ /^(?:ia64|alpha|hppa|nios2)$/ or $arch eq 'arm') {
297         # Stack protector disabled on ia64, alpha, hppa, nios2.
298         #   "warning: -fstack-protector not supported for this target"
299         # Stack protector disabled on arm (ok on armel).
300         #   compiler supports it incorrectly (leads to SEGV)
301         $use_feature{stackprotector} = 0;
302     }
303     if ($cpu =~ /^(?:ia64|hppa|avr32)$/) {
304         # relro not implemented on ia64, hppa, avr32.
305         $use_feature{relro} = 0;
306     }
307
308     # Mask features that might be influenced by other flags.
309     if ($flags->{build_options}->has('noopt')) {
310       # glibc 2.16 and later warn when using -O0 and _FORTIFY_SOURCE.
311       $use_feature{fortify} = 0;
312     }
313
314     # Handle logical feature interactions.
315     if ($use_feature{relro} == 0) {
316         # Disable bindnow if relro is not enabled, since it has no
317         # hardening ability without relro and may incur load penalties.
318         $use_feature{bindnow} = 0;
319     }
320     if ($use_feature{stackprotector} == 0) {
321         # Disable stackprotectorstrong if stackprotector is disabled.
322         $use_feature{stackprotectorstrong} = 0;
323     }
324
325     # PIE
326     if (defined $use_feature{pie} and $use_feature{pie} and
327         not $builtin_feature{pie}) {
328         my $flag = "-specs=$Dpkg::DATADIR/pie-compile.specs";
329         $flags->append('CFLAGS', $flag);
330         $flags->append('OBJCFLAGS',  $flag);
331         $flags->append('OBJCXXFLAGS', $flag);
332         $flags->append('FFLAGS', $flag);
333         $flags->append('FCFLAGS', $flag);
334         $flags->append('CXXFLAGS', $flag);
335         $flags->append('GCJFLAGS', $flag);
336         $flags->append('LDFLAGS', "-specs=$Dpkg::DATADIR/pie-link.specs");
337     } elsif (defined $use_feature{pie} and not $use_feature{pie} and
338              $builtin_feature{pie}) {
339         my $flag = "-specs=$Dpkg::DATADIR/no-pie-compile.specs";
340         $flags->append('CFLAGS', $flag);
341         $flags->append('OBJCFLAGS',  $flag);
342         $flags->append('OBJCXXFLAGS', $flag);
343         $flags->append('FFLAGS', $flag);
344         $flags->append('FCFLAGS', $flag);
345         $flags->append('CXXFLAGS', $flag);
346         $flags->append('GCJFLAGS', $flag);
347         $flags->append('LDFLAGS', "-specs=$Dpkg::DATADIR/no-pie-link.specs");
348     }
349
350     # Stack protector
351     if ($use_feature{stackprotectorstrong}) {
352         my $flag = '-fstack-protector-strong';
353         $flags->append('CFLAGS', $flag);
354         $flags->append('OBJCFLAGS', $flag);
355         $flags->append('OBJCXXFLAGS', $flag);
356         $flags->append('FFLAGS', $flag);
357         $flags->append('FCFLAGS', $flag);
358         $flags->append('CXXFLAGS', $flag);
359         $flags->append('GCJFLAGS', $flag);
360     } elsif ($use_feature{stackprotector}) {
361         my $flag = '-fstack-protector --param=ssp-buffer-size=4';
362         $flags->append('CFLAGS', $flag);
363         $flags->append('OBJCFLAGS', $flag);
364         $flags->append('OBJCXXFLAGS', $flag);
365         $flags->append('FFLAGS', $flag);
366         $flags->append('FCFLAGS', $flag);
367         $flags->append('CXXFLAGS', $flag);
368         $flags->append('GCJFLAGS', $flag);
369     }
370
371     # Fortify Source
372     if ($use_feature{fortify}) {
373         $flags->append('CPPFLAGS', '-D_FORTIFY_SOURCE=2');
374     }
375
376     # Format Security
377     if ($use_feature{format}) {
378         my $flag = '-Wformat -Werror=format-security';
379         $flags->append('CFLAGS', $flag);
380         $flags->append('CXXFLAGS', $flag);
381         $flags->append('OBJCFLAGS', $flag);
382         $flags->append('OBJCXXFLAGS', $flag);
383     }
384
385     # Read-only Relocations
386     if ($use_feature{relro}) {
387         $flags->append('LDFLAGS', '-Wl,-z,relro');
388     }
389
390     # Bindnow
391     if ($use_feature{bindnow}) {
392         $flags->append('LDFLAGS', '-Wl,-z,now');
393     }
394
395     # Set used features to their builtin setting if unset.
396     foreach my $feature (keys %builtin_feature) {
397         $use_feature{$feature} //= $builtin_feature{$feature};
398     }
399
400     # Store the feature usage.
401     while (my ($feature, $enabled) = each %use_feature) {
402         $flags->set_feature('hardening', $feature, $enabled);
403     }
404 }
405
406 =head1 CHANGES
407
408 =head2 Version 0.xx
409
410 This is a private module.
411
412 =cut
413
414 1;