pkgsrc/doc/guide/files/hardening.xml

643 lines
18 KiB
XML

<!-- $NetBSD: hardening.xml,v 1.6 2021/11/08 22:16:21 gutteridge Exp $ -->
<appendix id="hardening">
<title>Security hardening</title>
<para>
A number of mechanisms are available in pkgsrc to improve the security of the
resulting system. This page describes the mechanisms, and gives hints
about detecting and fixing problems.
</para>
<para>
Mechanisms can be enabled individually in
<filename>mk.conf</filename>, and are
individually described below.
</para>
<para>
Typically, a feature will cause some programs to fail to build or work
when first enabled. This can be due to latent problems in the
program, and can be due to other reasons. After enough testing to
have confidence that user problems will be quite rare, individual
mechanisms will be enabled by default.
</para>
<para>
For each mechanism, see the Caveats section below for an explanation
of what might go wrong at compile time and at run time, and how to
notice and address these problems.
</para>
<sect1 id="hardening.mechanisms">
<title>Mechanisms</title>
<sect2 id="hardening.mechanisms.enabled">
<title>Enabled by default</title>
<sect3 id="hardening.mechanisms.enabled.fortify">
<title>PKGSRC_USE_FORTIFY</title>
<para>
This allows substitute wrappers to be used for some commonly used
library functions that do not have built-in bounds checking - but
could in some cases.
</para>
<para>
Two mitigation levels are available:
</para>
<itemizedlist>
<listitem>
<para>"weak" only enables checks at compile-time.</para>
</listitem>
<listitem>
<para>"strong" enables checks at compile-time and runtime.</para>
</listitem>
</itemizedlist>
<para>
"strong" has been enabled by default since pkgsrc-2017Q3.
</para>
</sect3>
<sect3 id="hardening.mechanisms.enabled.ssp">
<title>PKGSRC_USE_SSP</title>
<para>
This enables a stack-smashing protection mitigation. It is done by adding a
guard variable to functions with vulnerable objects. The guards are initialized
when a function is entered and then checked when the function exits. The guard
check will fail and the program forcibly exited if the variable was modified in
the meantime. This can happen in case of buffer overflows or memory corruption,
and therefore exposing these bugs.
</para>
<para>
Different mitigation levels are available:
</para>
<itemizedlist>
<listitem>
<para>"yes", which will only protect functions considered vulnerable
by the compiler;</para>
</listitem>
<listitem>
<para>"all", which will protect every function;</para>
</listitem>
<listitem>
<para>"strong", the default, which will apply a better balance between the
two settings above.</para>
</listitem>
</itemizedlist>
<para>
This mitigation is supported by both GCC and clang. It may be supported in
additional compilers, possibly under a different name. It is particularly useful
for unsafe programming languages, such as C/C++.
</para>
<itemizedlist>
<listitem>
<para>"yes" is enabled by default where known supported since pkgsrc-2017Q3.</para>
</listitem>
<listitem>
<para>"strong" is enabled by default where known supported since pkgsrc-2021Q4.</para>
</listitem>
</itemizedlist>
<para>More details can be found here:</para>
<itemizedlist>
<listitem>
<para>
<ulink url="https://en.wikipedia.org/wiki/Buffer_overflow_protection">Buffer overflow protection on Wikipedia</ulink>
</para>
</listitem>
</itemizedlist>
</sect3>
<sect3 id="hardening.mechanisms.enabled.pie">
<title>PKGSRC_MKPIE</title>
<para>
This requests the creation of PIE (Position Independent Executables) for all
executables. The PIE mechanism is normally used for shared libraries, so that
they can be loaded at differing addresses at runtime. PIE itself does not have
useful security properties; however, it is necessary to fully leverage some,
such as ASLR. Some operating systems support Address Space Layout Randomization
(ASLR), which causes different addresses to be used each time a program is run.
This makes it more difficult for an attacker to guess addresses and thus makes
exploits harder to construct. With PIE, ASLR can really be applied to the entire
program, instead of the stack and heap only.
</para>
<para>
PIE executables will only be built for toolchains that are known to support PIE.
Currently, this means NetBSD on x86, ARM, SPARC64, m68k, and MIPS.
</para>
<para>
<varname>PKGSRC_MKPIE</varname> was enabled by default after the pkgsrc-2021Q3 branch.
</para>
</sect3>
</sect2>
<sect2 id="hardening.mechanisms.disabled">
<title>Not enabled by default</title>
<sect3 id="hardening.mechanisms.disabled.repro">
<title>PKGSRC_MKREPRO</title>
<para>
With this option, pkgsrc will try to build packages reproducibly. This allows
packages built from the same tree and with the same options, to produce
identical results bit by bit. This option should be combined with ASLR and
<varname>PKGSRC_MKPIE</varname> to avoid predictable address offsets for
attackers attempting to exploit security vulnerabilities.
</para>
<para>
More details can be found here:
</para>
<itemizedlist>
<listitem>
<para>
<ulink url="https://reproducible-builds.org/">Reproducible Builds - a set of software development practices that create an independently-verifiable path from source to binary code</ulink>
</para>
</listitem>
</itemizedlist>
<para>
More work likely needs to be done before pkgsrc is fully reproducible.
</para>
</sect3>
<sect3 id="hardening.mechanisms.enabled.relro">
<title>PKGSRC_USE_RELRO</title>
<para>
This also makes the exploitation of some security vulnerabilities more
difficult in some cases.
</para>
<para>Two different mitigation levels are available:</para>
<itemizedlist>
<listitem>
<para>
partial: the ELF sections are reordered so that internal data sections
precede the program's own data sections, and non-PLT GOT is read-only;
</para>
</listitem>
<listitem>
<para>
full: in addition to partial RELRO, every relocation is performed immediately
when starting the program (with a slight performance impact), allowing the
entire GOT to be read-only.
</para>
</listitem>
</itemizedlist>
<para>
This is currently supported by GCC. Many software distributions now enable this
feature by default, at the "partial" level. However, it cannot yet be enforced
globally in pkgsrc through cwrappers.
</para>
<para>
More details can be found here:
</para>
<itemizedlist>
<listitem>
<para>
<ulink url="https://www.redhat.com/en/blog/hardening-elf-binaries-using-relocation-read-only-relro">Hardening ELF binaries using Relocation Read-Only (RELRO)</ulink>
</para>
</listitem>
</itemizedlist>
</sect3>
<sect3 id="hardening.mechanisms.disabled.stackcheck">
<title>PKGSRC_USE_STACK_CHECK</title>
<para>
This uses <literal>-fstack-check</literal> with GCC for
another stack protection mitigation.
</para>
<para>
It asks the compiler to generate code verifying that it does not corrupt the
stack. According to GCC's manual page, this is really only useful for
multi-threaded programs.
</para>
</sect3>
</sect2>
</sect1>
<sect1 id="hardening.caveats">
<title>Caveats</title>
<sect2 id="hardening.caveats.pie">
<title>Problems with PKGSRC_MKPIE</title>
<sect3 id="hardening.caveats.pie.build">
<title>Packages failing to build</title>
<para>
A number of packages may fail to build with this option enabled. The
failures are often related to the absence of the <literal>-fPIC</literal>
compilation flag when building libraries or executables (or ideally
<literal>-fPIE</literal> in the latter case). This flag is added to the
<varname>CFLAGS</varname> already, but requires the package to
actually support it.
</para>
<sect4 id="hardening.caveats.pie.build.fix">
<title>How to fix</title>
<para>
These instructions are meant as a reference only; they likely need to be adapted
for many packages individually.
</para>
<para>
For packages using <filename>Makefiles</filename>:
</para>
<programlisting>
MAKE_FLAGS+= CFLAGS=${CFLAGS:Q}
MAKE_FLAGS+= LDFLAGS=${LDFLAGS:Q}
</programlisting>
<para>
For packages using <filename>Imakefiles</filename>:
</para>
<programlisting>
MAKE_FLAGS+= CCOPTIONS=${CFLAGS:Q}
MAKE_FLAGS+= LOCAL_LDFLAGS=${LDFLAGS:Q}
</programlisting>
</sect4>
</sect3>
<sect3 id="hardening.caveats.pie.crash">
<title>Run-time crashes</title>
<para>
Some programs may fail to run, or crash at random times once built as PIE. Two
scenarios are essentially possible. This is nearly always due to a bug in
the program being exposed due to ASLR.
</para>
</sect3>
<sect3 id="hardening.caveats.pie.disable">
<title>Disabling PKGSRC_MKPIE on a per-package basis</title>
<para>
Ideally, packages should be fixed for compatibility with MKPIE.
However, in some cases this is very difficult, due to complex build systems,
packages using non-standard toolchains, or programming languages with odd
bootstrapping mechanisms.
</para>
<para>
To disable <varname>PKGSRC_MKPIE</varname> on a per-package basis, set
<varname>MKPIE_SUPPORTED= no</varname> in the package's Makefile before
<filename>bsd.prefs.mk</filename> is included.
</para>
</sect3>
</sect2>
<sect2 id="hardening.caveats.fortify">
<title>Problems with PKGSRC_USE_FORTIFY</title>
<sect3 id="hardening.caveats.fortify.build">
<title>Packages failing to build</title>
<para>
This feature makes use of pre-processing directives to look for hardened,
alternative implementations of essential library calls. Some programs may fail
to build as a result; this usually happens for those trying too hard to be
portable, or otherwise abusing definitions in the standard library.
</para>
</sect3>
<sect3 id="hardening.caveats.fortify.crash">
<title>Run-time crashes</title>
<para>
This feature may cause some programs to crash, usually indicating an
actual bug in the program. The fix will typically involve patching the
original program's source code.
</para>
</sect3>
<sect3 id="hardening.caveats.fortify.opt">
<title>Optimization is required</title>
<para>
At least in the case of GCC, FORTIFY will only be applied if optimization is
applied while compiling. This means that the <varname>CFLAGS</varname> should
also contain <literal>-O</literal>, <literal>-O2</literal> or another
optimization level. This cannot easily be applied globally, as some packages
may require specific optimization levels.
</para>
</sect3>
<sect3 id="hardening.caveats.fortify.disable">
<title>Disabling FORTIFY on a per-package basis</title>
<note>
<para>FORTIFY should not be disabled to work around runtime crashes in
the program! This is a very bad idea and will expose you to security
vulnerabilities.
</para>
</note>
<para>
To disable FORTIFY on a per-package basis, set the following
in the package's <filename>Makefile</filename>
before <filename>bsd.prefs.mk</filename> is included:
</para>
<programlisting>
FORTIFY_SUPPORTED= no
</programlisting>
</sect3>
</sect2>
<sect2 id="hardening.caveats.relro">
<title>Problems with PKGSRC_USE_RELRO</title>
<sect3 id="hardening.caveats.relro.performance">
<title>Performance impact</title>
<para>
For better protection, full RELRO requires every symbol to be resolved when the
program starts, rather than simply when required at run-time. This will have
more impact on programs using a lot of symbols, or linked to libraries exposing
a lot of symbols. Therefore, daemons or programs otherwise running in
background are affected only when started. Programs loading plug-ins at
run-time are affected when loading the plug-ins.
</para>
<para>
The impact is not expected to be noticeable on modern hardware, except in some
cases for big programs.
</para>
</sect3>
<sect3 id="hardening.caveats.relro.crash">
<title>Run-time crashes</title>
<para>
Some programs handle plug-ins and dependencies in a way that conflicts with
RELRO: for instance, with an initialization routine listing any other plug-in
required. With full RELRO, the missing symbols are resolved before the
initialization routine can run, and the dynamic loader will not be able to find
them directly and abort as a result. Unfortunately, this is how Xorg loads its
drivers. Partial RELRO can be applied instead in this case.
</para>
</sect3>
<sect3 id="hardening.caveats.relro.disable">
<title>Disabling RELRO on a per-package basis</title>
<para>
To disable RELRO on a per-package basis, set the following
in the package's <filename>Makefile</filename>
before <filename>bsd.prefs.mk</filename> is included:
</para>
<programlisting>
RELRO_SUPPORTED= no
</programlisting>
<para>
It is also possible to at most enable partial RELRO, by
setting <varname>RELRO_SUPPORTED</varname> to <literal>partial</literal>.
</para>
</sect3>
</sect2>
<sect2 id="hardening.caveats.ssp">
<title>Problems with PKGSRC_USE_SSP</title>
<sect3 id="hardening.caveats.ssp.build">
<title>Packages failing to build</title>
<para>
The stack-smashing protection provided by this option does not work for some
programs. The most common situation in which this happens is when the program
allocates variables on the stack, with the size determined at run-time.
</para>
</sect3>
<sect3 id="hardening.caveats.ssp.crash">
<title>Run-time crashes</title>
<para>
Again, this feature may cause some programs to crash via a
<varname>SIGABRT</varname>, usually indicating an actual bug in the program.
</para>
<para>
On NetBSD <varname>LOG_CRIT</varname> level syslog
messages are sent and - by default -
appended to <filename>/var/log/messages</filename>, e.g.:
</para>
<programlisting>
Jan 6 15:42:51 hostname -: hostname program - - - buffer overflow detected; terminated
</programlisting>
<para>
(where <literal>hostname</literal> is the hostname(1) and
<literal>program</literal> is the basename(1) of the program crashed).
</para>
<para>
Patching the original program is then required.
</para>
<para>
Rebuilding the package via:
</para>
<programlisting>
<userinput>% env CFLAGS=-g INSTALL_UNSTRIPPED=yes make replace</userinput>
</programlisting>
<para>
and inspecting the backtrace of the coredump via the debugger
should point out the problematic call by inspecting the frame
calling the _chk() (SSP) function.
</para>
</sect3>
<sect3 id="hardening.caveats.ssp.performance">
<title>Performance impact</title>
<para>
The compiler emits extra code when using this feature: a check for buffer
overflows is performed when entering and exiting functions, requiring an extra
variable on the stack. The level of protection can otherwise be adjusted to
affect only those functions considered more sensitive by the compiler (with
-fstack-protector instead of -fstack-protector-all).
</para>
<para>
The impact is not expected to be noticeable on modern hardware. However,
programs with a hard requirement to run at the fastest possible speed should
avoid using this feature, or using libraries built with this feature.
</para>
</sect3>
<sect3 id="hardening.caveats.ssp.disable">
<title>Disabling SSP on a per-package basis</title>
<note>
<para>SSP should not be disabled to work around runtime crashes in
the program! This is a very bad idea and will expose you to security
vulnerabilities.</para>
</note>
<para>
To disable SSP on a per-package basis, set the following
in the package's <filename>Makefile</filename>
before <filename>bsd.prefs.mk</filename> is included:
</para>
<programlisting>
SSP_SUPPORTED= no
</programlisting>
</sect3>
</sect2>
</sect1>
<sect1 id="hardening.audit">
<title>Auditing the system</title>
<para>
The illusion of security is worse than having no security at all. This section
lists a number of ways to ensure the security features requested are actually
effective.
</para>
<para>
These instructions were obtained and tested on a system derived from NetBSD 7
(amd64). YMMV.
</para>
<sect2 id="hardening.audit.pie">
<title>Checking for PIE</title>
<para>
The ELF executable type in use changes for binaries built as PIE; without:
</para>
<programlisting>
<userinput>$ file /path/to/bin/ary</userinput>
/path/to/bin/ary: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for NetBSD 7.0, not stripped
</programlisting>
<para>as opposed to the following binary, built as PIE:</para>
<programlisting>
<userinput>$ file /path/to/pie/bin/ary</userinput>
/path/to/pie/bin/ary: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for NetBSD 7.0, not stripped
</programlisting>
<para>
The latter result is then what is expected.
</para>
</sect2>
<sect2 id="hardening.audit.relropartial">
<title>Checking for partial RELRO</title>
<para>
The following command should list a section called RELRO:
</para>
<programlisting>
<userinput>$ objdump -p /path/to/bin/ary</userinput>
/path/to/bin/ary: file format elf64-x86-64
Program Header:
[...]
RELRO off 0x0000000000000d78 vaddr 0x0000000000600d78 paddr 0x0000000000600d78 align 2**0
</programlisting>
<para>
This check is now performed automatically if
<varname>PKG_DEVELOPER</varname> is set and RELRO is enabled.
</para>
</sect2>
<sect2 id="hardening.audit.relrofull">
<title>Checking for full RELRO</title>
<para>
The dynamic loader will apply RELRO immediately when detecting the presence of
the <varname>BIND_NOW</varname> flag:
</para>
<programlisting>
<userinput>$ objdump -x /path/to/bin/ary</userinput>
/path/to/bin/ary: file format elf64-x86-64
Dynamic Section:
[...]
BIND_NOW 0x0000000000000000
</programlisting>
<para>
This has to be combined with partial RELRO (see above) to be fully efficient.
</para>
<para>
This check is now performed automatically (where supported) if
<varname>PKG_DEVELOPER</varname> is set.
</para>
</sect2>
<sect2 id="hardening.audit.ssp">
<title>Checking for SSP</title>
<note>
<para>
Checking for SSP using this method only works where the operating system
uses <literal>libssp</literal>. <literal>libssp</literal> is not used
on recent NetBSD/FreeBSD/Linux versions.
</para>
</note>
<para>
Building objects, binaries and libraries with SSP will affect the presence of
additional symbols in the resulting file:
</para>
<programlisting>
<userinput>$ nm /path/to/bin/ary</userinput>
[...]
U __stack_chk_fail
0000000000600ea0 B __stack_chk_guard
</programlisting>
<para>
This is an indicator that the program was indeed built with support for SSP.
</para>
<para>
This check is now performed automatically (where supported) if
<varname>PKG_DEVELOPER</varname> is set and SSP is enabled.
</para>
</sect2>
</sect1>
</appendix>