--- /dev/null
+/** @file\r
+ This is a host-based unit test for the VariableLockRequestToLock shim.\r
+\r
+ Copyright (c) Microsoft Corporation.\r
+ SPDX-License-Identifier: BSD-2-Clause-Patent\r
+\r
+**/\r
+\r
+#include <stdio.h>\r
+#include <string.h>\r
+#include <stdarg.h>\r
+#include <stddef.h>\r
+#include <setjmp.h>\r
+#include <cmocka.h>\r
+\r
+#include <Uefi.h>\r
+#include <Library/DebugLib.h>\r
+#include <Library/BaseMemoryLib.h>\r
+#include <Library/MemoryAllocationLib.h>\r
+#include <Library/UnitTestLib.h>\r
+#include <Library/VariablePolicyLib.h>\r
+#include <Library/VariablePolicyHelperLib.h>\r
+\r
+#include <Protocol/VariableLock.h>\r
+\r
+#define UNIT_TEST_NAME "VarPol/VarLock Shim Unit Test"\r
+#define UNIT_TEST_VERSION "1.0"\r
+\r
+///=== CODE UNDER TEST ===========================================================================\r
+\r
+EFI_STATUS\r
+EFIAPI\r
+VariableLockRequestToLock (\r
+ IN CONST EDKII_VARIABLE_LOCK_PROTOCOL *This,\r
+ IN CHAR16 *VariableName,\r
+ IN EFI_GUID *VendorGuid\r
+ );\r
+\r
+///=== TEST DATA ==================================================================================\r
+\r
+//\r
+// Test GUID 1 {F955BA2D-4A2C-480C-BFD1-3CC522610592}\r
+//\r
+EFI_GUID mTestGuid1 = {\r
+ 0xf955ba2d, 0x4a2c, 0x480c, {0xbf, 0xd1, 0x3c, 0xc5, 0x22, 0x61, 0x5, 0x92}\r
+};\r
+\r
+//\r
+// Test GUID 2 {2DEA799E-5E73-43B9-870E-C945CE82AF3A}\r
+//\r
+EFI_GUID mTestGuid2 = {\r
+ 0x2dea799e, 0x5e73, 0x43b9, {0x87, 0xe, 0xc9, 0x45, 0xce, 0x82, 0xaf, 0x3a}\r
+};\r
+\r
+//\r
+// Test GUID 3 {698A2BFD-A616-482D-B88C-7100BD6682A9}\r
+//\r
+EFI_GUID mTestGuid3 = {\r
+ 0x698a2bfd, 0xa616, 0x482d, {0xb8, 0x8c, 0x71, 0x0, 0xbd, 0x66, 0x82, 0xa9}\r
+};\r
+\r
+#define TEST_VAR_1_NAME L"TestVar1"\r
+#define TEST_VAR_2_NAME L"TestVar2"\r
+#define TEST_VAR_3_NAME L"TestVar3"\r
+\r
+#define TEST_POLICY_ATTRIBUTES_NULL 0\r
+#define TEST_POLICY_MIN_SIZE_NULL 0\r
+#define TEST_POLICY_MAX_SIZE_NULL MAX_UINT32\r
+\r
+#define TEST_POLICY_MIN_SIZE_10 10\r
+#define TEST_POLICY_MAX_SIZE_200 200\r
+\r
+///=== HELPER FUNCTIONS ===========================================================================\r
+\r
+/**\r
+ Mocked version of GetVariable, for testing.\r
+\r
+ @param VariableName\r
+ @param VendorGuid\r
+ @param Attributes\r
+ @param DataSize\r
+ @param Data\r
+**/\r
+EFI_STATUS\r
+EFIAPI\r
+StubGetVariableNull (\r
+ IN CHAR16 *VariableName,\r
+ IN EFI_GUID *VendorGuid,\r
+ OUT UINT32 *Attributes, OPTIONAL\r
+ IN OUT UINTN *DataSize,\r
+ OUT VOID *Data OPTIONAL\r
+ )\r
+{\r
+ UINT32 MockedAttr;\r
+ UINTN MockedDataSize;\r
+ VOID *MockedData;\r
+ EFI_STATUS MockedReturn;\r
+\r
+ check_expected_ptr (VariableName);\r
+ check_expected_ptr (VendorGuid);\r
+ check_expected_ptr (DataSize);\r
+\r
+ MockedAttr = (UINT32)mock();\r
+ MockedDataSize = (UINTN)mock();\r
+ MockedData = (VOID*)(UINTN)mock();\r
+ MockedReturn = (EFI_STATUS)mock();\r
+\r
+ if (Attributes != NULL) {\r
+ *Attributes = MockedAttr;\r
+ }\r
+ if (Data != NULL && !EFI_ERROR (MockedReturn)) {\r
+ CopyMem (Data, MockedData, MockedDataSize);\r
+ }\r
+\r
+ *DataSize = MockedDataSize;\r
+\r
+ return MockedReturn;\r
+}\r
+\r
+//\r
+// Anything you think might be helpful that isn't a test itself.\r
+//\r
+\r
+/**\r
+ This is a common setup function that will ensure the library is always\r
+ initialized with the stubbed GetVariable.\r
+\r
+ Not used by all test cases, but by most.\r
+\r
+ @param[in] Context Unit test case context\r
+**/\r
+STATIC\r
+UNIT_TEST_STATUS\r
+EFIAPI\r
+LibInitMocked (\r
+ IN UNIT_TEST_CONTEXT Context\r
+ )\r
+{\r
+ return EFI_ERROR (InitVariablePolicyLib (StubGetVariableNull)) ? UNIT_TEST_ERROR_PREREQUISITE_NOT_MET : UNIT_TEST_PASSED;\r
+}\r
+\r
+/**\r
+ Common cleanup function to make sure that the library is always de-initialized\r
+ prior to the next test case.\r
+\r
+ @param[in] Context Unit test case context\r
+**/\r
+STATIC\r
+VOID\r
+EFIAPI\r
+LibCleanup (\r
+ IN UNIT_TEST_CONTEXT Context\r
+ )\r
+{\r
+ DeinitVariablePolicyLib();\r
+}\r
+\r
+///=== TEST CASES =================================================================================\r
+\r
+///===== SHIM SUITE ===========================================================\r
+\r
+/**\r
+ Test Case that locks a single variable using the Variable Lock Protocol.\r
+ The call is expected to succeed.\r
+\r
+ @param[in] Context Unit test case context\r
+**/\r
+UNIT_TEST_STATUS\r
+EFIAPI\r
+LockingWithoutAnyPoliciesShouldSucceed (\r
+ IN UNIT_TEST_CONTEXT Context\r
+ )\r
+{\r
+ EFI_STATUS Status;\r
+\r
+ Status = VariableLockRequestToLock (NULL, TEST_VAR_1_NAME, &mTestGuid1);\r
+ UT_ASSERT_NOT_EFI_ERROR (Status);\r
+\r
+ return UNIT_TEST_PASSED;\r
+}\r
+\r
+/**\r
+ Test Case that locks the same variable twice using the Variable Lock Protocol.\r
+ Both calls are expected to succeed.\r
+\r
+ @param[in] Context Unit test case context\r
+ **/\r
+UNIT_TEST_STATUS\r
+EFIAPI\r
+LockingTwiceShouldSucceed (\r
+ IN UNIT_TEST_CONTEXT Context\r
+ )\r
+{\r
+ EFI_STATUS Status;\r
+\r
+ Status = VariableLockRequestToLock (NULL, TEST_VAR_1_NAME, &mTestGuid1);\r
+ UT_ASSERT_NOT_EFI_ERROR (Status);\r
+\r
+ Status = VariableLockRequestToLock (NULL, TEST_VAR_1_NAME, &mTestGuid1);\r
+ UT_ASSERT_NOT_EFI_ERROR (Status);\r
+\r
+ return UNIT_TEST_PASSED;\r
+}\r
+\r
+/**\r
+ Test Case that locks a variable using the Variable Policy Protocol then locks\r
+ the same variable using the Variable Lock Protocol.\r
+ Both calls are expected to succeed.\r
+\r
+ @param[in] Context Unit test case context\r
+ **/\r
+UNIT_TEST_STATUS\r
+EFIAPI\r
+LockingALockedVariableShouldSucceed (\r
+ IN UNIT_TEST_CONTEXT Context\r
+ )\r
+{\r
+ EFI_STATUS Status;\r
+ VARIABLE_POLICY_ENTRY *NewEntry;\r
+\r
+ //\r
+ // Create a variable policy that locks the variable.\r
+ //\r
+ Status = CreateBasicVariablePolicy (\r
+ &mTestGuid1,\r
+ TEST_VAR_1_NAME,\r
+ TEST_POLICY_MIN_SIZE_NULL,\r
+ TEST_POLICY_MAX_SIZE_200,\r
+ TEST_POLICY_ATTRIBUTES_NULL,\r
+ TEST_POLICY_ATTRIBUTES_NULL,\r
+ VARIABLE_POLICY_TYPE_LOCK_NOW,\r
+ &NewEntry\r
+ );\r
+ UT_ASSERT_NOT_EFI_ERROR (Status);\r
+\r
+ //\r
+ // Register the new policy.\r
+ //\r
+ Status = RegisterVariablePolicy (NewEntry);\r
+\r
+ Status = VariableLockRequestToLock (NULL, TEST_VAR_1_NAME, &mTestGuid1);\r
+ UT_ASSERT_NOT_EFI_ERROR (Status);\r
+\r
+ FreePool (NewEntry);\r
+\r
+ return UNIT_TEST_PASSED;\r
+}\r
+\r
+/**\r
+ Test Case that locks a variable using the Variable Policy Protocol with a\r
+ policy other than LOCK_NOW then attempts to lock the same variable using the\r
+ Variable Lock Protocol. The call to Variable Policy is expected to succeed\r
+ and the call to Variable Lock is expected to fail.\r
+\r
+ @param[in] Context Unit test case context\r
+ **/\r
+UNIT_TEST_STATUS\r
+EFIAPI\r
+LockingAnUnlockedVariableShouldFail (\r
+ IN UNIT_TEST_CONTEXT Context\r
+ )\r
+{\r
+ EFI_STATUS Status;\r
+ VARIABLE_POLICY_ENTRY *NewEntry;\r
+\r
+ // Create a variable policy that locks the variable.\r
+ Status = CreateVarStateVariablePolicy (&mTestGuid1,\r
+ TEST_VAR_1_NAME,\r
+ TEST_POLICY_MIN_SIZE_NULL,\r
+ TEST_POLICY_MAX_SIZE_200,\r
+ TEST_POLICY_ATTRIBUTES_NULL,\r
+ TEST_POLICY_ATTRIBUTES_NULL,\r
+ &mTestGuid2,\r
+ 1,\r
+ TEST_VAR_2_NAME,\r
+ &NewEntry);\r
+ UT_ASSERT_NOT_EFI_ERROR (Status);\r
+\r
+ // Register the new policy.\r
+ Status = RegisterVariablePolicy (NewEntry);\r
+\r
+ // Configure the stub to not care about parameters. We're testing errors.\r
+ expect_any_always( StubGetVariableNull, VariableName );\r
+ expect_any_always( StubGetVariableNull, VendorGuid );\r
+ expect_any_always( StubGetVariableNull, DataSize );\r
+\r
+ // With a policy, make sure that writes still work, since the variable doesn't exist.\r
+ will_return( StubGetVariableNull, TEST_POLICY_ATTRIBUTES_NULL ); // Attributes\r
+ will_return( StubGetVariableNull, 0 ); // Size\r
+ will_return( StubGetVariableNull, NULL ); // DataPtr\r
+ will_return( StubGetVariableNull, EFI_NOT_FOUND); // Status\r
+\r
+ Status = VariableLockRequestToLock (NULL, TEST_VAR_1_NAME, &mTestGuid1);\r
+ UT_ASSERT_TRUE (EFI_ERROR (Status));\r
+\r
+ FreePool (NewEntry);\r
+\r
+ return UNIT_TEST_PASSED;\r
+}\r
+\r
+/**\r
+ Test Case that locks a variable using the Variable Policy Protocol with a\r
+ policy other than LOCK_NOW, but is currently locked. Then attempts to lock\r
+ the same variable using the Variable Lock Protocol. The call to Variable\r
+ Policy is expected to succeed and the call to Variable Lock also expected to\r
+ succeed.\r
+\r
+ @param[in] Context Unit test case context\r
+ **/\r
+UNIT_TEST_STATUS\r
+EFIAPI\r
+LockingALockedVariableWithMatchingDataShouldSucceed (\r
+ IN UNIT_TEST_CONTEXT Context\r
+ )\r
+{\r
+ EFI_STATUS Status;\r
+ VARIABLE_POLICY_ENTRY *NewEntry;\r
+ UINT8 Data;\r
+\r
+ // Create a variable policy that locks the variable.\r
+ Status = CreateVarStateVariablePolicy (&mTestGuid1,\r
+ TEST_VAR_1_NAME,\r
+ TEST_POLICY_MIN_SIZE_NULL,\r
+ TEST_POLICY_MAX_SIZE_200,\r
+ TEST_POLICY_ATTRIBUTES_NULL,\r
+ TEST_POLICY_ATTRIBUTES_NULL,\r
+ &mTestGuid2,\r
+ 1,\r
+ TEST_VAR_2_NAME,\r
+ &NewEntry);\r
+ UT_ASSERT_NOT_EFI_ERROR (Status);\r
+\r
+ // Register the new policy.\r
+ Status = RegisterVariablePolicy (NewEntry);\r
+\r
+ // Configure the stub to not care about parameters. We're testing errors.\r
+ expect_any_always( StubGetVariableNull, VariableName );\r
+ expect_any_always( StubGetVariableNull, VendorGuid );\r
+ expect_any_always( StubGetVariableNull, DataSize );\r
+\r
+ // With a policy, make sure that writes still work, since the variable doesn't exist.\r
+ Data = 1;\r
+ will_return( StubGetVariableNull, TEST_POLICY_ATTRIBUTES_NULL ); // Attributes\r
+ will_return( StubGetVariableNull, sizeof (Data) ); // Size\r
+ will_return( StubGetVariableNull, &Data ); // DataPtr\r
+ will_return( StubGetVariableNull, EFI_SUCCESS); // Status\r
+\r
+ Status = VariableLockRequestToLock (NULL, TEST_VAR_1_NAME, &mTestGuid1);\r
+ UT_ASSERT_TRUE (!EFI_ERROR (Status));\r
+\r
+ FreePool (NewEntry);\r
+\r
+ return UNIT_TEST_PASSED;\r
+}\r
+\r
+/**\r
+ Test Case that locks a variable using the Variable Policy Protocol with a\r
+ policy other than LOCK_NOW, but variable data does not match. Then attempts\r
+ to lock the same variable using the Variable Lock Protocol. The call to\r
+ Variable Policy is expected to succeed and the call to Variable Lock is\r
+ expected to fail.\r
+\r
+ @param[in] Context Unit test case context\r
+ **/\r
+UNIT_TEST_STATUS\r
+EFIAPI\r
+LockingALockedVariableWithNonMatchingDataShouldFail (\r
+ IN UNIT_TEST_CONTEXT Context\r
+ )\r
+{\r
+ EFI_STATUS Status;\r
+ VARIABLE_POLICY_ENTRY *NewEntry;\r
+ UINT8 Data;\r
+\r
+ // Create a variable policy that locks the variable.\r
+ Status = CreateVarStateVariablePolicy (&mTestGuid1,\r
+ TEST_VAR_1_NAME,\r
+ TEST_POLICY_MIN_SIZE_NULL,\r
+ TEST_POLICY_MAX_SIZE_200,\r
+ TEST_POLICY_ATTRIBUTES_NULL,\r
+ TEST_POLICY_ATTRIBUTES_NULL,\r
+ &mTestGuid2,\r
+ 1,\r
+ TEST_VAR_2_NAME,\r
+ &NewEntry);\r
+ UT_ASSERT_NOT_EFI_ERROR (Status);\r
+\r
+ // Register the new policy.\r
+ Status = RegisterVariablePolicy (NewEntry);\r
+\r
+ // Configure the stub to not care about parameters. We're testing errors.\r
+ expect_any_always( StubGetVariableNull, VariableName );\r
+ expect_any_always( StubGetVariableNull, VendorGuid );\r
+ expect_any_always( StubGetVariableNull, DataSize );\r
+\r
+ // With a policy, make sure that writes still work, since the variable doesn't exist.\r
+ Data = 2;\r
+ will_return( StubGetVariableNull, TEST_POLICY_ATTRIBUTES_NULL ); // Attributes\r
+ will_return( StubGetVariableNull, sizeof (Data) ); // Size\r
+ will_return( StubGetVariableNull, &Data ); // DataPtr\r
+ will_return( StubGetVariableNull, EFI_SUCCESS); // Status\r
+\r
+ Status = VariableLockRequestToLock (NULL, TEST_VAR_1_NAME, &mTestGuid1);\r
+ UT_ASSERT_TRUE (EFI_ERROR (Status));\r
+\r
+ FreePool (NewEntry);\r
+\r
+ return UNIT_TEST_PASSED;\r
+}\r
+\r
+/**\r
+ Test Case that locks a variable using Variable Lock Protocol Policy Protocol\r
+ then and then attempts to lock the same variable using the Variable Policy\r
+ Protocol. The call to Variable Lock is expected to succeed and the call to\r
+ Variable Policy is expected to fail.\r
+\r
+ @param[in] Context Unit test case context\r
+ **/\r
+UNIT_TEST_STATUS\r
+EFIAPI\r
+SettingPolicyForALockedVariableShouldFail (\r
+ IN UNIT_TEST_CONTEXT Context\r
+ )\r
+{\r
+ EFI_STATUS Status;\r
+ VARIABLE_POLICY_ENTRY *NewEntry;\r
+\r
+ // Lock the variable.\r
+ Status = VariableLockRequestToLock (NULL, TEST_VAR_1_NAME, &mTestGuid1);\r
+ UT_ASSERT_NOT_EFI_ERROR (Status);\r
+\r
+ // Create a variable policy that locks the variable.\r
+ Status = CreateVarStateVariablePolicy (&mTestGuid1,\r
+ TEST_VAR_1_NAME,\r
+ TEST_POLICY_MIN_SIZE_NULL,\r
+ TEST_POLICY_MAX_SIZE_200,\r
+ TEST_POLICY_ATTRIBUTES_NULL,\r
+ TEST_POLICY_ATTRIBUTES_NULL,\r
+ &mTestGuid2,\r
+ 1,\r
+ TEST_VAR_2_NAME,\r
+ &NewEntry);\r
+ UT_ASSERT_NOT_EFI_ERROR (Status);\r
+\r
+ // Register the new policy.\r
+ Status = RegisterVariablePolicy (NewEntry);\r
+ UT_ASSERT_TRUE (EFI_ERROR (Status));\r
+\r
+ FreePool (NewEntry);\r
+\r
+ return UNIT_TEST_PASSED;\r
+}\r
+\r
+/**\r
+ Main entry point to this unit test application.\r
+\r
+ Sets up and runs the test suites.\r
+**/\r
+VOID\r
+EFIAPI\r
+UnitTestMain (\r
+ VOID\r
+ )\r
+{\r
+ EFI_STATUS Status;\r
+ UNIT_TEST_FRAMEWORK_HANDLE Framework;\r
+ UNIT_TEST_SUITE_HANDLE ShimTests;\r
+\r
+ Framework = NULL;\r
+\r
+ DEBUG ((DEBUG_INFO, "%a v%a\n", UNIT_TEST_NAME, UNIT_TEST_VERSION));\r
+\r
+ //\r
+ // Start setting up the test framework for running the tests.\r
+ //\r
+ Status = InitUnitTestFramework (&Framework, UNIT_TEST_NAME, gEfiCallerBaseName, UNIT_TEST_VERSION);\r
+ if (EFI_ERROR (Status)) {\r
+ DEBUG ((DEBUG_ERROR, "Failed in InitUnitTestFramework. Status = %r\n", Status));\r
+ goto EXIT;\r
+ }\r
+\r
+ //\r
+ // Add all test suites and tests.\r
+ //\r
+ Status = CreateUnitTestSuite (\r
+ &ShimTests, Framework,\r
+ "Variable Lock Shim Tests", "VarPolicy.VarLockShim", NULL, NULL\r
+ );\r
+ if (EFI_ERROR (Status)) {\r
+ DEBUG ((DEBUG_ERROR, "Failed in CreateUnitTestSuite for ShimTests\n"));\r
+ Status = EFI_OUT_OF_RESOURCES;\r
+ goto EXIT;\r
+ }\r
+ AddTestCase (\r
+ ShimTests,\r
+ "Locking a variable with no matching policies should always work", "EmptyPolicies",\r
+ LockingWithoutAnyPoliciesShouldSucceed, LibInitMocked, LibCleanup, NULL\r
+ );\r
+ AddTestCase (\r
+ ShimTests,\r
+ "Locking a variable twice should always work", "DoubleLock",\r
+ LockingTwiceShouldSucceed, LibInitMocked, LibCleanup, NULL\r
+ );\r
+ AddTestCase (\r
+ ShimTests,\r
+ "Locking a variable that's already locked by another policy should work", "LockAfterPolicy",\r
+ LockingALockedVariableShouldSucceed, LibInitMocked, LibCleanup, NULL\r
+ );\r
+ AddTestCase (\r
+ ShimTests,\r
+ "Locking a variable that already has an unlocked policy should fail", "LockAfterUnlockedPolicy",\r
+ LockingAnUnlockedVariableShouldFail, LibInitMocked, LibCleanup, NULL\r
+ );\r
+ AddTestCase (\r
+ ShimTests,\r
+ "Locking a variable that already has an locked policy should succeed", "LockAfterLockedPolicyMatchingData",\r
+ LockingALockedVariableWithMatchingDataShouldSucceed, LibInitMocked, LibCleanup, NULL\r
+ );\r
+ AddTestCase (\r
+ ShimTests,\r
+ "Locking a variable that already has an locked policy with matching data should succeed", "LockAfterLockedPolicyNonMatchingData",\r
+ LockingALockedVariableWithNonMatchingDataShouldFail, LibInitMocked, LibCleanup, NULL\r
+ );\r
+ AddTestCase (\r
+ ShimTests,\r
+ "Adding a policy for a variable that has previously been locked should always fail", "SetPolicyAfterLock",\r
+ SettingPolicyForALockedVariableShouldFail, LibInitMocked, LibCleanup, NULL\r
+ );\r
+\r
+ //\r
+ // Execute the tests.\r
+ //\r
+ Status = RunAllTestSuites (Framework);\r
+\r
+EXIT:\r
+ if (Framework != NULL) {\r
+ FreeUnitTestFramework (Framework);\r
+ }\r
+\r
+ return;\r
+}\r
+\r
+///\r
+/// Avoid ECC error for function name that starts with lower case letter\r
+///\r
+#define Main main\r
+\r
+/**\r
+ Standard POSIX C entry point for host based unit test execution.\r
+\r
+ @param[in] Argc Number of arguments\r
+ @param[in] Argv Array of pointers to arguments\r
+\r
+ @retval 0 Success\r
+ @retval other Error\r
+**/\r
+INT32\r
+Main (\r
+ IN INT32 Argc,\r
+ IN CHAR8 *Argv[]\r
+ )\r
+{\r
+ UnitTestMain ();\r
+ return 0;\r
+}\r