-
Notifications
You must be signed in to change notification settings - Fork 38.7k
prevector: avoid misaligned member accesses #17708
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
`#pragma pack(1)` prevents aligning the struct and its members to their required alignment. This can result in code that performs non-aligned reads and writes to integers and pointers, which is problematic on some architectures. It also triggers UBsan — see bitcoin#17156 (comment) and bitcoin#17510.
|
Specifying the alignment requirement at the prevector level instead of for |
|
Concept ACK: it would be really nice to be able to run with UndefinedBehaviorSanitizer (UBSan) without having to use any suppressions. Reviewers of this PR are encouraged to review also the related PR #17208 ("Make all tests pass UBSan without using any UBSan suppressions"). |
|
ACK on the prevector.h and ubsan change |
c28770d to
0df2f79
Compare
src/prevector.h
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: assert(A && B); is equivalent to assert(A); assert(B);, however the former has the (small) problem that if it fails it is not clear from the message which one of A or B is false.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sizeof(X) is always a multiple of alignof(X), so I think the second half here is just redundant.
|
ACK 0df2f79c0, I have tested the code |
src/coins.cpp
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think it should be a compile error if these are too small or big. This means that for any field added/removed to the structure, or possible for new compilers/architectures, these lines have to be updated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree, mostly wanted them in there to check that there weren't any unexpected changes at least on any of the architectures travis checks. (It's 44/48 for 4-byte pointers and 48/56 for 8-byte pointers). Rebased without that commit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, it's good for that.
I later realized there could be a case for checking CScript's size because it's supposed to be 32 bytes no matter the architecture (assuming pointer sizes <= 128 bit 😃 ), and it's unlikely for fields to be added to it. But I dunno.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CScript seems like it ought to be reasonably robust, and it's got that 28 constant already. Maybe if #17060 or the like makes progress could add some checks then? Figure it only makes sense to worry about the size of the things you're caching a bazillion of...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually I think to a degree CScript being 32 is non obvious... e.g. the size could be shifted greater if that were expected to be the "hot case" for fitting most CScripts. It does kind of seem like 28 might be too small, and worth re-measuring. P2SH fits in 32, but a segwit output should be 33 bytes long... unless I'm missing some case where 28 is a common size nowadays.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
p2pkh, p2sh and p2wpkh should all fit; p2wsh won't fit though (and neither will p2taproot if/when it exists)
|
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. ConflictsReviewers, this pull request conflicts with the following ones:
If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first. |
This can be done now that prevector is properly aligned.
0df2f79 to
5f26855
Compare
|
ACK 5f26855 |
|
ACK 5f26855 Very glad to see two UBSan suppressions go away! :) |
|
@jamesob Might wanna benchmark this one instead of the previous attempt if you haven't already |
|
I'm pretty sure the change, as it is now, doesn't modify the layout of any of the data structures used in UTXO caching. I think the only case where |
|
@laanwj Agree. |
|
This just feels like there must be a better solution. What we're trying to accomplish here should be completely legal, and shouldn't require pragma pack at all. Really the problem is that we want either (size, size, pointer) or (size, bytearray). If we could have a union between those two, with a guarantee the two first fields are merged, we'd be done. But because the language forces us to construct an explicit type for the ((size, pointer) or (bytearray)) structure, it on itself has a stronger alignment than if it were "inlined" so to speak. That said, I see no way to accomplish that that is guaranteed legal, so ACK. |
|
I'm pretty sure that it's valid to just union them and access the first size from either. C++11 & before
C++14 on
Therefore something like: struct size_field {const size_t s};
struct direct_t {size_t s; T t[];}
struct indirect_t {size_t s; size_t s2; T* t;}
union {
size_field s;
direct_t direct;
indirect_t indirect;
}reads to me as being standards compliant... edit: made size_field.s const to express that the value should be read-only from size_field |
Yes, my first impulse was also "move the size into the union", but the last decade of C++ (often enough, something allowed or even recommended one day becomes UB the next) made me scared of these kind of assumptions. I think we're looking at "aliasing" here as there will be two paths to access the same data. But I'm not a language lawyer, and common sense tells me to not depend on such edge cases of the standard. Especially given how critical this data structure is to bitcoin verification. So I'd prefer to merge the current solution. I think it's guaranteed not to make anything worse or introduce new UB. It uses the same pragmas as before but in a more contained way. (if someone comes up with a better solution that is easy to see is legal then we can always switch around the code again) |
|
It's worth noting that this behavior is very well defined in C99 and C++. The standard seems pretty clear to me:
The aliasing rules should only come into play when the field is written to; which we shouldn't have a problem with as we use the size field to switch on direct/indirectness. I have mixed feeling overall on if |
The I think this PR does not make anything less safe than it was before. It adds constraints to make sure the previous usage, which already happened to be safe (due to how the data structures were already happened to be laid out), is made explicit. I do not think this warrants making deeper changes to prevector. And if a compiler were to ignore the |
FWIW, I think @JeremyRubin is right in that that's both safe and a better approach; but in the short term at least it seems to be a much more intrusive change both to merge |
|
I was really hoping to see this fix making it to 0.19.1 :\ Can we move forward with this one? Doesn't feel good to ship with a known misaligned pointer use in Reachability: |
|
I agree with @ajtowns above and support moving forward with fixing this (and following up in the future with a pragma free version if needed). |
jonatack
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
5f26855 test: Remove ubsan alignment suppressions (Wladimir J. van der Laan) 9d933ef prevector: avoid misaligned member accesses (Anthony Towns) Pull request description: Ensure prevector data is appropriately aligned. Earlier discussion in #17530. **Edit laanwj**: In contrast to #17530, it does this without increase in size of any of the coin cache data structures (x86_64, clang) | Struct | (size,align) before | (size,align) after | | ------------- | ------------- | ------- | | Coin | 48, 8 | 48, 8 | | CCoinsCacheEntry | 56, 8 | 56, 8 | | CScript | 32, 1 | 32, 8 | ACKs for top commit: laanwj: ACK 5f26855 practicalswift: ACK 5f26855 jonatack: ACK 5f26855 Tree-SHA512: 98d112d6856f683d5b212410b73f3071d2994f1efb046a2418a35890aa1cf1aa7c96a960fc2e963fa15241e861093c1ea41951cf5b4b5431f88345eb1dd0a98a
5f26855 test: Remove ubsan alignment suppressions (Wladimir J. van der Laan) 9d933ef prevector: avoid misaligned member accesses (Anthony Towns) Pull request description: Ensure prevector data is appropriately aligned. Earlier discussion in bitcoin#17530. **Edit laanwj**: In contrast to bitcoin#17530, it does this without increase in size of any of the coin cache data structures (x86_64, clang) | Struct | (size,align) before | (size,align) after | | ------------- | ------------- | ------- | | Coin | 48, 8 | 48, 8 | | CCoinsCacheEntry | 56, 8 | 56, 8 | | CScript | 32, 1 | 32, 8 | ACKs for top commit: laanwj: ACK 5f26855 practicalswift: ACK 5f26855 jonatack: ACK 5f26855 Tree-SHA512: 98d112d6856f683d5b212410b73f3071d2994f1efb046a2418a35890aa1cf1aa7c96a960fc2e963fa15241e861093c1ea41951cf5b4b5431f88345eb1dd0a98a
|
It seems this had a performance related silent merge conflict. The commit itself increases performance: However, the merge commit decreases performance: |
Summary: ``` `#pragma pack(1)` prevents aligning the struct and its members to their required alignment. This can result in code that performs non-aligned reads and writes to integers and pointers, which is problematic on some architectures. It also triggers UBsan — see bitcoin/bitcoin#17156 (comment) and #17510. ``` Backport of core [[bitcoin/bitcoin#17708 | PR17708]]. Test Plan: With and without UBSAN: ninja check Reviewers: #bitcoin_abc, nakihito Reviewed By: nakihito Differential Revision: https://reviews.bitcoinabc.org/D5986
It makes sense for this to make things worse in the benchmark I think -- the nontrivial constructor prevector in the benchmark is a 116 or 120 byte object (28 4-byte nontrivials, a 4-byte size_t with the number of non-trivials, all aligned to a pointer so padded 4-bytes if pointers are 8-bytes), and the size has moved to the end of that structure rather than the beginning which could well be a different cache line than the first entry or the pointer to the allocated entries. Could probably confirm this by seeing if changing |
Summary: ``` `#pragma pack(1)` prevents aligning the struct and its members to their required alignment. This can result in code that performs non-aligned reads and writes to integers and pointers, which is problematic on some architectures. It also triggers UBsan — see bitcoin/bitcoin#17156 (comment) and #17510. ``` Backport of core [[bitcoin/bitcoin#17708 | PR17708]]. Test Plan: With and without UBSAN: ninja check Reviewers: #bitcoin_abc, nakihito Reviewed By: nakihito Differential Revision: https://reviews.bitcoinabc.org/D5986
5f26855 test: Remove ubsan alignment suppressions (Wladimir J. van der Laan) 9d933ef prevector: avoid misaligned member accesses (Anthony Towns) Pull request description: Ensure prevector data is appropriately aligned. Earlier discussion in bitcoin#17530. **Edit laanwj**: In contrast to bitcoin#17530, it does this without increase in size of any of the coin cache data structures (x86_64, clang) | Struct | (size,align) before | (size,align) after | | ------------- | ------------- | ------- | | Coin | 48, 8 | 48, 8 | | CCoinsCacheEntry | 56, 8 | 56, 8 | | CScript | 32, 1 | 32, 8 | ACKs for top commit: laanwj: ACK 5f26855 practicalswift: ACK 5f26855 jonatack: ACK 5f26855 Tree-SHA512: 98d112d6856f683d5b212410b73f3071d2994f1efb046a2418a35890aa1cf1aa7c96a960fc2e963fa15241e861093c1ea41951cf5b4b5431f88345eb1dd0a98a
5f26855 test: Remove ubsan alignment suppressions (Wladimir J. van der Laan) 9d933ef prevector: avoid misaligned member accesses (Anthony Towns) Pull request description: Ensure prevector data is appropriately aligned. Earlier discussion in bitcoin#17530. **Edit laanwj**: In contrast to bitcoin#17530, it does this without increase in size of any of the coin cache data structures (x86_64, clang) | Struct | (size,align) before | (size,align) after | | ------------- | ------------- | ------- | | Coin | 48, 8 | 48, 8 | | CCoinsCacheEntry | 56, 8 | 56, 8 | | CScript | 32, 1 | 32, 8 | ACKs for top commit: laanwj: ACK 5f26855 practicalswift: ACK 5f26855 jonatack: ACK 5f26855 Tree-SHA512: 98d112d6856f683d5b212410b73f3071d2994f1efb046a2418a35890aa1cf1aa7c96a960fc2e963fa15241e861093c1ea41951cf5b4b5431f88345eb1dd0a98a
5f26855 test: Remove ubsan alignment suppressions (Wladimir J. van der Laan) 9d933ef prevector: avoid misaligned member accesses (Anthony Towns) Pull request description: Ensure prevector data is appropriately aligned. Earlier discussion in bitcoin#17530. **Edit laanwj**: In contrast to bitcoin#17530, it does this without increase in size of any of the coin cache data structures (x86_64, clang) | Struct | (size,align) before | (size,align) after | | ------------- | ------------- | ------- | | Coin | 48, 8 | 48, 8 | | CCoinsCacheEntry | 56, 8 | 56, 8 | | CScript | 32, 1 | 32, 8 | ACKs for top commit: laanwj: ACK 5f26855 practicalswift: ACK 5f26855 jonatack: ACK 5f26855 Tree-SHA512: 98d112d6856f683d5b212410b73f3071d2994f1efb046a2418a35890aa1cf1aa7c96a960fc2e963fa15241e861093c1ea41951cf5b4b5431f88345eb1dd0a98a
Merge bitcoin#17708: prevector: avoid misaligned member accesses
Ensure prevector data is appropriately aligned. Earlier discussion in #17530.
Edit laanwj: In contrast to #17530, it does this without increase in size of any of the coin cache data structures (x86_64, clang)