Skip to content

Conversation

fanquake
Copy link
Member

TLDR: We are generally explicit about the hardening related flags we use,
rather than letting the distro / toolchain decide via their defaults. This PR
adds -z,separate-code which has been enabled by default for Linux targets
since binutils 2.31. Ubuntu Bionic (currently used for gitian) ships with
binutils 2.30, so this will enable the option for those builds.

This flag was added to binutils/ld in the 2.30 release,
see commit c11c786f0b45617bb8807ab6a57220d5ff50e414:

The new "-z separate-code" option will generate separate code LOAD
segment which must be in wholly disjoint pages from any other data.

It was made the default for Linux/x86 targets in the 2.31 release, see commit
f6aec96dce1ddbd8961a3aa8a2925db2021719bb:

This patch adds --enable-separate-code to ld configure to turn on
-z separate-code by default and enables it by default for Linux/x86.
This avoids mixing code pages with data to improve cache performance
as well as security.

To reduce x86-64 executable and shared object sizes, the maximum page
size is reduced from 2MB to 4KB when -z separate-code is turned on by
default. Note: -z max-page-size= can be used to set the maximum page
size.

We compared SPEC CPU 2017 performance before and after this change on
Skylake server. There are no any significant performance changes.
Everything is mostly below +/-1%.

Support was also added to LLVMs lld: https://reviews.llvm.org/D64903, however
there it remains off by default.

There were concerns about an increase in binary size, however in our case, the
difference would seem negligible, given we are shipping a
multi-megabyte binary, which then downloads 100's of GBs of data.

Also note that most recent versions of distros are shipping a new enough version
of binutils that this is available and/or already on by default (assuming the distro
has not turned it off, I haven't checked everywhere):

CentOS 8: 2.30
Debian Buster 2.31.1
Fedora 29: 2.31.1
FreeBSD: 2.33
GNU Guix: 2.33 / 2.34
Ubuntu 18.04: 2.30

Related threads / discussion:
https://bugzilla.redhat.com/show_bug.cgi?id=1623218

The ELF header when building on Debian Buster (where it's already enabled by default in binutils):

Program Header:
    PHDR off    0x0000000000000040 vaddr 0x0000000000000040 paddr 0x0000000000000040 align 2**3
         filesz 0x00000000000002a0 memsz 0x00000000000002a0 flags r--
  INTERP off    0x00000000000002e0 vaddr 0x00000000000002e0 paddr 0x00000000000002e0 align 2**0
         filesz 0x000000000000001c memsz 0x000000000000001c flags r--
    LOAD off    0x0000000000000000 vaddr 0x0000000000000000 paddr 0x0000000000000000 align 2**12
         filesz 0x0000000000038f10 memsz 0x0000000000038f10 flags r--
    LOAD off    0x0000000000039000 vaddr 0x0000000000039000 paddr 0x0000000000039000 align 2**12
         filesz 0x00000000006b9389 memsz 0x00000000006b9389 flags r-x
    LOAD off    0x00000000006f3000 vaddr 0x00000000006f3000 paddr 0x00000000006f3000 align 2**12
         filesz 0x0000000000204847 memsz 0x0000000000204847 flags r--
    LOAD off    0x00000000008f7920 vaddr 0x00000000008f8920 paddr 0x00000000008f8920 align 2**12
         filesz 0x00000000000183e0 memsz 0x0000000000022fd0 flags rw-
 DYNAMIC off    0x000000000090adb0 vaddr 0x000000000090bdb0 paddr 0x000000000090bdb0 align 2**3
         filesz 0x0000000000000240 memsz 0x0000000000000240 flags rw-

vs when opting out using -Wl,-z,noseparate-code:

Program Header:
    PHDR off    0x0000000000000040 vaddr 0x0000000000000040 paddr 0x0000000000000040 align 2**3
         filesz 0x0000000000000230 memsz 0x0000000000000230 flags r--
  INTERP off    0x0000000000000270 vaddr 0x0000000000000270 paddr 0x0000000000000270 align 2**0
         filesz 0x000000000000001c memsz 0x000000000000001c flags r--
    LOAD off    0x0000000000000000 vaddr 0x0000000000000000 paddr 0x0000000000000000 align 2**12
         filesz 0x00000000008f6a87 memsz 0x00000000008f6a87 flags r-x
    LOAD off    0x00000000008f7920 vaddr 0x00000000008f8920 paddr 0x00000000008f8920 align 2**12
         filesz 0x00000000000183e0 memsz 0x0000000000022fd0 flags rw-
 DYNAMIC off    0x000000000090adb0 vaddr 0x000000000090bdb0 paddr 0x000000000090bdb0 align 2**3
         filesz 0x0000000000000240 memsz 0x0000000000000240 flags rw-

@laanwj
Copy link
Member

laanwj commented Jul 15, 2020

Sounds good to me. I'm surprised that this wasn't already the case. Read-only code and data was already kept apart from writable data. I guess mixing read-only code and read-only data has a smaller security impact. But this is better.
ACK d9315ea

@practicalswift
Copy link
Contributor

ACK d9315ea -- explicit is better than implicit, and hardened is better than non-hardened.

@fanquake Would it make sense to test for this in contrib/devtools/security-check.py?

@laanwj
Copy link
Member

laanwj commented Jul 15, 2020

@fanquake Would it make sense to test for this in contrib/devtools/security-check.py?

I think this is a good idea, but how would you go about this? It is not entirely trivial. I think you'd have to correlate sections with LOAD headers and check that data sections are in a separate LOAD command than text ones? Or maybe it's easier.

Without the option there is no LOAD with r-- flags:

    LOAD off    0x0000000000000000 vaddr 0x0000000000000000 paddr 0x0000000000000000 align 2**12
         filesz 0x00000000008f6a87 memsz 0x00000000008f6a87 flags r-x
    LOAD off    0x00000000008f7920 vaddr 0x00000000008f8920 paddr 0x00000000008f8920 align 2**12
         filesz 0x00000000000183e0 memsz 0x0000000000022fd0 flags rw-

But with the option, there are:

    LOAD off    0x0000000000000000 vaddr 0x0000000000000000 paddr 0x0000000000000000 align 2**12
         filesz 0x0000000000038f10 memsz 0x0000000000038f10 flags r--
    LOAD off    0x0000000000039000 vaddr 0x0000000000039000 paddr 0x0000000000039000 align 2**12
         filesz 0x00000000006b9389 memsz 0x00000000006b9389 flags r-x
    LOAD off    0x00000000006f3000 vaddr 0x00000000006f3000 paddr 0x00000000006f3000 align 2**12
         filesz 0x0000000000204847 memsz 0x0000000000204847 flags r--
    LOAD off    0x00000000008f7920 vaddr 0x00000000008f8920 paddr 0x00000000008f8920 align 2**12
         filesz 0x00000000000183e0 memsz 0x0000000000022fd0 flags rw-

Also, LOADs with different flags than the previous one are page-aligned (they need to be, otherwise it wouldn't work with MMU permission bits). Maybe that's enough of a heuristic, I don't know.

edit: the load offset for the writable data is curious: 0x00000000008f7920 is not page aligned, and right after the read-only data, does this mean read-only and read-write data are stilll allowed to share a page?

@DrahtBot
Copy link
Contributor

Gitian builds

File commit 3864219
(master)
commit 0e71576
(master and this pull)
*-aarch64-linux-gnu-debug.tar.gz 234341edaaa03287... 65912b74af773d35...
*-aarch64-linux-gnu.tar.gz fe6c16d320115507... cab15072dd82e425...
*-arm-linux-gnueabihf-debug.tar.gz 42a6b4a487d81b8f... 278abaf2024c36a9...
*-arm-linux-gnueabihf.tar.gz a47bf9a033950563... 1f28fa196446b5cf...
*-osx-unsigned.dmg 6b39d6bb9fcba41e... b6ac8d511ba53192...
*-osx64.tar.gz 9de75640b54a62ad... ef155d86cb19a078...
*-riscv64-linux-gnu-debug.tar.gz f2b02a01562d8c1e... 568473379fa92ff2...
*-riscv64-linux-gnu.tar.gz 0270bb629d738fc0... ad04660e7d76461b...
*-win64-debug.zip 38645188383170e2... 72f9ee55135537cf...
*-win64-setup-unsigned.exe 273a380bf2f19bf4... 207eb7ae48666205...
*-win64.zip bf93c8f5d2457a57... 20272ecb6f7a719b...
*-x86_64-linux-gnu-debug.tar.gz 2c2a010fb9114dd5... 440dd368b366af53...
*-x86_64-linux-gnu.tar.gz 8b953c29cccb10d7... f2cc58ffb894e6e3...
*.tar.gz 17eb6dc576244bd1... d3aa9d41188f0f2b...
bitcoin-core-linux-0.21-res.yml 8a85867b0f5145d2... fc8518e7d2dda5dd...
bitcoin-core-osx-0.21-res.yml e6a4c03cdb1fa972... f08517e6d1ae8c4d...
bitcoin-core-win-0.21-res.yml 5e5f75dcf64ee726... fe559aa22ab96534...
linux-build.log 8747037a8d02910b... ca2b56d42545f9f3...
osx-build.log 03a1f2575139de06... a270c13b6c0eab1a...
win-build.log 54377010d59fa536... 7cfa1e376e2ccc63...
bitcoin-core-linux-0.21-res.yml.diff 811f27b4706c4dcc...
bitcoin-core-osx-0.21-res.yml.diff 2787a64d4fb0a46d...
bitcoin-core-win-0.21-res.yml.diff 33dd43191cf127b3...
linux-build.log.diff d61c43724915e448...
osx-build.log.diff 238f267bb59ec733...
win-build.log.diff 3cecc6f263b3a1f3...

@DrahtBot
Copy link
Contributor

Guix builds

File commit c57dc56
(master)
commit f79f65f
(master and this pull)
*-aarch64-linux-gnu-debug.tar.gz 566af411261e2aa0... bd963b73142fcce5...
*-aarch64-linux-gnu.tar.gz 7098d0008b61a449... c6b2d2dab26e06e2...
*-arm-linux-gnueabihf-debug.tar.gz d9b0782d89dacd0d... 2aa1fd8d794a1d89...
*-arm-linux-gnueabihf.tar.gz 6b318a0a3bfbcd86... 0ab80087c479be99...
*-riscv64-linux-gnu-debug.tar.gz 2164a16f9e72927e... 5b1d643143930a58...
*-riscv64-linux-gnu.tar.gz 55300c09f682d41e... 027067c2004c63c5...
*-win-unsigned.tar.gz c80c3e8c4902547d... 5063b6c936054338...
*-win64-debug.zip f84a439d96b10b2c... d0ab68b4f40d5c64...
*-win64-setup-unsigned.exe 08f1e3447ba64b25... a11edbbd38be0cb6...
*-win64.zip 7c596ad5ed898c3d... 236fd90b013744e0...
*-x86_64-linux-gnu-debug.tar.gz 1e950159c1ebd917... eaee9d21cd0edc50...
*-x86_64-linux-gnu.tar.gz 218f46a1367fdd78... c08738f945f44e02...
*.tar.gz 447b166b795cb3bf... 9b248ff90bb093df...
guix_build.log 02503d54fb2b96fd... dab3df989d7b18d9...
guix_build.log.diff 42c37a010b469e6c...

@laanwj
Copy link
Member

laanwj commented Jul 22, 2020

I've implemented a security check here: https://github.com/laanwj/bitcoin/tree/2020_07_separate_code_security_check

I think it's correct, though it might be too strict, it might be enough to spot-check only a few sections like .text and .data and .rodata. Then again, sometimes it's better to be strict to catch future problems. Haven't tested it for all platforms so we might still get some surprises there.

@fanquake
Copy link
Member Author

I've implemented a security check here

@laanwj I've pulled in that commit, and queued up some builds. We'll see how it goes.

@DrahtBot
Copy link
Contributor

Guix builds

File commit 9d4b3d8
(master)
commit 709190f
(master and this pull)
*-aarch64-linux-gnu-debug.tar.gz ee5c5e4217fbfed2...
*-aarch64-linux-gnu.tar.gz f86bb6c0ad7fcefe...
*-arm-linux-gnueabihf-debug.tar.gz 8c5f6fe8dc06a4cd...
*-arm-linux-gnueabihf.tar.gz e40775114a1e4c76...
*-riscv64-linux-gnu-debug.tar.gz a0e1330a90bc693c...
*-riscv64-linux-gnu.tar.gz 4b3ed4ebcaa761e8...
*-win-unsigned.tar.gz 201e294136be418f...
*-win64-debug.zip ef9feb725c481542...
*-win64-setup-unsigned.exe a9a82f1086e7d15a...
*-win64.zip 9b53a39cc0a924cf...
*-x86_64-linux-gnu-debug.tar.gz 52f520026be1c25b...
*-x86_64-linux-gnu.tar.gz 2400ec1f1b053f44...
*.tar.gz 15838c39de5c26d0... ccf0dd4e0691112c...
guix_build.log bec07083e11aa537... baff261f594cfbbc...
guix_build.log.diff 93550109f0087483...

@maflcko
Copy link
Member

maflcko commented Jul 24, 2020

make[1]: Leaving directory '/bitcoin/distsrc-x86_64-linux-gnu'
+ make -C src --jobs=1 check-security V=1
make: Entering directory '/bitcoin/distsrc-x86_64-linux-gnu/src'
Checking binary security...
READELF=/gnu/store/pzq9s5ldl90h72p26jkdakyccdg9ib3k-profile/bin/x86_64-linux-gnu-readelf OBJDUMP=x86_64-linux-gnu-objdump OTOOL= /gnu/store/pzq9s5ldl90h72p26jkdakyccdg9ib3k-profile/bin/python3.7 ../contrib/devtools/security-check.py bitcoind  bitcoin-cli bitcoin-tx bitcoin-wallet test/test_bitcoin bench/bench_bitcoin qt/bitcoin-qt  
test/test_bitcoin: failed separate_code
qt/bitcoin-qt: failed separate_code
make: *** [Makefile:20581: check-security] Error 1
make: Leaving directory '/bitcoin/distsrc-x86_64-linux-gnu/src'

@laanwj
Copy link
Member

laanwj commented Jul 26, 2020

That's strange. It works locally for x86_64. This temporary patch might help figure out, it prints the mismatching sections and complete elfread output in case of failure:

diff --git a/contrib/devtools/security-check.py b/contrib/devtools/security-check.py
index 4f315e10d1d69157f93dfc47e1be8f3deb07a11a..dd16c132dfdf275b3767e8e230988f6d1343a14f 100755
--- a/contrib/devtools/security-check.py
+++ b/contrib/devtools/security-check.py
@@ -178,11 +178,15 @@ def check_ELF_separate_code(executable):
                 flags_per_section[section] = flags
     # Spot-check ELF LOAD program header flags per section
     # If these sections exist, check them against the expected R/W/E flags
+    retval = True
     for (section, flags) in flags_per_section.items():
         if section in EXPECTED_FLAGS:
             if EXPECTED_FLAGS[section] != flags:
-                return False
-    return True
+                print(f"Section {section} {flags} != {EXPECTED_FLAGS[section]}")
+                retval = False
+    if not retval:
+        subprocess.run([READELF_CMD, '-l', '-W', executable])
+    return retval
 
 def get_PE_dll_characteristics(executable) -> int:
     '''Get PE DllCharacteristics bits'''

@DrahtBot
Copy link
Contributor

Gitian builds

File commit 31d2b40
(master)
commit 1b84456
(master and this pull)
*-aarch64-linux-gnu-debug.tar.gz 986650d9f9356510...
*-aarch64-linux-gnu.tar.gz 79cbeaa704a96478...
*-arm-linux-gnueabihf-debug.tar.gz 96b7a3b9e284d8af...
*-arm-linux-gnueabihf.tar.gz d77ea94ffd48844b...
*-osx-unsigned.dmg c4ba5a10469d255b... 68ae84857ea1b5ca...
*-osx64.tar.gz 8315af462f7aaf5b... ea7a19e11c4e2cb1...
*-riscv64-linux-gnu-debug.tar.gz 752c5eb9842bf138...
*-riscv64-linux-gnu.tar.gz 9bfe016a353653d0...
*-win64-debug.zip 6a537508161d275b... a2b1abd52ae09942...
*-win64-setup-unsigned.exe 02b02775bf91659d... db0ac03da1bb0993...
*-win64.zip 9450ffd9bebef316... b9d0b4b5e9918a82...
*-x86_64-linux-gnu-debug.tar.gz 9f4775f31ab2905f...
*-x86_64-linux-gnu.tar.gz 6b3680f1c663c4bc...
*.tar.gz 2e123f5f13a89bee... ec414af92561646f...
bitcoin-core-linux-0.21-res.yml b94cf552667530aa...
bitcoin-core-osx-0.21-res.yml 4be566be7ae57858... 83d081a6e265a3cc...
bitcoin-core-win-0.21-res.yml c822642f66d98ce1... 6e6f7d60eb4717ac...
linux-build.log 4731c854ba7547d2... f2484b0a555335ba...
osx-build.log f22b5403060e556e... 4fcf4bc6e7aa7fc8...
win-build.log 480e6c4ea56db77a... 74f07e8ed13bfceb...
bitcoin-core-osx-0.21-res.yml.diff 7d8f345287faafe6...
bitcoin-core-win-0.21-res.yml.diff d78cc435c697530a...
linux-build.log.diff 9046276aeb31c3df...
osx-build.log.diff 071c7904e763bdad...
win-build.log.diff 878656c4928a737a...

@maflcko
Copy link
Member

maflcko commented Jul 27, 2020

guix error also on gitian:

make[1]: Leaving directory '/home/ubuntu/build/bitcoin/distsrc-x86_64-linux-gnu'
+ make -j1 -C src check-security
make: Entering directory '/home/ubuntu/build/bitcoin/distsrc-x86_64-linux-gnu/src'
Checking binary security...
test/test_bitcoin: failed separate_code
qt/bitcoin-qt: failed separate_code
Makefile:18662: recipe for target 'check-security' failed
make: *** [check-security] Error 1
make: Leaving directory '/home/ubuntu/build/bitcoin/distsrc-x86_64-linux-gnu/src'

@fanquake
Copy link
Member Author

That's strange. It works locally for x86_64. This temporary patch might help figure out, it prints the mismatching sections and complete elfread output in case of failure:

guix error also on gitian:

Thanks. I can recreate some issues locally, have been taking a look today.

'.plt.sec': 'R E',
'.text': 'R E',
'.fini': 'R E',
# Read-only data
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are some additional sections we can add here. Such as .qtmetadata and .gcc_except_table.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, but let's get it to pass first !

@laanwj
Copy link
Member

laanwj commented Jul 27, 2020

edit: the load offset for the writable data is curious: 0x00000000008f7920 is not page aligned, and right after the read-only data, does this mean read-only and read-write data are stilll allowed to share a page?

I'm still spooked by this, by the way. Not sure if it is an (upstream) bug or expected behavior. I thought about adding a check that LOAD ranges with different permissions don't overlap on the same memory pages, but this is a clear violation of this, making the separation less effective.

(in any case, not a reason to not move forward with this as it is, better is better)

Edit: it does seem to work out locally, maybe I just miscounted? I've added a per-page permission check in this commit: laanwj@9d9fcef

laanwj added a commit to laanwj/bitcoin that referenced this pull request Jul 27, 2020
9ae9b2a devtools: Add security check for separate_code (Wladimir J. van der Laan)
83b8ba0 build: add -Wl,-z,separate-code to hardening flags (fanquake)

Pull request description:

  TLDR: We are generally explicit about the hardening related flags we use,
  rather than letting the distro / toolchain decide via their defaults. This PR
  adds `-z,separate-code` which has been enabled by default for Linux targets
   since binutils 2.31. Ubuntu Bionic (currently used for gitian) ships with
  binutils 2.30, so this will enable the option for those builds.

  This flag was added to binutils/ld in the 2.30 release,
  see commit c11c786f0b45617bb8807ab6a57220d5ff50e414:

  > The new "-z separate-code" option will generate separate code LOAD
  segment which must be in wholly disjoint pages from any other data.

  It was made the default for Linux/x86 targets in the 2.31 release, see commit
  f6aec96dce1ddbd8961a3aa8a2925db2021719bb:

  > This patch adds --enable-separate-code to ld configure to turn on
  -z separate-code by default and enables it by default for Linux/x86.
  This avoids mixing code pages with data to improve cache performance
  as well as security.

  > To reduce x86-64 executable and shared object sizes, the maximum page
  size is reduced from 2MB to 4KB when -z separate-code is turned on by
  default.  Note: -z max-page-size= can be used to set the maximum page
  size.

  > We compared SPEC CPU 2017 performance before and after this change on
  Skylake server.  There are no any significant performance changes.
  Everything is mostly below +/-1%.

  Support was also added to LLVMs lld: https://reviews.llvm.org/D64903, however
  there it remains off by default.

  There were concerns about an increase in binary size, however in our case, the
  difference would seem negligible, given we are shipping a
  multi-megabyte binary, which then downloads 100's of GBs of data.

  Also note that most recent versions of distros are shipping a new enough version
  of binutils that this is available and/or already on by default (assuming the distro
  has not turned it off, I haven't checked everywhere):

  CentOS 8: 2.30
  Debian Buster 2.31.1
  Fedora 29: 2.31.1
  FreeBSD: 2.33
  GNU Guix: 2.33 / 2.34
  Ubuntu 18.04: 2.30

  Related threads / discussion:
  https://bugzilla.redhat.com/show_bug.cgi?id=1623218

  The ELF header when building on Debian Buster (where it's already enabled by default in binutils):
  ```bash
  Program Header:
      PHDR off    0x0000000000000040 vaddr 0x0000000000000040 paddr 0x0000000000000040 align 2**3
           filesz 0x00000000000002a0 memsz 0x00000000000002a0 flags r--
    INTERP off    0x00000000000002e0 vaddr 0x00000000000002e0 paddr 0x00000000000002e0 align 2**0
           filesz 0x000000000000001c memsz 0x000000000000001c flags r--
      LOAD off    0x0000000000000000 vaddr 0x0000000000000000 paddr 0x0000000000000000 align 2**12
           filesz 0x0000000000038f10 memsz 0x0000000000038f10 flags r--
      LOAD off    0x0000000000039000 vaddr 0x0000000000039000 paddr 0x0000000000039000 align 2**12
           filesz 0x00000000006b9389 memsz 0x00000000006b9389 flags r-x
      LOAD off    0x00000000006f3000 vaddr 0x00000000006f3000 paddr 0x00000000006f3000 align 2**12
           filesz 0x0000000000204847 memsz 0x0000000000204847 flags r--
      LOAD off    0x00000000008f7920 vaddr 0x00000000008f8920 paddr 0x00000000008f8920 align 2**12
           filesz 0x00000000000183e0 memsz 0x0000000000022fd0 flags rw-
   DYNAMIC off    0x000000000090adb0 vaddr 0x000000000090bdb0 paddr 0x000000000090bdb0 align 2**3
           filesz 0x0000000000000240 memsz 0x0000000000000240 flags rw-
  ```
   vs when opting out using `-Wl,-z,noseparate-code`:
  ```bash
  Program Header:
      PHDR off    0x0000000000000040 vaddr 0x0000000000000040 paddr 0x0000000000000040 align 2**3
           filesz 0x0000000000000230 memsz 0x0000000000000230 flags r--
    INTERP off    0x0000000000000270 vaddr 0x0000000000000270 paddr 0x0000000000000270 align 2**0
           filesz 0x000000000000001c memsz 0x000000000000001c flags r--
      LOAD off    0x0000000000000000 vaddr 0x0000000000000000 paddr 0x0000000000000000 align 2**12
           filesz 0x00000000008f6a87 memsz 0x00000000008f6a87 flags r-x
      LOAD off    0x00000000008f7920 vaddr 0x00000000008f8920 paddr 0x00000000008f8920 align 2**12
           filesz 0x00000000000183e0 memsz 0x0000000000022fd0 flags rw-
   DYNAMIC off    0x000000000090adb0 vaddr 0x000000000090bdb0 paddr 0x000000000090bdb0 align 2**3
           filesz 0x0000000000000240 memsz 0x0000000000000240 flags rw-
  ```
fanquake and others added 2 commits July 28, 2020 12:57
This flag was added to binutils/ld in the 2.30 release, 
see commit c11c786f0b45617bb8807ab6a57220d5ff50e414:

> The new "-z separate-code" option will generate separate code LOAD
segment which must be in wholly disjoint pages from any other data.


It was made the default for Linux/x86 targets in the 2.31 release, see commit
f6aec96dce1ddbd8961a3aa8a2925db2021719bb:

> This patch adds --enable-separate-code to ld configure to turn on
-z separate-code by default and enables it by default for Linux/x86.
This avoids mixing code pages with data to improve cache performance
as well as security.

> To reduce x86-64 executable and shared object sizes, the maximum page
size is reduced from 2MB to 4KB when -z separate-code is turned on by
default.  Note: -z max-page-size= can be used to set the maximum page
size.

> We compared SPEC CPU 2017 performance before and after this change on
Skylake server.  There are no any significant performance changes.
Everything is mostly below +/-1%.

Support was also added to LLVMs lld: https://reviews.llvm.org/D64903, however
there is remains off by default.

There were concerns about an increase in binary size, however in our case, the
increase (1 page worth of bytes) would seem negligible, given we are shipping a
multi-megabyte binary, which then downloads 100's of GBs of data.

Also note that most recent versions of distros are shipping a new enough version
of binutils that this is available and/or on by default (assuming the distro has
not turned it off, I haven't checked everywhere):

CentOS 8: 2.30
Debian Buster 2.31.1
Fedora 29: 2.31.1
FreeBSD: 2.33
GNU Guix: 2.33 / 2.34
Ubuntu 18.04: 2.30

Related threads / discussion:
https://bugzilla.redhat.com/show_bug.cgi?id=1623218
Check that sections are appropriately separated in virtual memory,
based on their (expected) permissions. This checks for missing
-Wl,-z,separate-code and potentially other problems.

Co-authored-by: fanquake <fanquake@gmail.com>
@fanquake
Copy link
Member Author

fanquake commented Jul 28, 2020

I've pushed up a modified version of @laanwj's test that works for me across all binaries, and I think is a bit more robust. One of the issues with the previous test was that when the binaries were large (i.e bitcoin-qt and test_bitcoin) the output in the readelf table would spill into adjacent columns, and you'd end up testing something like 9 R against R E etc. I've also added some additional sections to check for.

If someone wants to test, one way to at least sanity-check the behaviour is to modify the permission of one of the sections in EXPECTED_FLAGS. i.e:

diff --git a/contrib/devtools/security-check.py b/contrib/devtools/security-check.py
index dc74de919..965a9b250 100755
--- a/contrib/devtools/security-check.py
+++ b/contrib/devtools/security-check.py
@@ -146,7 +146,7 @@ def check_ELF_separate_code(executable):
         # Read + execute
         '.init': 'R E',
         '.plt': 'R E',
-        '.plt.got': 'R E',
+        '.plt.got': 'WRONG!',
         '.plt.sec': 'R E',
         '.text': 'R E',
         '.fini': 'R E',

and check that the test fails for all binaries (assuming you've chosen a section that is in all binaries. i.e NOT .qtmetadata).

make -C src check-security
make: Entering directory '/home/ubuntu/build/bitcoin/distsrc-x86_64-linux-gnu/src'
Checking binary security...
bitcoind: failed separate_code
bitcoin-cli: failed separate_code
bitcoin-tx: failed separate_code
bitcoin-wallet: failed separate_code
test/test_bitcoin: failed separate_code
qt/bitcoin-qt: failed separate_code

I'm still spooked by this, by the way. Not sure if it is an (upstream) bug or expected behavior. I thought about adding a check that LOAD ranges with different permissions don't overlap on the same memory pages, but this is a clear violation of this, making the separation less effective.

Now that I've got this test working, I'll take a look here as well. In any case, I think any changes / an additional test for that behaviour could be a in a followup PR.

@DrahtBot
Copy link
Contributor

Guix builds

File commit b62fbf9
(master)
commit c15df1c
(master and this pull)
*-aarch64-linux-gnu-debug.tar.gz 25331867e75dd6c6... f1702f5b9f73e215...
*-aarch64-linux-gnu.tar.gz 33bd4c083fffc6e9... 67e8544f749c261c...
*-arm-linux-gnueabihf-debug.tar.gz 216b5084ac49e5be... 9153a37e0aed1fd2...
*-arm-linux-gnueabihf.tar.gz 7e4181baf522a216... ddf26be19d05e21b...
*-riscv64-linux-gnu-debug.tar.gz 31d3c5b7d593ca32... 151aa4fa6e595b0f...
*-riscv64-linux-gnu.tar.gz 961e5a190241702a... a6a1162284c8aa38...
*-win-unsigned.tar.gz 88753999a310b885... cb0f494748d5718b...
*-win64-debug.zip a35a0db5f972af30... 2e13a174e4236895...
*-win64-setup-unsigned.exe 1c6f592ca493ddb0... 4b935553bfead346...
*-win64.zip 86d9fb12913b5ad0... 98b15ebf8bef6be7...
*-x86_64-linux-gnu-debug.tar.gz 3602d1d2b996dae2... c978987dd894b9b8...
*-x86_64-linux-gnu.tar.gz f3a179b1d4273f79... d1e6e65cc201fc8b...
*.tar.gz 61f52cd309786100... 91787788e64f900d...
guix_build.log 886b2c8d9f9a837a... d9d1825635bb0712...
guix_build.log.diff b0031a3f298f7cd0...

@laanwj
Copy link
Member

laanwj commented Jul 29, 2020

ACK 65d0f1a
Thanks for fixing the test. I think further improving the check can be left for another PR.

@laanwj laanwj merged commit 400f45e into bitcoin:master Jul 29, 2020
@DrahtBot
Copy link
Contributor

Gitian builds

File commit a41ae68
(master)
commit bb347b9
(master and this pull)
*-aarch64-linux-gnu-debug.tar.gz 2c5d1671feadd5e0... 3c0fd715dbd9b7a0...
*-aarch64-linux-gnu.tar.gz c7062ea145dcbd3b... 6162249a8e98a7ef...
*-arm-linux-gnueabihf-debug.tar.gz 7e77e81bd5ec6f8a... 0bb0155af048625e...
*-arm-linux-gnueabihf.tar.gz 84ccb33a9842229d... fc3f14284aac69da...
*-osx-unsigned.dmg 4a9adb0aa5ec9a6f... c06e32d004c769df...
*-osx64.tar.gz 545fa26bc01a9b79... 6215c7992e91d9c3...
*-riscv64-linux-gnu-debug.tar.gz 2c4d543bc1b43d33... a64218aa8cca8951...
*-riscv64-linux-gnu.tar.gz 41bca6ecee146491... c7a9ab7a95b44ee8...
*-win64-debug.zip 9f8bb80a3fd510b8... b63bdcdfd80e803f...
*-win64-setup-unsigned.exe db04c170eb0f0c75... feb50bc93ffb4ebe...
*-win64.zip c2ffbc59409f3c00... 566f4d00e2f56f50...
*-x86_64-linux-gnu-debug.tar.gz 15a0f0131e7daef1... 394afba7dd29e83d...
*-x86_64-linux-gnu.tar.gz 22446c2d1d1d1b4d... cb1fadaade103af7...
*.tar.gz a623828de9051d68... 6de849682e3590ea...
bitcoin-core-linux-0.21-res.yml 00484df79e5bcebf... 302d9c86e26183ea...
bitcoin-core-osx-0.21-res.yml f850bd04d4efe283... ddcc1fb531f6ebd1...
bitcoin-core-win-0.21-res.yml 5a1e861d61d968f1... a4deb3fe84abdac9...
linux-build.log 33a770b52a732fab... 8a58d8df90838c7d...
osx-build.log fe6f0571f7541f57... 878982b2b1cbac1c...
win-build.log c7cae424b8235045... 602c6042b075f7e8...
bitcoin-core-linux-0.21-res.yml.diff ac4aaf82fb0785bc...
bitcoin-core-osx-0.21-res.yml.diff 38eb8ddc16abd1a9...
bitcoin-core-win-0.21-res.yml.diff 4a2fcbcd3d64a039...
linux-build.log.diff 01e026137f373b9a...
osx-build.log.diff e419ab9b4a48c997...
win-build.log.diff 753eaf12447a5d91...

@fanquake fanquake deleted the z_separate_code branch July 30, 2020 04:58
@fanquake fanquake mentioned this pull request Jul 30, 2020
sidhujag pushed a commit to syscoin/syscoin that referenced this pull request Jul 31, 2020
65d0f1a devtools: Add security check for separate_code (Wladimir J. van der Laan)
2e9e637 build: add -Wl,-z,separate-code to hardening flags (fanquake)

Pull request description:

  TLDR: We are generally explicit about the hardening related flags we use,
  rather than letting the distro / toolchain decide via their defaults. This PR
  adds `-z,separate-code` which has been enabled by default for Linux targets
   since binutils 2.31. Ubuntu Bionic (currently used for gitian) ships with
  binutils 2.30, so this will enable the option for those builds.

  This flag was added to binutils/ld in the 2.30 release,
  see commit c11c786f0b45617bb8807ab6a57220d5ff50e414:

  > The new "-z separate-code" option will generate separate code LOAD
  segment which must be in wholly disjoint pages from any other data.

  It was made the default for Linux/x86 targets in the 2.31 release, see commit
  f6aec96dce1ddbd8961a3aa8a2925db2021719bb:

  > This patch adds --enable-separate-code to ld configure to turn on
  -z separate-code by default and enables it by default for Linux/x86.
  This avoids mixing code pages with data to improve cache performance
  as well as security.

  > To reduce x86-64 executable and shared object sizes, the maximum page
  size is reduced from 2MB to 4KB when -z separate-code is turned on by
  default.  Note: -z max-page-size= can be used to set the maximum page
  size.

  > We compared SPEC CPU 2017 performance before and after this change on
  Skylake server.  There are no any significant performance changes.
  Everything is mostly below +/-1%.

  Support was also added to LLVMs lld: https://reviews.llvm.org/D64903, however
  there it remains off by default.

  There were concerns about an increase in binary size, however in our case, the
  difference would seem negligible, given we are shipping a
  multi-megabyte binary, which then downloads 100's of GBs of data.

  Also note that most recent versions of distros are shipping a new enough version
  of binutils that this is available and/or already on by default (assuming the distro
  has not turned it off, I haven't checked everywhere):

  CentOS 8: 2.30
  Debian Buster 2.31.1
  Fedora 29: 2.31.1
  FreeBSD: 2.33
  GNU Guix: 2.33 / 2.34
  Ubuntu 18.04: 2.30

  Related threads / discussion:
  https://bugzilla.redhat.com/show_bug.cgi?id=1623218

  The ELF header when building on Debian Buster (where it's already enabled by default in binutils):
  ```bash
  Program Header:
      PHDR off    0x0000000000000040 vaddr 0x0000000000000040 paddr 0x0000000000000040 align 2**3
           filesz 0x00000000000002a0 memsz 0x00000000000002a0 flags r--
    INTERP off    0x00000000000002e0 vaddr 0x00000000000002e0 paddr 0x00000000000002e0 align 2**0
           filesz 0x000000000000001c memsz 0x000000000000001c flags r--
      LOAD off    0x0000000000000000 vaddr 0x0000000000000000 paddr 0x0000000000000000 align 2**12
           filesz 0x0000000000038f10 memsz 0x0000000000038f10 flags r--
      LOAD off    0x0000000000039000 vaddr 0x0000000000039000 paddr 0x0000000000039000 align 2**12
           filesz 0x00000000006b9389 memsz 0x00000000006b9389 flags r-x
      LOAD off    0x00000000006f3000 vaddr 0x00000000006f3000 paddr 0x00000000006f3000 align 2**12
           filesz 0x0000000000204847 memsz 0x0000000000204847 flags r--
      LOAD off    0x00000000008f7920 vaddr 0x00000000008f8920 paddr 0x00000000008f8920 align 2**12
           filesz 0x00000000000183e0 memsz 0x0000000000022fd0 flags rw-
   DYNAMIC off    0x000000000090adb0 vaddr 0x000000000090bdb0 paddr 0x000000000090bdb0 align 2**3
           filesz 0x0000000000000240 memsz 0x0000000000000240 flags rw-
  ```
   vs when opting out using `-Wl,-z,noseparate-code`:
  ```bash
  Program Header:
      PHDR off    0x0000000000000040 vaddr 0x0000000000000040 paddr 0x0000000000000040 align 2**3
           filesz 0x0000000000000230 memsz 0x0000000000000230 flags r--
    INTERP off    0x0000000000000270 vaddr 0x0000000000000270 paddr 0x0000000000000270 align 2**0
           filesz 0x000000000000001c memsz 0x000000000000001c flags r--
      LOAD off    0x0000000000000000 vaddr 0x0000000000000000 paddr 0x0000000000000000 align 2**12
           filesz 0x00000000008f6a87 memsz 0x00000000008f6a87 flags r-x
      LOAD off    0x00000000008f7920 vaddr 0x00000000008f8920 paddr 0x00000000008f8920 align 2**12
           filesz 0x00000000000183e0 memsz 0x0000000000022fd0 flags rw-
   DYNAMIC off    0x000000000090adb0 vaddr 0x000000000090bdb0 paddr 0x000000000090bdb0 align 2**3
           filesz 0x0000000000000240 memsz 0x0000000000000240 flags rw-
  ```

ACKs for top commit:
  laanwj:
    ACK 65d0f1a

Tree-SHA512: 6e40e434efea8a8e39f6cb244dfd16aaa5a9db5a2ea762a05d1727357b20e33b7e47c1a652ee88490c9d7952a4caa2f992396fb30346239300d37ae123e36d49
luke-jr pushed a commit to bitcoinknots/bitcoin that referenced this pull request Aug 15, 2020
This flag was added to binutils/ld in the 2.30 release,
see commit c11c786f0b45617bb8807ab6a57220d5ff50e414:

> The new "-z separate-code" option will generate separate code LOAD
segment which must be in wholly disjoint pages from any other data.

It was made the default for Linux/x86 targets in the 2.31 release, see commit
f6aec96dce1ddbd8961a3aa8a2925db2021719bb:

> This patch adds --enable-separate-code to ld configure to turn on
-z separate-code by default and enables it by default for Linux/x86.
This avoids mixing code pages with data to improve cache performance
as well as security.

> To reduce x86-64 executable and shared object sizes, the maximum page
size is reduced from 2MB to 4KB when -z separate-code is turned on by
default.  Note: -z max-page-size= can be used to set the maximum page
size.

> We compared SPEC CPU 2017 performance before and after this change on
Skylake server.  There are no any significant performance changes.
Everything is mostly below +/-1%.

Support was also added to LLVMs lld: https://reviews.llvm.org/D64903, however
there is remains off by default.

There were concerns about an increase in binary size, however in our case, the
increase (1 page worth of bytes) would seem negligible, given we are shipping a
multi-megabyte binary, which then downloads 100's of GBs of data.

Also note that most recent versions of distros are shipping a new enough version
of binutils that this is available and/or on by default (assuming the distro has
not turned it off, I haven't checked everywhere):

CentOS 8: 2.30
Debian Buster 2.31.1
Fedora 29: 2.31.1
FreeBSD: 2.33
GNU Guix: 2.33 / 2.34
Ubuntu 18.04: 2.30

Related threads / discussion:
https://bugzilla.redhat.com/show_bug.cgi?id=1623218

Github-Pull: bitcoin#19525
Rebased-From: 2e9e637
luke-jr pushed a commit to bitcoinknots/bitcoin that referenced this pull request Aug 15, 2020
Check that sections are appropriately separated in virtual memory,
based on their (expected) permissions. This checks for missing
-Wl,-z,separate-code and potentially other problems.

Co-authored-by: fanquake <fanquake@gmail.com>

Github-Pull: bitcoin#19525
Rebased-From: 65d0f1a
@bitcoin bitcoin locked as resolved and limited conversation to collaborators Feb 15, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants