Skip to content

Conversation

fanquake
Copy link
Member

@fanquake fanquake commented May 4, 2020

ae30d40
The return type of fdelt_chk changed from unsigned long int to long int in glibc 2.16. See this commit. Now that we require glibc >=2.17 we can remove our back-compat code.

ab7bce5
While looking at the above changes, I noticed that our glibc fdelt sanity check doesn't seem to be checking anything. fdelt_warn() also isn't something we'd want to actually "trigger" at runtime, as doing so would cause bitcoind to abort.

The comments:

// trigger: Call FD_SET to trigger __fdelt_chk. FORTIFY_SOURCE must be defined
// as >0 and optimizations must be set to at least -O2.

suggest calling FD_SET to check the invocation of fdelt_chk (this is aliased with fdelt_warn in glibc). However just calling FD_SET() will not necessarily cause the compiler to insert a call to fd_warn().

Whether or not GCC (recent Clang should work, but may use different heuristics) inserts a call to fdelt_warn() depends on if the compiler can determine if the value passed in is a compile time constant (using __builtin_constant_p) and whether the value is < 0 or >= FD_SETSIZE. The glibc implementation is here. This means our check should never cause a call to be inserted.

Compiling master without --glibc-back-compat (if you do pass --glibc-back-compat the outcome is still the same; however the abort will only happen with >=FD_SETSIZE as that is what our fdelt_warn() checks for), there are no calls to fdelt_warn() inserted by the compiler:

objdump -dC bitcoind | grep sanity_fdelt
...
0000000000399d20 <sanity_test_fdelt()>:
  399d20:       48 81 ec 98 00 00 00    sub    $0x98,%rsp
  399d27:       b9 10 00 00 00          mov    $0x10,%ecx
  399d2c:       64 48 8b 04 25 28 00    mov    %fs:0x28,%rax
  399d33:       00 00
  399d35:       48 89 84 24 88 00 00    mov    %rax,0x88(%rsp)
  399d3c:       00
  399d3d:       31 c0                   xor    %eax,%eax
  399d3f:       48 89 e7                mov    %rsp,%rdi
  399d42:       fc                      cld
  399d43:       f3 48 ab                rep stos %rax,%es:(%rdi)
  399d46:       48 8b 84 24 88 00 00    mov    0x88(%rsp),%rax
  399d4d:       00
  399d4e:       64 48 33 04 25 28 00    xor    %fs:0x28,%rax
  399d55:       00 00
  399d57:       75 0d                   jne    399d66 <sanity_test_fdelt()+0x46>
  399d59:       b8 01 00 00 00          mov    $0x1,%eax
  399d5e:       48 81 c4 98 00 00 00    add    $0x98,%rsp
  399d65:       c3                      retq
  399d66:       e8 85 df c8 ff          callq  27cf0 <__stack_chk_fail@plt>
  399d6b:       0f 1f 44 00 00          nopl   0x0(%rax,%rax,1)

If you modify the sanity test to pass -1 or FD_SETSIZE to FD_SET, you'll see calls to fdelt_warn inserted, and the runtime behaviour is an abort as expected.

diff --git a/src/compat/glibc_sanity_fdelt.cpp b/src/compat/glibc_sanity_fdelt.cpp
index 87140d0c7..16974bfa0 100644
--- a/src/compat/glibc_sanity_fdelt.cpp
+++ b/src/compat/glibc_sanity_fdelt.cpp
@@ -20,7 +20,7 @@ bool sanity_test_fdelt()
 {
     fd_set fds;
     FD_ZERO(&fds);
-    FD_SET(0, &fds);
+    FD_SET(FD_SETSIZE, &fds);
     return FD_ISSET(0, &fds);
 }
 #endif
0000000000399d20 <sanity_test_fdelt()>:
  399d20:	48 81 ec 98 00 00 00 	sub    $0x98,%rsp
  399d27:	b9 10 00 00 00       	mov    $0x10,%ecx
  399d2c:	64 48 8b 04 25 28 00 	mov    %fs:0x28,%rax
  399d33:	00 00 
  399d35:	48 89 84 24 88 00 00 	mov    %rax,0x88(%rsp)
  399d3c:	00 
  399d3d:	31 c0                	xor    %eax,%eax
  399d3f:	48 89 e7             	mov    %rsp,%rdi
  399d42:	fc                   	cld    
  399d43:	f3 48 ab             	rep stos %rax,%es:(%rdi)
  399d46:	48 c7 c7 ff ff ff ff 	mov    $0xffffffffffffffff,%rdi
  399d4d:	e8 3e ff ff ff       	callq  399c90 <__fdelt_warn>
  399d52:	0f b6 04 24          	movzbl (%rsp),%eax
  399d56:	83 e0 01             	and    $0x1,%eax
  399d59:	48 8b 94 24 88 00 00 	mov    0x88(%rsp),%rdx
  399d60:	00 
  399d61:	64 48 33 14 25 28 00 	xor    %fs:0x28,%rdx
  399d68:	00 00 
  399d6a:	75 08                	jne    399d74 <sanity_test_fdelt()+0x54>
  399d6c:	48 81 c4 98 00 00 00 	add    $0x98,%rsp
  399d73:	c3                   	retq   
  399d74:	e8 77 df c8 ff       	callq  27cf0 <__stack_chk_fail@plt>
  399d79:	0f 1f 80 00 00 00 00 	nopl   0x0(%rax)
src/bitcoind
*** buffer overflow detected ***: src/bitcoind terminated
Aborted

I think the test should should be removed and replaced (if possible) with additional checks in security-check.py. I was thinking about adding a version of this script as part of the output, but that needs more thought. I'll address this in a follow up.

@laanwj
Copy link
Member

laanwj commented May 4, 2020

ACK if this passes the gitian build. This was a backward compatibility function for compatibility with old libc, it (including its sanity check) might no longer be needed.

@DrahtBot
Copy link
Contributor

DrahtBot commented May 4, 2020

The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

Conflicts

No conflicts as of last run.

@DrahtBot
Copy link
Contributor

DrahtBot commented May 6, 2020

Gitian builds

File commit fbd5227
(master)
commit a8f87e2
(master and this pull)
bitcoin-core-linux-0.21-res.yml d8ca613a4fe8256a... 66b1e92f64772166...
bitcoin-core-osx-0.21-res.yml d8e9784290b7e11a... 2f2807a25eaf1e26...
bitcoin-core-win-0.21-res.yml 9fbe5a1f58d3ebbc... 6b437ebf7c2ebb93...
*-aarch64-linux-gnu-debug.tar.gz 68a9a39ec36cbed2... f0f5a39378eeb454...
*-aarch64-linux-gnu.tar.gz c4c9d98c18ac8d69... 69dd0f00253925c2...
*-arm-linux-gnueabihf-debug.tar.gz 040c0a4971178429... 86cf4f82c0906a46...
*-arm-linux-gnueabihf.tar.gz 9a9b2af69edf6221... 2832611bc2f79a95...
*-osx-unsigned.dmg 5aa18c88ffc0c32c... c0af112da3ddf808...
*-osx64.tar.gz 2c777921c43fd9b4... 1500443cda24e6cd...
*-riscv64-linux-gnu-debug.tar.gz 0ebf3ca4d2b52400... 9b9860f10c54832a...
*-riscv64-linux-gnu.tar.gz 22dfb6bf774fd577... 045401d434f89ec8...
*-win64-debug.zip 657e164c6335053a... 2e6edcd25d2690d5...
*-win64-setup-unsigned.exe 316100ef42f12b7a... 85f72f620fd94bdb...
*-win64.zip f91651bea4685766... bcefa86df0e9d7d2...
*-x86_64-linux-gnu-debug.tar.gz 617fd73aed93ec87... 65081f23d8331ef5...
*-x86_64-linux-gnu.tar.gz a995ec2a7729141c... 768513cf891cf4bb...
*.tar.gz c52ade0f24a7225e... 1a1432caac80b3d1...
linux-build.log b0ce0d32e6daf419... f057d1172be548e4...
osx-build.log af7f743adb7be8ea... cbeaeca97cef3c6f...
win-build.log b421925d0046eb64... f62ef987fabbf2f8...
bitcoin-core-linux-0.21-res.yml.diff 24ec6963e24f3e1c...
bitcoin-core-osx-0.21-res.yml.diff 15f7bed2cdc2efb4...
bitcoin-core-win-0.21-res.yml.diff 80190b52cb0d6656...
linux-build.log.diff 9e7b4dba9347053a...
osx-build.log.diff 6535c50b65f84929...
win-build.log.diff 9c6269796e068ef4...

@DrahtBot
Copy link
Contributor

DrahtBot commented May 6, 2020

🐙 This pull request conflicts with the target branch and needs rebase.

fanquake added 2 commits May 7, 2020 15:44
Now that we require glibc 2.17 or later, we no longer need to check for
different return types in fdelt_chk. It was changed from unsigned long
int to long int in glibc 2.16 . See this commit:
https://sourceware.org/git/?p=glibc.git;a=commit;h=ceb9e56b3d1f8c1922e0526c2e841373843460e2
and related issue:
https://sourceware.org/bugzilla/show_bug.cgi?id=14210.
As is, this sanity check doesn't seem to be testing fdelt_chk, because
passing a value of "0" to FD_SET wont cause the compiler to insert any
calls to fdelt_chk().

The documentation is a little misleading. If we actually triggered fdelt_chk
at runtime, bitcoind would abort. I think this check would be better replaced
(if possible) by additional checks in security-check.py.

The compiler may insert a call to fdelt_warn() (aliased with fdelt_chk
in glibc) at compile time if it can determine that an invalid value is
being passed to FD_SET.

These checks are essentially; value < 0 or value >= FD_SETSIZE along
with a check for wether the value is a compile time constant.

If the compiler can determine an invalid value is being passed, a call
to fdelt_warn will be inserted. Passing 0 should never cause a call to
be inserted.

You can check this after compiling:
```bash
objdump -dC bitcoind | grep sanity_fdelt
...
0000000000399d20 <sanity_test_fdelt()>:
  399d20:	48 81 ec 98 00 00 00 	sub    $0x98,%rsp
  399d27:	b9 10 00 00 00       	mov    $0x10,%ecx
  399d2c:	64 48 8b 04 25 28 00 	mov    %fs:0x28,%rax
  399d33:	00 00
  399d35:	48 89 84 24 88 00 00 	mov    %rax,0x88(%rsp)
  399d3c:	00
  399d3d:	31 c0                	xor    %eax,%eax
  399d3f:	48 89 e7             	mov    %rsp,%rdi
  399d42:	fc                   	cld
  399d43:	f3 48 ab             	rep stos %rax,%es:(%rdi)
  399d46:	48 8b 84 24 88 00 00 	mov    0x88(%rsp),%rax
  399d4d:	00
  399d4e:	64 48 33 04 25 28 00 	xor    %fs:0x28,%rax
  399d55:	00 00
  399d57:	75 0d                	jne    399d66 <sanity_test_fdelt()+0x46>
  399d59:	b8 01 00 00 00       	mov    $0x1,%eax
  399d5e:	48 81 c4 98 00 00 00 	add    $0x98,%rsp
  399d65:	c3                   	retq
  399d66:	e8 85 df c8 ff       	callq  27cf0 <__stack_chk_fail@plt>
  399d6b:	0f 1f 44 00 00       	nopl   0x0(%rax,%rax,1)

```

To test, you could modify this test to pass -1 to FD_SET, and check
that a call to fdelt_warn() is inserted, and that running bitcoind
fails. i.e:

```bash
0000000000399d20 <sanity_test_fdelt()>:
  399d20:	48 81 ec 98 00 00 00 	sub    $0x98,%rsp
  399d27:	b9 10 00 00 00       	mov    $0x10,%ecx
  399d2c:	64 48 8b 04 25 28 00 	mov    %fs:0x28,%rax
  399d33:	00 00
  399d35:	48 89 84 24 88 00 00 	mov    %rax,0x88(%rsp)
  399d3c:	00
  399d3d:	31 c0                	xor    %eax,%eax
  399d3f:	48 89 e7             	mov    %rsp,%rdi
  399d42:	fc                   	cld
  399d43:	f3 48 ab             	rep stos %rax,%es:(%rdi)
  399d46:	48 c7 c7 ff ff ff ff 	mov    $0xffffffffffffffff,%rdi
  399d4d:	e8 3e ff ff ff       	callq  399c90 <__fdelt_warn>
  399d52:	0f b6 04 24          	movzbl (%rsp),%eax
  399d56:	83 e0 01             	and    $0x1,%eax
  399d59:	48 8b 94 24 88 00 00 	mov    0x88(%rsp),%rdx
  399d60:	00
  399d61:	64 48 33 14 25 28 00 	xor    %fs:0x28,%rdx
  399d68:	00 00
  399d6a:	75 08                	jne    399d74 <sanity_test_fdelt()+0x54>
  399d6c:	48 81 c4 98 00 00 00 	add    $0x98,%rsp
  399d73:	c3                   	retq
  399d74:	e8 77 df c8 ff       	callq  27cf0 <__stack_chk_fail@plt>
  399d79:	0f 1f 80 00 00 00 00 	nopl   0x0(%rax)

```

```bash
./src/bitcoind
*** buffer overflow detected ***: src/bitcoind terminated
Aborted
```
@fanquake fanquake force-pushed the remove_fdelt_chk_back_compat branch from ab7bce5 to df6bde0 Compare May 7, 2020 07:47
@DrahtBot
Copy link
Contributor

DrahtBot commented May 9, 2020

Guix builds

File commit f547532
(master)
commit fc2dcba
(master and this pull)
*-aarch64-linux-gnu-debug.tar.gz d5006db2694bec1b... d05e1c4eaef07582...
*-aarch64-linux-gnu.tar.gz 0afefe2db48f05dc... dfdc8f189bc78d3a...
*-arm-linux-gnueabihf-debug.tar.gz 04a8d59114298233... 2dfca6cdefe4b983...
*-arm-linux-gnueabihf.tar.gz 211f51d96261451c... c295944554e5c51f...
*-riscv64-linux-gnu-debug.tar.gz 445f037907a24424... 4aa26c98c23fe03d...
*-riscv64-linux-gnu.tar.gz b2015050299c1a4b... 1a822e3d4db0ce92...
*-win-unsigned.tar.gz 9438cf09ce3d4397... b1051f8980380dea...
*-win64-debug.zip dce64611fca35d1b... 08b0bedf15bc0c22...
*-win64-setup-unsigned.exe 6e0d3c918efc06dd... 25d0d2961875d974...
*-win64.zip 1a26dc815a5e2126... 40cb6263b002b3ed...
*-x86_64-linux-gnu-debug.tar.gz d0b621fb9a721aba... 61a077170d2c8ea2...
*-x86_64-linux-gnu.tar.gz 2c527d43a27c956e... ede902a3966fe95e...
*.tar.gz cc9ae34886e9e8d1... 7aca3aefd2b4495b...
guix_build.log 7af6197133c336db... 46b70ee92ab25d34...
guix_build.log.diff cf71d876f8eb7dc2...

@laanwj
Copy link
Member

laanwj commented May 13, 2020

ACK df6bde0

@laanwj laanwj merged commit 5d18c0a into bitcoin:master May 13, 2020
@fanquake fanquake deleted the remove_fdelt_chk_back_compat branch May 13, 2020 23:49
sidhujag pushed a commit to syscoin/syscoin that referenced this pull request May 14, 2020
df6bde0 test: remove glibc fdelt sanity check (fanquake)
8bf1540 build: remove fdelt_chk backwards compatibility code (fanquake)

Pull request description:

  bitcoin@ae30d40
  The return type of [`fdelt_chk`](https://sourceware.org/git/?p=glibc.git;a=blob;f=debug/fdelt_chk.c;h=f62ce7349707cb68f55831c1c591fd7387a90258;hb=HEAD) changed from `unsigned  long int` to `long int` in glibc 2.16. See [this commit](https://sourceware.org/git/?p=glibc.git;a=commit;h=ceb9e56b3d1f8c1922e0526c2e841373843460e2). Now that we require [glibc >=2.17](bitcoin#17538) we can remove our back-compat code.

  bitcoin@ab7bce5
  While looking at the above changes, I noticed that our glibc fdelt sanity check doesn't seem to be checking anything. `fdelt_warn()` also isn't something we'd want to actually "trigger" at runtime, as doing so would cause `bitcoind` to abort.

  The comments:
  > // trigger: Call FD_SET to trigger __fdelt_chk. FORTIFY_SOURCE must be defined
  > //   as >0 and optimizations must be set to at least -O2.

  suggest calling FD_SET to check the invocation of `fdelt_chk` (this is [aliased with fdelt_warn in glibc](https://sourceware.org/git/?p=glibc.git;a=blob;f=debug/fdelt_chk.c;h=f62ce7349707cb68f55831c1c591fd7387a90258;hb=HEAD)). However just calling `FD_SET()` will not necessarily cause the compiler to insert a call to `fd_warn()`.

  Whether or not GCC (recent Clang should work, but may use different heuristics) inserts a call to `fdelt_warn()` depends on if the compiler can determine if the value passed in is a compile time constant (using [`__builtin_constant_p`](https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html)) and whether the value is < 0 or >= `FD_SETSIZE`. The glibc implementation is [here](https://sourceware.org/git/?p=glibc.git;a=blob;f=misc/bits/select2.h;h=7e17430ed94dd1679af10afa3d74795f9c97c0e8;hb=HEAD). This means our check should never cause a call to be inserted.

  Compiling master without `--glibc-back-compat` (if you do pass `--glibc-back-compat` the outcome is still the same; however the abort will only happen with >=`FD_SETSIZE` as that is what our [fdelt_warn()](https://github.com/bitcoin/bitcoin/blob/master/src/compat/glibc_compat.cpp#L24) checks for), there are no calls to `fdelt_warn()` inserted by the compiler:
  ```bash
  objdump -dC bitcoind | grep sanity_fdelt
  ...
  0000000000399d20 <sanity_test_fdelt()>:
    399d20:       48 81 ec 98 00 00 00    sub    $0x98,%rsp
    399d27:       b9 10 00 00 00          mov    $0x10,%ecx
    399d2c:       64 48 8b 04 25 28 00    mov    %fs:0x28,%rax
    399d33:       00 00
    399d35:       48 89 84 24 88 00 00    mov    %rax,0x88(%rsp)
    399d3c:       00
    399d3d:       31 c0                   xor    %eax,%eax
    399d3f:       48 89 e7                mov    %rsp,%rdi
    399d42:       fc                      cld
    399d43:       f3 48 ab                rep stos %rax,%es:(%rdi)
    399d46:       48 8b 84 24 88 00 00    mov    0x88(%rsp),%rax
    399d4d:       00
    399d4e:       64 48 33 04 25 28 00    xor    %fs:0x28,%rax
    399d55:       00 00
    399d57:       75 0d                   jne    399d66 <sanity_test_fdelt()+0x46>
    399d59:       b8 01 00 00 00          mov    $0x1,%eax
    399d5e:       48 81 c4 98 00 00 00    add    $0x98,%rsp
    399d65:       c3                      retq
    399d66:       e8 85 df c8 ff          callq  27cf0 <__stack_chk_fail@plt>
    399d6b:       0f 1f 44 00 00          nopl   0x0(%rax,%rax,1)

  ```

  If you modify the sanity test to pass `-1` or `FD_SETSIZE` to `FD_SET`, you'll see calls to `fdelt_warn` inserted, and the runtime behaviour is an abort as expected.

  ```diff
  diff --git a/src/compat/glibc_sanity_fdelt.cpp b/src/compat/glibc_sanity_fdelt.cpp
  index 87140d0..16974bfa0 100644
  --- a/src/compat/glibc_sanity_fdelt.cpp
  +++ b/src/compat/glibc_sanity_fdelt.cpp
  @@ -20,7 +20,7 @@ bool sanity_test_fdelt()
   {
       fd_set fds;
       FD_ZERO(&fds);
  -    FD_SET(0, &fds);
  +    FD_SET(FD_SETSIZE, &fds);
       return FD_ISSET(0, &fds);
   }
   #endif
  ```

  ```bash
  0000000000399d20 <sanity_test_fdelt()>:
    399d20:	48 81 ec 98 00 00 00 	sub    $0x98,%rsp
    399d27:	b9 10 00 00 00       	mov    $0x10,%ecx
    399d2c:	64 48 8b 04 25 28 00 	mov    %fs:0x28,%rax
    399d33:	00 00
    399d35:	48 89 84 24 88 00 00 	mov    %rax,0x88(%rsp)
    399d3c:	00
    399d3d:	31 c0                	xor    %eax,%eax
    399d3f:	48 89 e7             	mov    %rsp,%rdi
    399d42:	fc                   	cld
    399d43:	f3 48 ab             	rep stos %rax,%es:(%rdi)
    399d46:	48 c7 c7 ff ff ff ff 	mov    $0xffffffffffffffff,%rdi
    399d4d:	e8 3e ff ff ff       	callq  399c90 <__fdelt_warn>
    399d52:	0f b6 04 24          	movzbl (%rsp),%eax
    399d56:	83 e0 01             	and    $0x1,%eax
    399d59:	48 8b 94 24 88 00 00 	mov    0x88(%rsp),%rdx
    399d60:	00
    399d61:	64 48 33 14 25 28 00 	xor    %fs:0x28,%rdx
    399d68:	00 00
    399d6a:	75 08                	jne    399d74 <sanity_test_fdelt()+0x54>
    399d6c:	48 81 c4 98 00 00 00 	add    $0x98,%rsp
    399d73:	c3                   	retq
    399d74:	e8 77 df c8 ff       	callq  27cf0 <__stack_chk_fail@plt>
    399d79:	0f 1f 80 00 00 00 00 	nopl   0x0(%rax)
   ```

   ```bash
   src/bitcoind
  *** buffer overflow detected ***: src/bitcoind terminated
  Aborted
   ```

  I think the test should should be removed and replaced (if possible) with additional checks in security-check.py. I was thinking about adding a version of [this script](https://github.com/fanquake/core-review/blob/master/fortify.py) as part of the output, but that needs more thought. I'll address this in a follow up.

ACKs for top commit:
  laanwj:
    ACK  df6bde0

Tree-SHA512: d8b3af4f4eb2d6c767ca6e72ece51d0ab9042e1bbdfcbbdb7ad713414df21489ba3217662b531b8bfdac0265d2ce5431abfae6e861b6187d182ff26c6e59b32d
deadalnix pushed a commit to Bitcoin-ABC/bitcoin-abc that referenced this pull request Jun 4, 2020
Summary:
```The return type of fdelt_chk changed from unsigned long int to long
int in glibc 2.16. See this commit. Now that we require glibc >=2.17 we
can remove our back-compat code.
[...]
```

Note: we require glibc >= 2.19 so this also applies to us.

Backport of core [[bitcoin/bitcoin#15146 | PR15146]] and [[bitcoin/bitcoin#18862 | PR18862]].
[[bitcoin/bitcoin#15146 | PR15146]] fix an edge case from the sanity check which is totally removed
by the second commit of [[bitcoin/bitcoin#18862 | PR18862]]. The diff is then mostly a backport of
the first commit from [[bitcoin/bitcoin#18862 | PR18862]]:
bitcoin/bitcoin@8bf1540

Test Plan: Run the gitian builds.

Reviewers: #bitcoin_abc, deadalnix

Reviewed By: #bitcoin_abc, deadalnix

Differential Revision: https://reviews.bitcoinabc.org/D6342
@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.

3 participants