File Coverage

File:Dpkg/Vendor/Debian.pm
Coverage:48.3%

linestmtbrancondsubpodtimecode
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
21package Dpkg::Vendor::Debian;
22
23
19
19
19
330
12
226
use strict;
24
19
19
19
27
12
446
use warnings;
25
26our $VERSION = '0.01';
27
28
19
19
19
37
15
291
use Dpkg;
29
19
19
19
32
13
438
use Dpkg::Gettext;
30
19
19
19
41
11
481
use Dpkg::ErrorHandling;
31
19
19
19
31
17
563
use Dpkg::Control::Types;
32
33
19
19
19
28
11
34
use parent qw(Dpkg::Vendor::Default);
34
35=encoding utf8
36
37 - 46
=head1 NAME

Dpkg::Vendor::Debian - Debian vendor class

=head1 DESCRIPTION

This vendor class customizes the behaviour of dpkg scripts for Debian
specific behavior and policies.

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

=head2 Version 0.xx

This is a private module.

=cut
489
4901;