-
-
Notifications
You must be signed in to change notification settings - Fork 7.8k
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
tools,unix: Enable Undefined Behavior Sanitizer (UBSan) for unix port and in CI. #15303
base: master
Are you sure you want to change the base?
Conversation
As detected by UBSan. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton <angus@redyak.com.au>
A number of places call memset, memcpy, memcmp where one or both pointer arguments is NULL but the length is also zero. This is generally safe provided the length is zero, however it is also UB and there is a risk that an ambitious compiler can decide that because an argument is passed to the one of these functions then it is not NULL, and therefore optimise out an existing NULL check. The checks are added in place, not guarded behind a macro, to avoid that (maybe remote) possibility. There is a size impact, although it seems like gcc does a pretty good job of optimising them back out in a lot of cases. Alternative to this would be to tell UBSan not to check non-null argument attributes and accept any associated risk. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton <angus@redyak.com.au>
Allows UB to be caught during CI passes. One incident of UB in third party code has to be ignored using a suppressions file. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton <angus@redyak.com.au>
@@ -91,6 +91,17 @@ ifndef DEBUG | |||
CFLAGS += -U _FORTIFY_SOURCE | |||
endif | |||
|
|||
# Optional Undefined Behavior Sanitizer |
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.
In a display of pedantry befitting a computer programmer, I've used US English spelling for the proper noun Undefined Behavior Sanitizer but used British English spelling elsewhere. If this is too silly then I can change it.
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## master #15303 +/- ##
==========================================
- Coverage 98.42% 98.42% -0.01%
==========================================
Files 161 161
Lines 21204 20955 -249
==========================================
- Hits 20870 20624 -246
+ Misses 334 331 -3 ☔ View full report in Codecov by Sentry. |
Code size report:
|
Few things still needed:
|
It seems UBSan generates enough code in vm.c that an nlr_jump back to the nlr_push target will crashe on x86. By default with UBSan, an exception jumps and lands on an SSE2 instruction:
Exception handler crashes at 28f. Compiling vm.c with -fno-sanitize works, compiling with -mno-sse doesn't work as UBSan just generates different additional code here and it also crashes. I think probably the safe way to use UBSan with NLR is going to be falling back to setjmp/longjmp, after all that's the only C standard compliant way to do it... So probably that means leaving the coverage builds as they are, and adding another CI config for UBSan. 🙃 |
I don't have any detailed comments but thank you for picking up this work and trying to move it forward! |
Hmm OK, this is more code size increase than I saw in my testing as well (GCC version dependent, maybe?) So I think something else to tackle here... |
Some notes-to-self for when I get back to this:
|
This PR enables UBSan on the coverage build by default, so it will be checked in CI.
Includes two supporting commits to fix UB that appeared in test runs. Kudos to @jepler who did a ton of previous work with UBSan in #7237, #7370, #3799, probably others - hence why this PR is pretty small.
Null argument checks
The biggest commit in this PR is to fix cases where UBSan detects a null pointer being passed to a function argument decorated as non-null. In every case this was a libc function (memset, memcpy, etc) where the length argument was zero, so a no-op in principle.
@jepler did a good job of explaining the potential risk in #3799 so I'll quote that here:
There are multiple ways we could handle this:
@jepler I'd be very interested in your thoughts on this, if you're around. I can see benefits for each approach.