File: | Dpkg/Vendor/Debian.pm |
Coverage: | 65.2% |
line | stmt | bran | cond | sub | pod | time | code |
---|---|---|---|---|---|---|---|
1 | # Copyright © 2009-2011 Raphaël Hertzog <hertzog@debian.org> | ||||||
2 | # Copyright © 2009-2024 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 | =encoding utf8 | ||||||
22 | |||||||
23 - 34 | =head1 NAME Dpkg::Vendor::Debian - Debian vendor class =head1 DESCRIPTION This vendor class customizes the behavior of dpkg scripts for Debian specific behavior and policies. B<Note>: This is a private module, its API can change at any time. =cut | ||||||
35 | |||||||
36 | package Dpkg::Vendor::Debian 0.01; | ||||||
37 | |||||||
38 | 60 60 60 | 1380 42 1032 | use strict; | ||||
39 | 60 60 60 | 110 35 1681 | use warnings; | ||||
40 | |||||||
41 | 60 60 60 | 125 55 1930 | use List::Util qw(any none); | ||||
42 | |||||||
43 | 60 60 60 | 148 46 959 | use Dpkg; | ||||
44 | 60 60 60 | 114 36 1567 | use Dpkg::Gettext; | ||||
45 | 60 60 60 | 122 36 1926 | use Dpkg::ErrorHandling; | ||||
46 | 60 60 60 | 119 44 2805 | use Dpkg::Control::Types; | ||||
47 | |||||||
48 | 60 60 60 | 123 53 203 | use parent qw(Dpkg::Vendor::Default); | ||||
49 | |||||||
50 | sub run_hook { | ||||||
51 | 891 | 1 | 1053 | my ($self, $hook, @params) = @_; | |||
52 | |||||||
53 | 891 | 3676 | if ($hook eq 'package-keyrings') { | ||||
54 | 0 | 0 | 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 | 0 | 0 | return ('/usr/share/keyrings/debian-archive-keyring.gpg'); | ||||
59 | } elsif ($hook eq 'archive-keyrings-historic') { | ||||||
60 | 0 | 0 | return ('/usr/share/keyrings/debian-archive-removed-keys.gpg'); | ||||
61 | } elsif ($hook eq 'builtin-build-depends') { | ||||||
62 | 0 | 0 | return qw(build-essential:native); | ||||
63 | } elsif ($hook eq 'builtin-build-conflicts') { | ||||||
64 | 0 | 0 | return (); | ||||
65 | } elsif ($hook eq 'register-custom-fields') { | ||||||
66 | } elsif ($hook eq 'extend-patch-header') { | ||||||
67 | 0 | 0 | my ($textref, $ch_info) = @params; | ||||
68 | 0 | 0 | if ($ch_info->{'Closes'}) { | ||||
69 | 0 | 0 | foreach my $bug (split(/\s+/, $ch_info->{'Closes'})) { | ||||
70 | 0 | 0 | $$textref .= "Bug-Debian: https://bugs.debian.org/$bug\n"; | ||||
71 | } | ||||||
72 | } | ||||||
73 | |||||||
74 | # XXX: Layer violation... | ||||||
75 | 0 | 0 | require Dpkg::Vendor::Ubuntu; | ||||
76 | 0 | 0 | my $b = Dpkg::Vendor::Ubuntu::find_launchpad_closes($ch_info->{'Changes'}); | ||||
77 | 0 | 0 | foreach my $bug (@$b) { | ||||
78 | 0 | 0 | $$textref .= "Bug-Ubuntu: https://bugs.launchpad.net/bugs/$bug\n"; | ||||
79 | } | ||||||
80 | } elsif ($hook eq 'update-buildflags') { | ||||||
81 | 81 | 168 | $self->set_build_features(@params); | ||||
82 | 81 | 201 | $self->add_build_flags(@params); | ||||
83 | } elsif ($hook eq 'builtin-system-build-paths') { | ||||||
84 | 0 | 0 | return qw(/build/); | ||||
85 | } elsif ($hook eq 'build-tainted-by') { | ||||||
86 | 0 | 0 | return $self->_build_tainted_by(); | ||||
87 | } elsif ($hook eq 'sanitize-environment') { | ||||||
88 | # Reset umask to a sane default. | ||||||
89 | 0 | 0 | umask 0022; | ||||
90 | # Reset locale to a sane default. | ||||||
91 | # | ||||||
92 | # We ignore the LANGUAGE GNU extension, as that only affects | ||||||
93 | # LC_MESSAGES which will use LC_CTYPE for its codeset. We need to | ||||||
94 | # move the high priority LC_ALL catch-all into the low-priority | ||||||
95 | # LANG catch-all so that we can override LC_* variables, and remove | ||||||
96 | # any existing LC_* variables which would have been ignored anyway, | ||||||
97 | # and would now take precedence over LANG. | ||||||
98 | 0 | 0 | if (length $ENV{LC_ALL}) { | ||||
99 | 0 | 0 | $ENV{LANG} = delete $ENV{LC_ALL}; | ||||
100 | 0 0 | 0 0 | foreach my $lc (grep { m/^LC_/ } keys %ENV) { | ||||
101 | 0 | 0 | delete $ENV{$lc}; | ||||
102 | } | ||||||
103 | } | ||||||
104 | 0 | 0 | $ENV{LC_COLLATE} = 'C.UTF-8'; | ||||
105 | 0 | 0 | $ENV{LC_CTYPE} = 'C.UTF-8'; | ||||
106 | } elsif ($hook eq 'backport-version-regex') { | ||||||
107 | 12 | 41 | return qr/~(bpo|deb)/; | ||||
108 | } else { | ||||||
109 | 750 | 1123 | return $self->SUPER::run_hook($hook, @params); | ||||
110 | } | ||||||
111 | } | ||||||
112 | |||||||
113 | sub init_build_features { | ||||||
114 | 81 | 0 | 128 | my ($self, $use_feature, $builtin_feature) = @_; | |||
115 | } | ||||||
116 | |||||||
117 | sub set_build_features { | ||||||
118 | 81 | 1 | 94 | my ($self, $flags) = @_; | |||
119 | |||||||
120 | # Default feature states. | ||||||
121 | 81 | 904 | my %use_feature = ( | ||||
122 | future => { | ||||||
123 | # XXX: Should start a deprecation cycle at some point. | ||||||
124 | lfs => 0, | ||||||
125 | }, | ||||||
126 | abi => { | ||||||
127 | # XXX: This is set to undef so that we can handle the alias from | ||||||
128 | # the future feature area. | ||||||
129 | lfs => undef, | ||||||
130 | # XXX: This is set to undef to handle mask on the default setting. | ||||||
131 | time64 => undef, | ||||||
132 | }, | ||||||
133 | qa => { | ||||||
134 | bug => undef, | ||||||
135 | 'bug-implicit-func' => undef, | ||||||
136 | canary => 0, | ||||||
137 | }, | ||||||
138 | reproducible => { | ||||||
139 | timeless => 1, | ||||||
140 | fixfilepath => 1, | ||||||
141 | fixdebugpath => 1, | ||||||
142 | }, | ||||||
143 | optimize => { | ||||||
144 | lto => 0, | ||||||
145 | }, | ||||||
146 | sanitize => { | ||||||
147 | address => 0, | ||||||
148 | thread => 0, | ||||||
149 | leak => 0, | ||||||
150 | undefined => 0, | ||||||
151 | }, | ||||||
152 | hardening => { | ||||||
153 | # XXX: This is set to undef so that we can cope with the brokenness | ||||||
154 | # of gcc managing this feature builtin. | ||||||
155 | pie => undef, | ||||||
156 | stackprotector => 1, | ||||||
157 | stackprotectorstrong => 1, | ||||||
158 | stackclash => 1, | ||||||
159 | fortify => 1, | ||||||
160 | format => 1, | ||||||
161 | relro => 1, | ||||||
162 | bindnow => 0, | ||||||
163 | branch => 1, | ||||||
164 | }, | ||||||
165 | ); | ||||||
166 | |||||||
167 | 81 | 205 | my %builtin_feature = ( | ||||
168 | abi => { | ||||||
169 | lfs => 0, | ||||||
170 | time64 => 0, | ||||||
171 | }, | ||||||
172 | hardening => { | ||||||
173 | pie => 1, | ||||||
174 | }, | ||||||
175 | ); | ||||||
176 | |||||||
177 | 81 | 3660 | require Dpkg::Arch; | ||||
178 | |||||||
179 | 81 | 159 | my $arch = Dpkg::Arch::get_host_arch(); | ||||
180 | 81 | 175 | my ($abi, $libc, $os, $cpu) = Dpkg::Arch::debarch_to_debtuple($arch); | ||||
181 | 81 | 161 | my ($abi_bits, $abi_endian) = Dpkg::Arch::debarch_to_abiattrs($arch); | ||||
182 | |||||||
183 | 81 | 447 | unless (defined $abi and defined $libc and defined $os and defined $cpu) { | ||||
184 | 0 | 0 | warning(g_("unknown host architecture '%s'"), $arch); | ||||
185 | 0 | 0 | ($abi, $os, $cpu) = ('', '', ''); | ||||
186 | } | ||||||
187 | 81 | 215 | unless (defined $abi_bits and defined $abi_endian) { | ||||
188 | 0 | 0 | warning(g_("unknown abi attributes for architecture '%s'"), $arch); | ||||
189 | 0 | 0 | ($abi_bits, $abi_endian) = (0, 'unknown'); | ||||
190 | } | ||||||
191 | |||||||
192 | # Mask builtin features that are not enabled by default in the compiler. | ||||||
193 | 81 2349 | 160 2218 | my %builtin_pie_arch = map { $_ => 1 } qw( | ||||
194 | amd64 | ||||||
195 | arm64 | ||||||
196 | armel | ||||||
197 | armhf | ||||||
198 | hurd-amd64 | ||||||
199 | hurd-i386 | ||||||
200 | i386 | ||||||
201 | kfreebsd-amd64 | ||||||
202 | kfreebsd-i386 | ||||||
203 | loong64 | ||||||
204 | mips | ||||||
205 | mips64 | ||||||
206 | mips64el | ||||||
207 | mips64r6 | ||||||
208 | mips64r6el | ||||||
209 | mipsel | ||||||
210 | mipsn32 | ||||||
211 | mipsn32el | ||||||
212 | mipsn32r6 | ||||||
213 | mipsn32r6el | ||||||
214 | mipsr6 | ||||||
215 | mipsr6el | ||||||
216 | powerpc | ||||||
217 | ppc64 | ||||||
218 | ppc64el | ||||||
219 | riscv64 | ||||||
220 | s390x | ||||||
221 | sparc | ||||||
222 | sparc64 | ||||||
223 | ); | ||||||
224 | 81 | 200 | if (not exists $builtin_pie_arch{$arch}) { | ||||
225 | 0 | 0 | $builtin_feature{hardening}{pie} = 0; | ||||
226 | } | ||||||
227 | |||||||
228 | 81 | 156 | if ($abi_bits != 32) { | ||||
229 | 42 | 57 | $builtin_feature{abi}{lfs} = 1; | ||||
230 | } | ||||||
231 | |||||||
232 | # On glibc, new ports default to time64, old ports currently default | ||||||
233 | # to time32, so we track the latter as that is a list that is not | ||||||
234 | # going to grow further, and might shrink. | ||||||
235 | # On musl libc based systems all ports use time64. | ||||||
236 | 81 2187 | 130 1830 | my %time32_arch = map { $_ => 1 } qw( | ||||
237 | arm | ||||||
238 | armeb | ||||||
239 | armel | ||||||
240 | armhf | ||||||
241 | hppa | ||||||
242 | i386 | ||||||
243 | hurd-i386 | ||||||
244 | kfreebsd-i386 | ||||||
245 | m68k | ||||||
246 | mips | ||||||
247 | mipsel | ||||||
248 | mipsn32 | ||||||
249 | mipsn32el | ||||||
250 | mipsn32r6 | ||||||
251 | mipsn32r6el | ||||||
252 | mipsr6 | ||||||
253 | mipsr6el | ||||||
254 | nios2 | ||||||
255 | powerpc | ||||||
256 | powerpcel | ||||||
257 | powerpcspe | ||||||
258 | s390 | ||||||
259 | sh3 | ||||||
260 | sh3eb | ||||||
261 | sh4 | ||||||
262 | sh4eb | ||||||
263 | sparc | ||||||
264 | ); | ||||||
265 | 81 | 368 | if ($abi_bits != 32 or | ||||
266 | not exists $time32_arch{$arch} or | ||||||
267 | $libc eq 'musl') { | ||||||
268 | 42 | 49 | $builtin_feature{abi}{time64} = 1; | ||||
269 | } | ||||||
270 | |||||||
271 | 81 | 229 | $self->init_build_features(\%use_feature, \%builtin_feature); | ||||
272 | |||||||
273 | ## Setup | ||||||
274 | |||||||
275 | 81 | 3026 | require Dpkg::BuildOptions; | ||||
276 | |||||||
277 | # Adjust features based on user or maintainer's desires. | ||||||
278 | 81 | 267 | my $opts_build = Dpkg::BuildOptions->new(envvar => 'DEB_BUILD_OPTIONS'); | ||||
279 | 81 | 120 | my $opts_maint = Dpkg::BuildOptions->new(envvar => 'DEB_BUILD_MAINT_OPTIONS'); | ||||
280 | |||||||
281 | 81 | 356 | foreach my $area (sort keys %use_feature) { | ||||
282 | 567 | 689 | $opts_build->parse_features($area, $use_feature{$area}); | ||||
283 | 567 | 568 | $opts_maint->parse_features($area, $use_feature{$area}); | ||||
284 | } | ||||||
285 | |||||||
286 | ## Area: abi | ||||||
287 | |||||||
288 | 81 153 | 299 267 | if (any { $arch eq $_ } qw(hurd-i386 kfreebsd-i386)) { | ||||
289 | # Mask time64 on hurd-i386 and kfreebsd-i386, as their kernel lacks | ||||||
290 | # support for that arch and it will not be implemented. | ||||||
291 | 9 | 10 | $use_feature{abi}{time64} = 0; | ||||
292 | } elsif (not defined $use_feature{abi}{time64}) { | ||||||
293 | # If the user has not requested a specific setting, by default only | ||||||
294 | # enable time64 everywhere except for i386, where we preserve it for | ||||||
295 | # binary backwards compatibility. | ||||||
296 | 54 | 62 | if ($arch eq 'i386') { | ||||
297 | 15 | 26 | $use_feature{abi}{time64} = 0; | ||||
298 | } else { | ||||||
299 | 39 | 45 | $use_feature{abi}{time64} = 1; | ||||
300 | } | ||||||
301 | } | ||||||
302 | |||||||
303 | # In Debian gcc enables time64 (and lfs) for the following architectures | ||||||
304 | # by injecting pre-processor flags, though the libc ABI has not changed. | ||||||
305 | 81 594 | 212 371 | if (any { $arch eq $_ } qw(armel armhf hppa m68k mips mipsel powerpc sh4)) { | ||||
306 | 9 | 23 | $flags->set_option_value('cc-abi-time64', 1); | ||||
307 | } else { | ||||||
308 | 72 | 150 | $flags->set_option_value('cc-abi-time64', 0); | ||||
309 | } | ||||||
310 | |||||||
311 | 81 | 245 | if ($use_feature{abi}{time64} && ! $builtin_feature{abi}{time64}) { | ||||
312 | # On glibc 64-bit time_t support requires LFS. | ||||||
313 | 9 | 22 | $use_feature{abi}{lfs} = 1 if $libc eq 'gnu'; | ||||
314 | } | ||||||
315 | |||||||
316 | # XXX: Handle lfs alias from future abi feature area. | ||||||
317 | 81 | 264 | $use_feature{abi}{lfs} //= $use_feature{future}{lfs}; | ||||
318 | # XXX: Once the feature is set in the abi area, we always override the | ||||||
319 | # one in the future area. | ||||||
320 | 81 | 79 | $use_feature{future}{lfs} = $use_feature{abi}{lfs}; | ||||
321 | |||||||
322 | ## Area: qa | ||||||
323 | |||||||
324 | # For time64 we require -Werror=implicit-function-declaration, to avoid | ||||||
325 | # linking against the wrong symbol. Instead of enabling this conditionally | ||||||
326 | # on time64 being enabled, do it unconditionally so that the effects are | ||||||
327 | # uniform and visible on all architectures. Unless it has been set | ||||||
328 | # explicitly. | ||||||
329 | 81 | 343 | $use_feature{qa}{'bug-implicit-func'} //= $use_feature{qa}{bug} // 1; | ||||
330 | |||||||
331 | 81 | 252 | $use_feature{qa}{bug} //= 0; | ||||
332 | |||||||
333 | ## Area: reproducible | ||||||
334 | |||||||
335 | # Mask features that might have an unsafe usage. | ||||||
336 | 81 | 149 | if ($use_feature{reproducible}{fixfilepath} or | ||||
337 | $use_feature{reproducible}{fixdebugpath}) { | ||||||
338 | 81 | 244 | require Cwd; | ||||
339 | |||||||
340 | 81 | 563 | my $build_path =$ENV{DEB_BUILD_PATH} || Cwd::getcwd(); | ||||
341 | |||||||
342 | 81 | 142 | $flags->set_option_value('build-path', $build_path); | ||||
343 | |||||||
344 | # If we have any unsafe character in the path, disable the flag, | ||||||
345 | # so that we do not need to worry about escaping the characters | ||||||
346 | # on output. | ||||||
347 | 81 | 200 | if ($build_path =~ m/[^-+:.0-9a-zA-Z~\/_]/) { | ||||
348 | 0 | 0 | $use_feature{reproducible}{fixfilepath} = 0; | ||||
349 | 0 | 0 | $use_feature{reproducible}{fixdebugpath} = 0; | ||||
350 | } | ||||||
351 | } | ||||||
352 | |||||||
353 | ## Area: optimize | ||||||
354 | |||||||
355 | 81 | 122 | if ($opts_build->has('noopt')) { | ||||
356 | 0 | 0 | $flags->set_option_value('optimize-level', 0); | ||||
357 | } else { | ||||||
358 | 81 | 91 | $flags->set_option_value('optimize-level', 2); | ||||
359 | } | ||||||
360 | |||||||
361 | ## Area: sanitize | ||||||
362 | |||||||
363 | # Handle logical feature interactions. | ||||||
364 | 81 | 129 | if ($use_feature{sanitize}{address} and $use_feature{sanitize}{thread}) { | ||||
365 | # Disable the thread sanitizer when the address one is active, they | ||||||
366 | # are mutually incompatible. | ||||||
367 | 0 | 0 | $use_feature{sanitize}{thread} = 0; | ||||
368 | } | ||||||
369 | 81 | 241 | if ($use_feature{sanitize}{address} or $use_feature{sanitize}{thread}) { | ||||
370 | # Disable leak sanitizer, it is implied by the address or thread ones. | ||||||
371 | 0 | 0 | $use_feature{sanitize}{leak} = 0; | ||||
372 | } | ||||||
373 | |||||||
374 | ## Area: hardening | ||||||
375 | |||||||
376 | # Mask features that are not available on certain architectures. | ||||||
377 | 81 99 | 189 272 | if (none { $os eq $_ } qw(linux kfreebsd hurd) or | ||||
378 | 243 | 259 | any { $cpu eq $_ } qw(alpha hppa ia64)) { | ||||
379 | # Disabled on non-(linux/kfreebsd/hurd). | ||||||
380 | # Disabled on alpha, hppa, ia64. | ||||||
381 | 0 | 0 | $use_feature{hardening}{pie} = 0; | ||||
382 | } | ||||||
383 | 81 324 | 247 379 | if (any { $cpu eq $_ } qw(ia64 alpha hppa nios2) or $arch eq 'arm') { | ||||
384 | # Stack protector disabled on ia64, alpha, hppa, nios2. | ||||||
385 | # "warning: -fstack-protector not supported for this target" | ||||||
386 | # Stack protector disabled on arm (ok on armel). | ||||||
387 | # compiler supports it incorrectly (leads to SEGV) | ||||||
388 | 0 | 0 | $use_feature{hardening}{stackprotector} = 0; | ||||
389 | } | ||||||
390 | 81 216 | 191 173 | if (none { $arch eq $_ } qw(amd64 arm64 armhf armel)) { | ||||
391 | # Stack clash protector only available on amd64 and arm. | ||||||
392 | 39 | 45 | $use_feature{hardening}{stackclash} = 0; | ||||
393 | } | ||||||
394 | 81 162 | 161 231 | if (any { $cpu eq $_ } qw(ia64 hppa)) { | ||||
395 | # relro not implemented on ia64, hppa. | ||||||
396 | 0 | 0 | $use_feature{hardening}{relro} = 0; | ||||
397 | } | ||||||
398 | 81 129 | 149 132 | if (none { $cpu eq $_ } qw(amd64 arm64)) { | ||||
399 | # On amd64 use -fcf-protection. | ||||||
400 | # On arm64 use -mbranch-protection=standard. | ||||||
401 | 48 | 53 | $use_feature{hardening}{branch} = 0; | ||||
402 | } | ||||||
403 | 81 | 141 | $flags->set_option_value('hardening-branch-cpu', $cpu); | ||||
404 | |||||||
405 | # Mask features that might be influenced by other flags. | ||||||
406 | 81 | 126 | if ($flags->get_option_value('optimize-level') == 0) { | ||||
407 | # glibc 2.16 and later warn when using -O0 and _FORTIFY_SOURCE. | ||||||
408 | 0 | 0 | $use_feature{hardening}{fortify} = 0; | ||||
409 | } | ||||||
410 | 81 | 120 | $flags->set_option_value('fortify-level', 2); | ||||
411 | |||||||
412 | # Handle logical feature interactions. | ||||||
413 | 81 | 110 | if ($use_feature{hardening}{relro} == 0) { | ||||
414 | # Disable bindnow if relro is not enabled, since it has no | ||||||
415 | # hardening ability without relro and may incur load penalties. | ||||||
416 | 0 | 0 | $use_feature{hardening}{bindnow} = 0; | ||||
417 | } | ||||||
418 | 81 | 118 | if ($use_feature{hardening}{stackprotector} == 0) { | ||||
419 | # Disable stackprotectorstrong if stackprotector is disabled. | ||||||
420 | 0 | 0 | $use_feature{hardening}{stackprotectorstrong} = 0; | ||||
421 | } | ||||||
422 | |||||||
423 | ## Commit | ||||||
424 | |||||||
425 | # Set used features to their builtin setting if unset. | ||||||
426 | 81 | 166 | foreach my $area (sort keys %builtin_feature) { | ||||
427 | 162 405 | 112 558 | while (my ($feature, $enabled) = each %{$builtin_feature{$area}}) { | ||||
428 | 243 | 268 | $flags->set_builtin($area, $feature, $enabled); | ||||
429 | } | ||||||
430 | } | ||||||
431 | |||||||
432 | # Store the feature usage. | ||||||
433 | 81 | 188 | foreach my $area (sort keys %use_feature) { | ||||
434 | 567 2430 | 339 3400 | while (my ($feature, $enabled) = each %{$use_feature{$area}}) { | ||||
435 | 1863 | 1543 | $flags->set_feature($area, $feature, $enabled); | ||||
436 | } | ||||||
437 | } | ||||||
438 | } | ||||||
439 | |||||||
440 | sub add_build_flags { | ||||||
441 | 81 | 1 | 107 | my ($self, $flags) = @_; | |||
442 | |||||||
443 | ## Global default flags | ||||||
444 | |||||||
445 | 81 | 214 | my @compile_flags = qw( | ||||
446 | CFLAGS | ||||||
447 | CXXFLAGS | ||||||
448 | OBJCFLAGS | ||||||
449 | OBJCXXFLAGS | ||||||
450 | FFLAGS | ||||||
451 | FCFLAGS | ||||||
452 | ); | ||||||
453 | |||||||
454 | 81 | 89 | my $default_flags; | ||||
455 | my $default_d_flags; | ||||||
456 | |||||||
457 | 81 | 120 | my $optimize_level = $flags->get_option_value('optimize-level'); | ||||
458 | 81 | 109 | $default_flags = "-g -O$optimize_level"; | ||||
459 | 81 | 103 | if ($optimize_level == 0) { | ||||
460 | 0 | 0 | $default_d_flags = '-fdebug'; | ||||
461 | } else { | ||||||
462 | 81 | 75 | $default_d_flags = '-frelease'; | ||||
463 | } | ||||||
464 | |||||||
465 | 81 | 219 | $flags->append($_, $default_flags) foreach @compile_flags; | ||||
466 | 81 | 231 | $flags->append('DFLAGS', $default_d_flags); | ||||
467 | |||||||
468 | ## Area: abi | ||||||
469 | |||||||
470 | 81 | 118 | my %abi_builtins = $flags->get_builtins('abi'); | ||||
471 | 81 | 120 | my $cc_abi_time64 = $flags->get_option_value('cc-abi-time64'); | ||||
472 | |||||||
473 | 81 | 163 | if ($flags->use_feature('abi', 'lfs') && ! $abi_builtins{lfs}) { | ||||
474 | 18 | 33 | $flags->append('CPPFLAGS', | ||||
475 | '-D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64'); | ||||||
476 | } elsif (! $flags->use_feature('abi', 'lfs') && | ||||||
477 | ! $abi_builtins{lfs} && $cc_abi_time64) { | ||||||
478 | 3 | 3 | $flags->append('CPPFLAGS', | ||||
479 | '-U_LARGEFILE_SOURCE -U_FILE_OFFSET_BITS'); | ||||||
480 | } | ||||||
481 | |||||||
482 | 81 | 106 | if ($flags->use_feature('abi', 'time64') && ! $abi_builtins{time64}) { | ||||
483 | 9 | 17 | $flags->append('CPPFLAGS', '-D_TIME_BITS=64'); | ||||
484 | } elsif (! $flags->use_feature('abi', 'time64') && | ||||||
485 | ! $abi_builtins{time64} && $cc_abi_time64) { | ||||||
486 | 3 | 4 | $flags->append('CPPFLAGS', '-U_TIME_BITS'); | ||||
487 | } | ||||||
488 | |||||||
489 | ## Area: qa | ||||||
490 | |||||||
491 | # Warnings that detect actual bugs. | ||||||
492 | 81 | 123 | if ($flags->use_feature('qa', 'bug-implicit-func')) { | ||||
493 | 75 | 101 | $flags->append('CFLAGS', '-Werror=implicit-function-declaration'); | ||||
494 | } else { | ||||||
495 | 6 | 7 | $flags->append('CFLAGS', '-Wno-error=implicit-function-declaration'); | ||||
496 | } | ||||||
497 | 81 | 108 | if ($flags->use_feature('qa', 'bug')) { | ||||
498 | # C/C++ flags | ||||||
499 | 6 | 7 | my @cfamilyflags = qw( | ||||
500 | array-bounds | ||||||
501 | clobbered | ||||||
502 | volatile-register-var | ||||||
503 | ); | ||||||
504 | 6 | 6 | foreach my $warnflag (@cfamilyflags) { | ||||
505 | 18 | 22 | $flags->append('CFLAGS', "-Werror=$warnflag"); | ||||
506 | 18 | 19 | $flags->append('CXXFLAGS', "-Werror=$warnflag"); | ||||
507 | } | ||||||
508 | } | ||||||
509 | |||||||
510 | # Inject dummy canary options to detect issues with build flag propagation. | ||||||
511 | 81 | 120 | if ($flags->use_feature('qa', 'canary')) { | ||||
512 | 0 | 0 | require Digest::MD5; | ||||
513 | 0 | 0 | my $id = Digest::MD5::md5_hex(int rand 4096); | ||||
514 | |||||||
515 | 0 | 0 | foreach my $flag (qw(CPPFLAGS CFLAGS OBJCFLAGS CXXFLAGS OBJCXXFLAGS)) { | ||||
516 | 0 | 0 | $flags->append($flag, "-D__DEB_CANARY_${flag}_${id}__"); | ||||
517 | } | ||||||
518 | 0 | 0 | $flags->append('LDFLAGS', "-Wl,-z,deb-canary-${id}"); | ||||
519 | } | ||||||
520 | |||||||
521 | ## Area: reproducible | ||||||
522 | |||||||
523 | # Warn when the __TIME__, __DATE__ and __TIMESTAMP__ macros are used. | ||||||
524 | 81 | 144 | if ($flags->use_feature('reproducible', 'timeless')) { | ||||
525 | 81 | 106 | $flags->append('CPPFLAGS', '-Wdate-time'); | ||||
526 | } | ||||||
527 | |||||||
528 | # Avoid storing the build path in the binaries. | ||||||
529 | 81 | 108 | if ($flags->use_feature('reproducible', 'fixfilepath') or | ||||
530 | $flags->use_feature('reproducible', 'fixdebugpath')) { | ||||||
531 | 81 | 99 | my $build_path = $flags->get_option_value('build-path'); | ||||
532 | 81 | 72 | my $map; | ||||
533 | |||||||
534 | # -ffile-prefix-map is a superset of -fdebug-prefix-map, prefer it | ||||||
535 | # if both are set. | ||||||
536 | 81 | 89 | if ($flags->use_feature('reproducible', 'fixfilepath')) { | ||||
537 | 81 | 96 | $map = '-ffile-prefix-map=' . $build_path . '=.'; | ||||
538 | } else { | ||||||
539 | 0 | 0 | $map = '-fdebug-prefix-map=' . $build_path . '=.'; | ||||
540 | } | ||||||
541 | |||||||
542 | 81 | 136 | $flags->append($_, $map) foreach @compile_flags; | ||||
543 | } | ||||||
544 | |||||||
545 | ## Area: optimize | ||||||
546 | |||||||
547 | 81 | 107 | if ($flags->use_feature('optimize', 'lto')) { | ||||
548 | 9 | 8 | my $flag = '-flto=auto -ffat-lto-objects'; | ||||
549 | 9 | 11 | $flags->append($_, $flag) foreach (@compile_flags, 'LDFLAGS'); | ||||
550 | } | ||||||
551 | |||||||
552 | ## Area: sanitize | ||||||
553 | |||||||
554 | 81 | 100 | if ($flags->use_feature('sanitize', 'address')) { | ||||
555 | 0 | 0 | my $flag = '-fsanitize=address -fno-omit-frame-pointer'; | ||||
556 | 0 | 0 | $flags->append('CFLAGS', $flag); | ||||
557 | 0 | 0 | $flags->append('CXXFLAGS', $flag); | ||||
558 | 0 | 0 | $flags->append('LDFLAGS', '-fsanitize=address'); | ||||
559 | } | ||||||
560 | |||||||
561 | 81 | 101 | if ($flags->use_feature('sanitize', 'thread')) { | ||||
562 | 0 | 0 | my $flag = '-fsanitize=thread'; | ||||
563 | 0 | 0 | $flags->append('CFLAGS', $flag); | ||||
564 | 0 | 0 | $flags->append('CXXFLAGS', $flag); | ||||
565 | 0 | 0 | $flags->append('LDFLAGS', $flag); | ||||
566 | } | ||||||
567 | |||||||
568 | 81 | 104 | if ($flags->use_feature('sanitize', 'leak')) { | ||||
569 | 0 | 0 | $flags->append('LDFLAGS', '-fsanitize=leak'); | ||||
570 | } | ||||||
571 | |||||||
572 | 81 | 91 | if ($flags->use_feature('sanitize', 'undefined')) { | ||||
573 | 0 | 0 | my $flag = '-fsanitize=undefined'; | ||||
574 | 0 | 0 | $flags->append('CFLAGS', $flag); | ||||
575 | 0 | 0 | $flags->append('CXXFLAGS', $flag); | ||||
576 | 0 | 0 | $flags->append('LDFLAGS', $flag); | ||||
577 | } | ||||||
578 | |||||||
579 | ## Area: hardening | ||||||
580 | |||||||
581 | # PIE | ||||||
582 | 81 | 125 | my $use_pie = $flags->get_feature('hardening', 'pie'); | ||||
583 | 81 | 120 | my %hardening_builtins = $flags->get_builtins('hardening'); | ||||
584 | 81 | 261 | if (defined $use_pie && $use_pie && ! $hardening_builtins{pie}) { | ||||
585 | 0 | 0 | my $flag = "-specs=$Dpkg::DATADIR/pie-compile.specs"; | ||||
586 | 0 | 0 | $flags->append($_, $flag) foreach @compile_flags; | ||||
587 | 0 | 0 | $flags->append('LDFLAGS', "-specs=$Dpkg::DATADIR/pie-link.specs"); | ||||
588 | } elsif (defined $use_pie && ! $use_pie && $hardening_builtins{pie}) { | ||||||
589 | 0 | 0 | my $flag = "-specs=$Dpkg::DATADIR/no-pie-compile.specs"; | ||||
590 | 0 | 0 | $flags->append($_, $flag) foreach @compile_flags; | ||||
591 | 0 | 0 | $flags->append('LDFLAGS', "-specs=$Dpkg::DATADIR/no-pie-link.specs"); | ||||
592 | } | ||||||
593 | |||||||
594 | # Stack protector | ||||||
595 | 81 | 98 | if ($flags->use_feature('hardening', 'stackprotectorstrong')) { | ||||
596 | 81 | 73 | my $flag = '-fstack-protector-strong'; | ||||
597 | 81 | 124 | $flags->append($_, $flag) foreach @compile_flags; | ||||
598 | } elsif ($flags->use_feature('hardening', 'stackprotector')) { | ||||||
599 | 0 | 0 | my $flag = '-fstack-protector --param=ssp-buffer-size=4'; | ||||
600 | 0 | 0 | $flags->append($_, $flag) foreach @compile_flags; | ||||
601 | } | ||||||
602 | |||||||
603 | # Stack clash | ||||||
604 | 81 | 124 | if ($flags->use_feature('hardening', 'stackclash')) { | ||||
605 | 42 | 36 | my $flag = '-fstack-clash-protection'; | ||||
606 | 42 | 60 | $flags->append($_, $flag) foreach @compile_flags; | ||||
607 | } | ||||||
608 | |||||||
609 | # Fortify Source | ||||||
610 | 81 | 107 | if ($flags->use_feature('hardening', 'fortify')) { | ||||
611 | 81 | 94 | my $fortify_level = $flags->get_option_value('fortify-level'); | ||||
612 | 81 | 134 | $flags->append('CPPFLAGS', "-D_FORTIFY_SOURCE=$fortify_level"); | ||||
613 | } | ||||||
614 | |||||||
615 | # Format Security | ||||||
616 | 81 | 111 | if ($flags->use_feature('hardening', 'format')) { | ||||
617 | 81 | 72 | my $flag = '-Wformat -Werror=format-security'; | ||||
618 | 81 | 99 | $flags->append('CFLAGS', $flag); | ||||
619 | 81 | 88 | $flags->append('CXXFLAGS', $flag); | ||||
620 | 81 | 91 | $flags->append('OBJCFLAGS', $flag); | ||||
621 | 81 | 92 | $flags->append('OBJCXXFLAGS', $flag); | ||||
622 | } | ||||||
623 | |||||||
624 | # Read-only Relocations | ||||||
625 | 81 | 97 | if ($flags->use_feature('hardening', 'relro')) { | ||||
626 | 81 | 88 | $flags->append('LDFLAGS', '-Wl,-z,relro'); | ||||
627 | } | ||||||
628 | |||||||
629 | # Bindnow | ||||||
630 | 81 | 100 | if ($flags->use_feature('hardening', 'bindnow')) { | ||||
631 | 0 | 0 | $flags->append('LDFLAGS', '-Wl,-z,now'); | ||||
632 | } | ||||||
633 | |||||||
634 | # Branch protection | ||||||
635 | 81 | 112 | if ($flags->use_feature('hardening', 'branch')) { | ||||
636 | 33 | 43 | my $cpu = $flags->get_option_value('hardening-branch-cpu'); | ||||
637 | 33 | 24 | my $flag; | ||||
638 | 33 | 70 | if ($cpu eq 'arm64') { | ||||
639 | 0 | 0 | $flag = '-mbranch-protection=standard'; | ||||
640 | } elsif ($cpu eq 'amd64') { | ||||||
641 | 33 | 33 | $flag = '-fcf-protection'; | ||||
642 | } | ||||||
643 | # The following should always be true on Debian, but it might not | ||||||
644 | # be on derivatives. | ||||||
645 | 33 | 36 | if (defined $flag) { | ||||
646 | 33 | 43 | $flags->append($_, $flag) foreach @compile_flags; | ||||
647 | } | ||||||
648 | } | ||||||
649 | |||||||
650 | # XXX: Handle *_FOR_BUILD flags here until we can properly initialize them. | ||||||
651 | 81 | 264 | require Dpkg::Arch; | ||||
652 | |||||||
653 | 81 | 152 | my $host_arch = Dpkg::Arch::get_host_arch(); | ||||
654 | 81 | 148 | my $build_arch = Dpkg::Arch::get_build_arch(); | ||||
655 | |||||||
656 | 81 | 137 | if ($host_arch eq $build_arch) { | ||||
657 | 72 | 162 | foreach my $flag ($flags->list()) { | ||||
658 | 1440 | 1838 | next if $flag =~ m/_FOR_BUILD$/; | ||||
659 | 720 | 626 | my $value = $flags->get($flag); | ||||
660 | 720 | 756 | $flags->append($flag . '_FOR_BUILD', $value); | ||||
661 | } | ||||||
662 | } else { | ||||||
663 | 9 | 22 | $flags->append($_ . '_FOR_BUILD', $default_flags) foreach @compile_flags; | ||||
664 | 9 | 10 | $flags->append('DFLAGS_FOR_BUILD', $default_d_flags); | ||||
665 | } | ||||||
666 | } | ||||||
667 | |||||||
668 | sub _build_tainted_by { | ||||||
669 | 0 | my $self = shift; | |||||
670 | 0 | my %tainted; | |||||
671 | |||||||
672 | 0 | foreach my $pathname (qw(/bin /sbin /lib /lib32 /libo32 /libx32 /lib64)) { | |||||
673 | 0 | next unless -l $pathname; | |||||
674 | |||||||
675 | 0 | my $linkname = readlink $pathname; | |||||
676 | 0 | if ($linkname eq "usr$pathname" or $linkname eq "/usr$pathname") { | |||||
677 | 0 | $tainted{'merged-usr-via-aliased-dirs'} = 1; | |||||
678 | 0 | last; | |||||
679 | } | ||||||
680 | } | ||||||
681 | |||||||
682 | 0 | require File::Find; | |||||
683 | 0 | my %usr_local_types = ( | |||||
684 | configs => [ qw(etc) ], | ||||||
685 | includes => [ qw(include) ], | ||||||
686 | programs => [ qw(bin sbin) ], | ||||||
687 | libraries => [ qw(lib) ], | ||||||
688 | ); | ||||||
689 | 0 | foreach my $type (keys %usr_local_types) { | |||||
690 | File::Find::find({ | ||||||
691 | 0 | wanted => sub { $tainted{"usr-local-has-$type"} = 1 if -f }, | |||||
692 | no_chdir => 1, | ||||||
693 | 0 0 0 0 | }, grep { -d } map { "/usr/local/$_" } @{$usr_local_types{$type}}); | |||||
694 | } | ||||||
695 | |||||||
696 | 0 | my @tainted = sort keys %tainted; | |||||
697 | 0 | return @tainted; | |||||
698 | } | ||||||
699 | |||||||
700 - 706 | =head1 CHANGES =head2 Version 0.xx This is a private module. =cut | ||||||
707 | |||||||
708 | 1; |