-
Notifications
You must be signed in to change notification settings - Fork 2.6k
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
Unit test framework pkg enable sanitize #6408
base: master
Are you sure you want to change the base?
Unit test framework pkg enable sanitize #6408
Conversation
@heatd @mhaeuser @shijunjing please provide feedback on unconditionally adding address sanitizer to all host based unit tests. There are email discussion about ASAN, UBSAN, and fuzzing and this would be first step along that path. |
|
||
Pointer = (UINT8 *)AllocatePool (100); | ||
UT_ASSERT_NOT_NULL (Pointer); | ||
Pointer[110] = 0; |
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.
This is the same test as for Overflow.
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.
good catch. I will fix on next update
IN UNIT_TEST_CONTEXT Context | ||
) | ||
{ | ||
*(UINT8 *)(NULL) = 0; |
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.
Due to various quirks around the 0-address, I think "null dereference" and "invalid dereference" should be two entirely distinct concepts.
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 agree, The will change this one to match google test death test on access to address (-1) and will add a new unit tests that does NULL pointer dereference.
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 agree, The will change this one to match google test death test on access to address (-1) and will add a new unit tests that does NULL pointer dereference.
you can't actually reliably do NULL pointer dereference in C without workarounds. Here's clang generating a completely empty function (not even a RET!) because of UB: https://godbolt.org/z/dGzj893e6
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.
Yes. Adding these types of unit tests to verify the ability to catch developer errors are challenging. I would like to see CLANG used for host based unit testing in the future, but right now, the only tool change enabled are VS20xx and GCC.
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'm not sure this is challenging, you "simply" need to employ volatile
for such cases. Somehow I missed this, thanks @heatd!
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.
Since we compile unit tests with all optimizations disabled, I am not sure of even volatile
is required. But can always add volatile
as needed for compiler compatibity.
Here is what is see from VS2019 for the statement *(UINT8 *)(NULL) = 0;
Sample Unit Test Sanitize NULL Pointer v0.1
---------------------------------------------------------
------------ RUNNING ALL TEST SUITES --------------
---------------------------------------------------------
---------------------------------------------------------
RUNNING TEST SUITE: Sanitize tests
---------------------------------------------------------
[==========] Running 1 test(s).
[ RUN ] Sanitize NULL Address
=================================================================
==15628==ERROR: AddressSanitizer: access-violation on unknown address 0x000000000000 (pc 0x7ff79c2215d1 bp 0x000000000000 sp 0x0027c54feb60 T0)
==15628==The signal is caused by a WRITE memory access.
==15628==Hint: address points to the zero page.
#0 0x7ff79c2215d0 in SanitizeNullAddress c:\work\github\tianocore\edk2\UnitTestFrameworkPkg\Test\UnitTest\Sample\SampleUnitTestNullAddress\SampleUnitTestNullAddress.c:40
#1 0x7ff79c25641d in CmockaUnitTestFunctionRunner c:\work\github\tianocore\edk2\UnitTestFrameworkPkg\Library\UnitTestLib\RunTestsCmocka.c:56
#2 0x7ff79c251aa1 in cmocka_run_one_test_or_fixture c:\work\github\tianocore\edk2\UnitTestFrameworkPkg\Library\CmockaLib\cmocka\src\cmocka.c:2890
#3 0x7ff79c251f8d in cmocka_run_one_tests c:\work\github\tianocore\edk2\UnitTestFrameworkPkg\Library\CmockaLib\cmocka\src\cmocka.c:2998
#4 0x7ff79c24a49b in _cmocka_run_group_tests c:\work\github\tianocore\edk2\UnitTestFrameworkPkg\Library\CmockaLib\cmocka\src\cmocka.c:3129
#5 0x7ff79c257594 in RunTestSuite c:\work\github\tianocore\edk2\UnitTestFrameworkPkg\Library\UnitTestLib\RunTestsCmocka.c:220
#6 0x7ff79c256f10 in RunAllTestSuites c:\work\github\tianocore\edk2\UnitTestFrameworkPkg\Library\UnitTestLib\RunTestsCmocka.c:276
#7 0x7ff79c221899 in UefiTestMain c:\work\github\tianocore\edk2\UnitTestFrameworkPkg\Test\UnitTest\Sample\SampleUnitTestNullAddress\SampleUnitTestNullAddress.c:90
#8 0x7ff79c221981 in main c:\work\github\tianocore\edk2\UnitTestFrameworkPkg\Test\UnitTest\Sample\SampleUnitTestNullAddress\SampleUnitTestNullAddress.c:109
#9 0x7ff79c2abce8 in invoke_main D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:78
#10 0x7ff79c2abc3d in __scrt_common_main_seh D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:288
#11 0x7ff79c2abafd in __scrt_common_main D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:330
#12 0x7ff79c2abd5d in mainCRTStartup D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_main.cpp:16
#13 0x7ffe7cd4257c (C:\windows\System32\KERNEL32.DLL+0x18001257c)
#14 0x7ffe7de8af07 (C:\windows\SYSTEM32\ntdll.dll+0x18005af07)
AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: access-violation c:\work\github\tianocore\edk2\UnitTestFrameworkPkg\Test\UnitTest\Sample\SampleUnitTestNullAddress\SampleUnitTestNullAddress.c:40 in SanitizeNullAddress
==15628==ABORTING
Status = RunAllTestSuites (Framework); | ||
|
||
EXIT: | ||
if (Framework) { |
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.
Don't the same rules apply as for EDK II code, where "implicit" comparisons are forbidden?
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.
Yes. Pointer should check against NULL. Only BOOLEAN variable can be used this way. Unfortunately this pattern is widely used in unit tests. I will fix in a different PR
// TODO: Finish this function. | ||
if (SuiteEntry) { | ||
TestCase = (UNIT_TEST_LIST_ENTRY *)GetFirstNode (&(SuiteEntry->UTS.TestCaseList)); | ||
while ((LIST_ENTRY *)TestCase != &(SuiteEntry->UTS.TestCaseList)) { |
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.
The double-casts and the manual comparison look very weird, should this not use (BASE_)CR and functions like IsNull()?
You shouldn't enable ASAN unconditionally, because various sanitizers are incompatible with each other. For instance, clang supports MSAN which can catch uninitialized memory reads (also TSAN, but concurrency is nonexistent in UEFI). I didn't look too much at the code but otherwise SGTM. I also think that doing ASAN in an actual firmware environment could be very beneficial for catching bugs, which otherwise you're not catching (particularly considering that very little EFI code has unit tests). |
1e87ddd
to
8e59990
Compare
@heatd I am aware that some sanitizers can not be combined. I think I will be valuable to enable more sanitizers over time, but that will have to be balanced against the CI time if unit tests have to be build and run with multiple configurations. More investigation is needed. I am suggesting in this draft PR that address sanitization always be enabled when running edk2 host-based unit tests and we can handle sanitizer compatibilities as they are enabled later. I agree that this feature only provide real value if the unit tests start to cover large amounts of FW sources. We should focus efforts on this gap. Enabling sanitizers in target FW environments is very challenging, would require significant development, support, and maintenance. It would also always be behind the latest features on sanitizers targeted for OS applications. |
I have pushed an update that should address some of the CI failures and some of the feedback from @mhaeuser |
OK.
Right. Feasibility of that testing goal aside, I seriously suspect unit tests can't carry nearly as far as a real workload.
FWIW, I suspect it wouldn't be that hard to do this. Basically you'd need to reserve 1/8th of memory in PEI to be set up as shadow memory in DxeCore (I think?). Steven Shi IIRC had a nice sample implementation of this that I remember fairly impressed me at the time. You'd also probably want to add a heap quarantine a-la ASAN malloc, but that's also not super hard. Also, these interfaces are quite stable. ASAN doesn't really have new features. In any case, these are just things folks should consider; I'm certainly not enlisting to do any of this work anymore :). Having any kind of ASAN is already a good improvement. |
Actually unit tests can cover corner cases that are very hard or impossible to reproduce on real targets. Mostly around error paths for out of memory and error paths for hardware errors/failures. Host based unit testing can use mocks to and code coverage to help ensure all code paths are tested. |
I have to say, despite being a fan of automated tests, I have never been a fan of classical unit tests, let alone test-driven development (though I acknowledge its advantages). This is less about "this is useless", it is not, but more about "this is very time-consuming and prone to errors that effectively risk making it useless". People tend to think of the typical corner-cases that are already prominent during development, but miss out on the finer details (this obviously includes myself). My personal approach has been to utilize fuzz-testing using fault injection (e.g. our AUDK userland fuzzing allocator is shimmed to randomly inject allocation faults to test exactly these OOM paths) as much as possible, and when there is a bug, I save the bugged input for regression-testing. To make this somewhat scale, I wrote multiple scripts to better handle corpus-based testing (heavily PoC): https://github.com/mhaeuser/MastersThesis/tree/ue_test/Scripts/Corpus Examples of this include:
If you give it enough valid inputs and enough ways to break the environment, the fuzzer will do its work, given enough time and RAM. I combined this with manually checking the coverage reports and fabricating faulty inputs for unreached error paths alike |
I agree with the potential value of fuzz testing and how it can compliment other testing methods. There are just better development and debug tools for host based unit tests that are OS applications than target FW envs, so I am interested in seeing how we can apply fuzz testing in host envs. |
0519d7b
to
1202caa
Compare
@VivianNK please review |
All our fuzz-testing is in host envs. I don’t think fuzz-testing in FW envs makes any sense (this is separate from sanitizers, which absolutely do make sense there!). |
IN UNIT_TEST_LIST_ENTRY *TestEntry | ||
) | ||
{ | ||
// TODO: Finish this function. |
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.
What's the goal in "finishing" the function?
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.
Old comment that needs to be removed now that the function is implemented
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.
Oh, that function is finished? Then the superfluous return type should be dropped, the result is discarded anyway.
ASSERT_NE (Pointer, (UINT8 *)NULL); | ||
FreePool (Pointer); | ||
// | ||
// Second free that should be caught by address sanitizer, log details, and exit |
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.
It exits the entire GoogleTest or is it able to continue execution outside of this TEST() function? What does "exit" mean in this case?
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.
These test cases verify that the address sanitizer does catch the issue and is verified with the use of EXPECT_DEATH() that allows the rest of the tests to be run.
If a developer actually has a double free in their unit test code or code under test, then the address sanitizer would catch the condition, log a stack back trace and other details, and terminate/exit the unit test application. This is the expected developer experience when a condition is caught by the address sanitizer. It would also generate an error for CI.
int | ||
main ( | ||
int argc, | ||
char *argv[] | ||
) | ||
{ | ||
testing::InitGoogleTest (&argc, argv); | ||
testing::FLAGS_gtest_catch_exceptions = 0; |
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.
Moving forward we should be adding this flag to any GoogleTest that uses EXPECT_DEATH?
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.
This is a setting for the address sanitizer, not EXPECT_DEATH(). When GoogleTest is combined with the address sanitizer, all exceptions need to be handled by the address sanitizer so it can show details of the failure. This can be done globally with setting the env var GTEST_CATCH_EXCEPTIONS=0. I have done this env var setting in this PR for Azure Pipelines CI agents. And it is recommended that developers set this as well to always see full details.
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 have updated the Integration Instruction in the description of this PR with those details. They also need to be added to the UnitTestFrameworkPkg ReadMe.
bf4d2d4
to
056be99
Compare
056be99
to
d217f6b
Compare
⚠ WARNING: Cannot add some reviewers: A user specified as a reviewer for this PR is not a collaborator of the repository. Please add them as a collaborator to the repository so they can be requested in the future. Non-collaborators requested: Attn Admins: Admin Instructions:
|
@os-d I updated this PR to latest which included the use of VS2022. It is getting linker errors for ASAN symbols missing. I can build locally with VS2022 tools installed without these errors. Is there a version of the CI agents for VS2022 that include ASAN libs pre-installed? How do we update VS2022 to install ASAN libs if that is not available? This page claims that Microsoft.VisualStudio.Component.VC.ASAN is installed. https://github.com/actions/runner-images/blob/main/images/windows/Windows2022-Readme.md |
@mdkinney , I'm not familiar with the runner image composition. It does look like the version of the ASAN libs was updated by a major version between 2019 and 2022, did you confirm your local ASAN libs is the same major version? |
d217f6b
to
fe2aa39
Compare
This conversation implies that LTCG and ASAN are not compatible with each other. |
@os-d Adding /LTCG:OFF for unit test builds did not help. Same missing symbols. |
@mdkinney , I can see the private chat there and I’m not sure how relevant it is here, it looks like they had a mismatch of some component using ASAN and some not. Here is the OSS PR where they fixed it: microsoft/ebpf-for-windows#3027, which, they did come to the conclusion that LTCG should be disabled and Whole Program Optimization should be disabled. WPO (/GL) is set, out of curiousity, if you also disable that, does it build with ASAN? I.e. do /GL- in your CC flags. That being said, this looks suspiciously like what changed, perhaps more info here: https://learn.microsoft.com/en-us/cpp/sanitizers/asan-building?view=msvc-170 which links to: https://devblogs.microsoft.com/cppblog/msvc-address-sanitizer-one-dll-for-all-runtime-configurations/ |
@os-d Thanks. I think the key is this note:
I could not get /INFERASANLIBS to work, so the specific libs are specified, and looks like there are different filename. Not sure why my local version of VS2022 works and the one in the CI agent does not. I only installed the build essentials and not enterprise. I will install enterprise edition locally and see if I can reproduce and generate the right solution. |
@os-d I have installed VS2022 Enterprise locally and still can not reproduce this issue. |
Add use of DEBUG_CLEAR_MEMORY() macros on all allocation and free operations in MemoryAllocationLibPosix to match behavior of all other MemoryAllocationLib instances. Signed-off-by: Michael D Kinney <[email protected]>
Implement FreeUnitTestEntry(), FreeUnitTestSuiteEntry(), and FreeUnitTestFramework() so the UnitTestLib does not introduce any memory leaks. Signed-off-by: Michael D Kinney <[email protected]>
Update DSC files to always enable DEBUG_CLEAR_MEMORY() macros so memory is cleared on every memory allocation/free operation. Signed-off-by: Michael D Kinney <[email protected]>
988bae0
to
f69d5b0
Compare
Add Empty_C_File_Host_Application_Build.c to BaseTools/Conf that is a C source file with only comments that is used to compile into an OBJ file using CC_FLAGS for a HOST_APPLICATION module and the OBJ is passed into the VS20xx DLINK action to provide the context required to select the correct default windows application libraries. Update build_rule.template to compile the empty C file and generate OBJ in the OUTPUT_DIR of the HOST_APPLICATION component and use the OBJ in the DLINK action. This simplifies CC_FLAGS and DLINK_FLAGS for all modules of type HOST_APPLICATION. Signed-off-by: Michael D Kinney <[email protected]>
* Update host based unit test VS20xx builds to use /MTd instead of /MT to enable use of debug libraries for host based unit tests. * Enable /fsanitize=address for host based unit test VS2019 builds * Enable /fsanitize=address for host based unit test VS2022 builds * Enable -fsanitize=address for host based unit test GCC builds * Add UNIT_TESTING_ADDRESS_SANITIZER_ENABLE define that is set to TRUE by default so it is always enabled, but can be set to FALSE to temporarily disable during development/debug of unit tests. * Add address sanitizer information to ReadMe.md Enabling the Address Sanitizer can detect double frees, buffer overflow, buffer underflow, access to invalid addresses, and various exceptions. These can be detected in both the unit test case sources as well as the code under test. Signed-off-by: Michael D Kinney <[email protected]>
Add GoogleTest and Framework based unit tests that are expected to fail and be caught by Address Sanitizer. These unit tests verify that an address sanitizer is enabled and detecting the following conditions. It also provide examples of the expected output when an Address Sanitizer detected these types of issues. * double free * buffer overflow * buffer underflow * null ptr * invalid address * divide by zero Signed-off-by: Michael D Kinney <[email protected]>
Use snprintf() in host based unit tests to format log messages and add host specific wrapper for LongJump() that is decorated with NORETURN to provide hint to address sanitizer that LongJump() never returns. Signed-off-by: Michael D Kinney <[email protected]>
f69d5b0
to
c6930e8
Compare
@os-d I was able to reproduce the VS2022 issue locally and have a new approach that cleans up HOST_APPLICATION component builds for VS2019 and VS2022 by maximizing the compiler selection of default libraries. |
…ress Add HostMemoryAllocationBelowAddressLib class and implementation that uses OS specific services to perform pool and page allocations below a specified address in a host based unit test application execution environment. This library class is only required for mocking buffers that are assumed to be below a specific address by code under test. Signed-off-by: Michael D Kinney <[email protected]>
The environment variable `GTEST_CATCH_EXCEPTION` must be set to `0` for so all exceptions are handled by the address sanitizer and not GoogleTest. This allows stack back trace and other details to be logged by the address sanitizer so the source of the issue identified address sanitizer can be determined. The environment variable `ASAN_OPTIONS` must be set to `detect_leaks=0` to disable memory leak detection. The unit test frameworks may have memory leaks and some firmware code under test use cases may perform a memory allocation without a matching memory free as their expected behavior. Signed-off-by: Michael D Kinney <[email protected]>
c6930e8
to
6fc0a9e
Compare
Description
UnitTestFrameworkPkg: Use /MTd and enable Address Sanitizers
/MT to enable use of debug libraries for host based unit tests.
TRUE by default so it is always enabled, but can be set to FALSE
to temporarily disable during development/debug of unit tests.
buffers below a specified address in unit test code and mocks to
support code under test that requires a buffer below a specific
address (e.g. 4GB). The services included are:
Enabling the Address Sanitizer can detect double frees, buffer
overflow, buffer underflow, access to invalid addresses, and
various exceptions. These can be detected in both the unit test
case sources as well as the code under test.
How This Was Tested
Integration Instructions
The environment variable
GTEST_CATCH_EXCEPTION
must be set to0
for so all exceptions are handled by the address sanitizer and not GoogleTest. This allows stack back trace and other details to be logged by the address sanitizer so the source of the issue identified address sanitizer can be determined.The environment variable
ASAN_OPTIONS
must be set todetect_leaks=0
to disable memory leak detection. The unit test frameworks may have memory leaks and some firmware code under test use cases may perform a memory allocation without a matching memory free as their expected behavior.Windows
Linux