[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

PAC and BTI support on arm64



This mail is about whether, and how best, to enable Pointer
Authentication and Branch Target Identification (PAC+BTI) on arm64 in
Debian.

Summary:

Pointer Authentication and Branch Target Identification are
significant new security features in ARMv8.3 and ARMv8.5 respectively
arm64 hardware. They are present in new (debian-relevant) hardware,
starting with the Graviton 3. It is both straightforward and
reasonably safe to enable these features by default now so that they
can be reasonably well-tested in time for Bookworm. There is a kernel
option to turn them off at runtime should hardware be found where this
is a problem, and of course a compiler option to disable them at build
time. They are important security enhancements, with a very small
overhead, which can only work if enabled at build-time, so adding
-mbranch-protection=standard to the default build options seems like
the right thing to do. 

Discussion:

See below for the details of these options, what they do, how they
work, etc, and the argument for why they should be enabled by default.
My main question at this point is about exactly how this should be
dealt with in dpkg and/or (Debian) gcc.

The -mbranch-protection=standard option could be set either by dpkg or
by gcc. I'm not quite sure how we decide which is most appropriate?
They could be an unconditional architecture default, or could be part
of the dpkg hardening flags. It could be one hardening feature 'PACBTI',
or separate 'PAC' and 'BTI' features (with corresponding logic to set
the right flags).

Attached is my simple-minded example patch to dpkg to set it
unconditionally (for arm64). This is not intended to be final, but
illustrates what I have tried so far.

A notable aspect of this flag is that it is arch-specific. It should
not be issued when targetting arches other than arm64 as it is not a
valid flag there. dpkg-buildflags gets this right with below patch.
The -fcf-protection option on amd64 has the same
characteristic and indeed is pretty-much the same functionality (as
BTI). (It might have been better if the gcc developers had had one
option that added this type of branch protection on both
architectures, and ignored it on other arches, but they didn't.) That
option appears to have been set by default in Ubuntu 19.10 gcc
targetting aarch64 (although it's a bit hard to tell as gcc dumpspecs
is pretty cryptic, and I could be wrong.

The hardening features seem to assume that they are all
implemented/available on all architectures? I'm not sure if extra work
would be needed for a hardening feature that only existed on one arch
(so far) (PAC). (BTI exists on two arches). Perhaps all this is an
argument for just turning it on by default in gcc instead?

Currently the small number of packages that fail to build with
-mbranch-protection=standard are almost all because the build does a
cross-build of some sort, and the non-arm64 toolchain barfs on the
unrecognised option. A simple example whch dies is nmap (running
i686-w64-mingw32-gcc ).

Given that dpkg-buildflags does this right (with my patch):
DEB_HOST_ARCH=amd64 dpkg-buildflags --get CFLAGS
-g -O2 -ffile-prefix-map=/home/wookey/packages/dpkg-1.21.8=.
-fstack-protector-strong -Wformat -Werror=format-security

DEB_HOST_ARCH=arm64 dpkg-buildflags --get CFLAGS
-g -O2 -mbranch-protection=standard
-ffile-prefix-map=/home/wookey/packages/dpkg-1.21.8=.
-fstack-protector-strong -Wformat -Werror=format-security

I presume these are bugs in the packaging to do with not setting the
HOST_ARCH correctly, or assuming that flags do not
vary by architecture. This needs further investigation.

Hardening flags are not set by default but are strongly recomended for
package builds, and flagged by bldc if not present, so whilst setting
this option in there will not give as complete coverage as setting it by default, it
should end up being applied to most builds.

As I said, I'm not sure what our policy is on what goes in gcc default
flags, dpkg default flags or dpkg feature flags. So I'm open to
suggestions on the best/right way to implement this, then will prepare
patches/file bugs.

Details on PAC and BTI:

An important security feature on arm64 hardware that supports it
(ARMv8.3 or later), is Pointer Authentication ('PAC'). This allows
pointers to be tagged (signed) and checked, mitigating some types of
attack that corrupt and/or replace pointers ('Return Oriented
Programming' (ROP) attacks). Details on how it actually works can be
found here (2019 Suse Conf talk:
https://www.youtube.com/watch?v=iW3mXDSijSQ). ARM developer blog:
https://developer.arm.com/documentation/102433/0100/Return-oriented-programming?lang=en

There are both user and kernel aspects to the support. Both need to be
enabled to get the functionality. The user aspect is simple to enable
and not intrusive so we think it should be enabled by default on arm64
builds. This is done by setting a suitable -mbranch-protection=
option. ('standard', 'pac-ret' or 'pac-ret+leaf', see below).
Functionality was implemented in gcc7 but 10.2 is recommended.

There is also the related functionality Branch Target Identification
(BTI) (available in ARMv8.5 onwards) which creates defined 'landing
pads' as the only places where indirect jumps are allowed to land.

This protects against 'Jump Oriented Programming' (JOP) attacks.
https://developer.arm.com/documentation/102433/0100/Jump-oriented-programming?lang=en

This functionality (emitting these instructions) is enabled with
-mbranch-protection=bti which has been available since gcc9.1 (and
binutils 2.32) (but gcc10.2 or later is recommended)

The new instructions which actually generate and check these tagged
pointers and 'landing pads' are implemented in the NOP space of
ARMv8.0/8.1/8.2 so that they have no effect at all on older hardware
which does not have this functionality. This means that the features
should (by design) be safe to enable by default so that they work on
hardware with support, but do nothing on hardware which does not have
it. In principle there is a small overhead to fetching (and
not-executing) these instructions on earlier hardware, but due to
packing rules and pipelining most of the time it won't actually make
any difference. The overhead that does exist (either as NOPs on older
hardware or actual instructions on newer hardware) is considered
worthwhile for the improved security, and enabling these by default
fit's with debian's general approach of taking security seriously,
even if that adds some small overhead. 

In practice, because these features have complimentary
characteristics, and are safe on older instruction set/hardware
versions in the same way, it is recommended to enable both together
using the GCC option -mbranch-protection=standard, which enables both
PAC and BTI instructions to be emitted.

The only known compatibility risk is running modern binaries (built
with PAC enabled) on modern hardware (ARMv8.3 or newer) on binaries
(e.g. in an old chroot) built with gcc older than 7 (so that the
tagged pointers are not recognised and properly masked), which might
cause hangs/faults during exception unwinding, should some
exception-causing error occur. I think gcc7 was the default compiler
in Stretch/oldoldstable? The number of people running oldoldstable
chroots/containers on very new hardware should be quite small, so I
think we can live with this. The alternative is to wait for another
release cycle.

Obviously this is a potentially intrusive change, despite the
backwards-compatible design, so we have done a mass rebuild of the
archive with this option set to see if there were any problems. Of
14371 packages there were 12 packages with build issues, 4 of those
were trying to use -mbranch-protection=standard with an x86 compiler
due to the simplistic way the flags were set for the test: echo -e
"APPEND CFLAGS -mbranch-protection=standard\nAPPEND CXXFLAGS
-mbranch-protection=standard"> /etc/dpkg/buildflags.conf (which
applies to all arches, not just arm64)

Logs are here: http://qa-logs.debian.net/2021/11/18/

So that leaves a few packages (<8) that do appear to need
investigation. I will file bugs for any changes needed.

General info on arm hardware changes+compiler/kernel support:
https://en.opensuse.org/Arm_architecture_support

I have implemented this in the Debian Vendor options, but actually it
should probably be turned on everywhere unless some distro has a good
reason not to. IIUC the debian settings are inherited unless overridden?

Wookey
-- 
Principal hats:  Debian, Wookware, ARM
http://wookware.org/
--- scripts/Dpkg/Vendor/Debian.pm.orig	2022-03-26 17:17:59.000000000 +0000
+++ scripts/Dpkg/Vendor/Debian.pm	2022-05-29 01:20:36.368000000 +0000
@@ -185,6 +185,15 @@
     $flags->append($_, $default_flags) foreach @compile_flags;
     $flags->append('DFLAGS', $default_d_flags);
 
+    ## Area: arch-specific
+
+    if ($arch eq 'arm64') {
+        my $flag = '-mbranch-protection=standard';
+        $flags->append('CFLAGS', $flag);
+        $flags->append('CXXFLAGS', $flag);
+    }
+
+
     ## Area: future
 
     if ($use_feature{future}{lfs}) {

Attachment: signature.asc
Description: PGP signature


Reply to: