1 /** @file -- VarCheckPolicyLib.c
2 This is a NULL library instance that leverages the VarCheck interface
3 and the business logic behind the VariablePolicy code to make its decisions.
4 
5 Copyright (c) Microsoft Corporation.
6 SPDX-License-Identifier: BSD-2-Clause-Patent
7 
8 **/
9 
10 #include <Library/VarCheckLib.h>
11 #include <Library/BaseLib.h>
12 #include <Library/DebugLib.h>
13 #include <Library/SafeIntLib.h>
14 #include <Library/MmServicesTableLib.h>
15 #include <Library/BaseMemoryLib.h>
16 #include <Library/MemoryAllocationLib.h>
17 
18 #include <Protocol/MmCommunication.h>
19 
20 #include <Protocol/VariablePolicy.h>
21 #include <Library/VariablePolicyLib.h>
22 
23 #include <Guid/VarCheckPolicyMmi.h>
24 
25 #include "VarCheckPolicyLib.h"
26 
27 //================================================
28 // As a VarCheck library, we're linked into the VariableServices
29 // and may not be able to call them indirectly. To get around this,
30 // use the internal GetVariable function to query the variable store.
31 //================================================
32 EFI_STATUS
33 EFIAPI
34 VariableServiceGetVariable (
35   IN      CHAR16            *VariableName,
36   IN      EFI_GUID          *VendorGuid,
37   OUT     UINT32            *Attributes OPTIONAL,
38   IN OUT  UINTN             *DataSize,
39   OUT     VOID              *Data
40   );
41 
42 
43 UINT8     mSecurityEvalBuffer[VAR_CHECK_POLICY_MM_COMM_BUFFER_SIZE];
44 
45 // Pagination Cache Variables
46 UINT8       *mPaginationCache = NULL;
47 UINTN       mPaginationCacheSize = 0;
48 UINT32      mCurrentPaginationCommand = 0;
49 
50 
51 /**
52   MM Communication Handler to recieve commands from the DXE protocol for
53   Variable Policies. This communication channel is used to register new policies
54   and poll and toggle the enforcement of variable policies.
55 
56   @param[in]      DispatchHandle      All parameters standard to MM communications convention.
57   @param[in]      RegisterContext     All parameters standard to MM communications convention.
58   @param[in,out]  CommBuffer          All parameters standard to MM communications convention.
59   @param[in,out]  CommBufferSize      All parameters standard to MM communications convention.
60 
61   @retval     EFI_SUCCESS
62   @retval     EFI_INVALID_PARAMETER   CommBuffer or CommBufferSize is null pointer.
63   @retval     EFI_INVALID_PARAMETER   CommBuffer size is wrong.
64   @retval     EFI_INVALID_PARAMETER   Revision or signature don't match.
65 
66 **/
67 STATIC
68 EFI_STATUS
69 EFIAPI
VarCheckPolicyLibMmiHandler(IN EFI_HANDLE DispatchHandle,IN CONST VOID * RegisterContext,IN OUT VOID * CommBuffer,IN OUT UINTN * CommBufferSize)70 VarCheckPolicyLibMmiHandler (
71   IN     EFI_HANDLE                   DispatchHandle,
72   IN     CONST VOID                   *RegisterContext,
73   IN OUT VOID                         *CommBuffer,
74   IN OUT UINTN                        *CommBufferSize
75   )
76 {
77   UINTN                                     InternalCommBufferSize;
78   VOID                                      *InternalCommBuffer;
79   EFI_STATUS                                Status;
80   EFI_STATUS                                SubCommandStatus;
81   VAR_CHECK_POLICY_COMM_HEADER              *PolicyCommmHeader;
82   VAR_CHECK_POLICY_COMM_HEADER              *InternalPolicyCommmHeader;
83   VAR_CHECK_POLICY_COMM_IS_ENABLED_PARAMS   *IsEnabledParams;
84   VAR_CHECK_POLICY_COMM_DUMP_PARAMS         *DumpParamsIn;
85   VAR_CHECK_POLICY_COMM_DUMP_PARAMS         *DumpParamsOut;
86   UINT8                                     *DumpInputBuffer;
87   UINT8                                     *DumpOutputBuffer;
88   UINTN                                     DumpTotalPages;
89   VARIABLE_POLICY_ENTRY                     *PolicyEntry;
90   UINTN                                     ExpectedSize;
91   UINT32                                    TempSize;
92 
93   Status = EFI_SUCCESS;
94 
95   //
96   // Validate some input parameters.
97   //
98   // If either of the pointers are NULL, we can't proceed.
99   if (CommBuffer == NULL || CommBufferSize == NULL) {
100     DEBUG(( DEBUG_INFO, "%a - Invalid comm buffer pointers!\n", __FUNCTION__ ));
101     return EFI_INVALID_PARAMETER;
102   }
103   // Make sure that the buffer does not overlap SMM.
104   // This should be covered by the SmiManage infrastructure, but just to be safe...
105   InternalCommBufferSize = *CommBufferSize;
106   if (InternalCommBufferSize > VAR_CHECK_POLICY_MM_COMM_BUFFER_SIZE ||
107       !VarCheckPolicyIsBufferOutsideValid((UINTN)CommBuffer, (UINT64)InternalCommBufferSize)) {
108     DEBUG ((DEBUG_ERROR, "%a - Invalid CommBuffer supplied! 0x%016lX[0x%016lX]\n", __FUNCTION__, CommBuffer, InternalCommBufferSize));
109     return EFI_INVALID_PARAMETER;
110   }
111   // If the size does not meet a minimum threshold, we cannot proceed.
112   ExpectedSize = sizeof(VAR_CHECK_POLICY_COMM_HEADER);
113   if (InternalCommBufferSize < ExpectedSize) {
114     DEBUG(( DEBUG_INFO, "%a - Bad comm buffer size! %d < %d\n", __FUNCTION__, InternalCommBufferSize, ExpectedSize ));
115     return EFI_INVALID_PARAMETER;
116   }
117 
118   //
119   // Before proceeding any further, copy the buffer internally so that we can compare
120   // without worrying about TOCTOU.
121   //
122   InternalCommBuffer = &mSecurityEvalBuffer[0];
123   CopyMem(InternalCommBuffer, CommBuffer, InternalCommBufferSize);
124   PolicyCommmHeader = CommBuffer;
125   InternalPolicyCommmHeader = InternalCommBuffer;
126   // Check the revision and the signature of the comm header.
127   if (InternalPolicyCommmHeader->Signature != VAR_CHECK_POLICY_COMM_SIG ||
128       InternalPolicyCommmHeader->Revision != VAR_CHECK_POLICY_COMM_REVISION) {
129     DEBUG(( DEBUG_INFO, "%a - Signature or revision are incorrect!\n", __FUNCTION__ ));
130     // We have verified the buffer is not null and have enough size to hold Result field.
131     PolicyCommmHeader->Result = EFI_INVALID_PARAMETER;
132     return EFI_SUCCESS;
133   }
134 
135   // If we're in the middle of a paginated dump and any other command is sent,
136   // pagination cache must be cleared.
137   if (mPaginationCache != NULL && InternalPolicyCommmHeader->Command != mCurrentPaginationCommand) {
138     FreePool (mPaginationCache);
139     mPaginationCache = NULL;
140     mPaginationCacheSize = 0;
141     mCurrentPaginationCommand = 0;
142   }
143 
144   //
145   // Now we can process the command as it was sent.
146   //
147   PolicyCommmHeader->Result = EFI_ABORTED;    // Set a default return for incomplete commands.
148   switch(InternalPolicyCommmHeader->Command) {
149     case VAR_CHECK_POLICY_COMMAND_DISABLE:
150       PolicyCommmHeader->Result = DisableVariablePolicy();
151       break;
152 
153     case VAR_CHECK_POLICY_COMMAND_IS_ENABLED:
154       // Make sure that we're dealing with a reasonable size.
155       // This add should be safe because these are fixed sizes so far.
156       ExpectedSize += sizeof(VAR_CHECK_POLICY_COMM_IS_ENABLED_PARAMS);
157       if (InternalCommBufferSize < ExpectedSize) {
158         DEBUG(( DEBUG_INFO, "%a - Bad comm buffer size! %d < %d\n", __FUNCTION__, InternalCommBufferSize, ExpectedSize ));
159         PolicyCommmHeader->Result = EFI_INVALID_PARAMETER;
160         break;
161       }
162 
163       // Now that we know we've got a valid size, we can fill in the rest of the data.
164       IsEnabledParams = (VAR_CHECK_POLICY_COMM_IS_ENABLED_PARAMS*)((UINT8*)CommBuffer + sizeof(VAR_CHECK_POLICY_COMM_HEADER));
165       IsEnabledParams->State = IsVariablePolicyEnabled();
166       PolicyCommmHeader->Result = EFI_SUCCESS;
167       break;
168 
169     case VAR_CHECK_POLICY_COMMAND_REGISTER:
170       // Make sure that we're dealing with a reasonable size.
171       // This add should be safe because these are fixed sizes so far.
172       ExpectedSize += sizeof(VARIABLE_POLICY_ENTRY);
173       if (InternalCommBufferSize < ExpectedSize) {
174         DEBUG(( DEBUG_INFO, "%a - Bad comm buffer size! %d < %d\n", __FUNCTION__, InternalCommBufferSize, ExpectedSize ));
175         PolicyCommmHeader->Result = EFI_INVALID_PARAMETER;
176         break;
177       }
178 
179       // At the very least, we can assume that we're working with a valid policy entry.
180       // Time to compare its internal size.
181       PolicyEntry = (VARIABLE_POLICY_ENTRY*)((UINT8*)InternalCommBuffer + sizeof(VAR_CHECK_POLICY_COMM_HEADER));
182       if (PolicyEntry->Version != VARIABLE_POLICY_ENTRY_REVISION ||
183           PolicyEntry->Size < sizeof(VARIABLE_POLICY_ENTRY) ||
184           EFI_ERROR(SafeUintnAdd(sizeof(VAR_CHECK_POLICY_COMM_HEADER), PolicyEntry->Size, &ExpectedSize)) ||
185           InternalCommBufferSize < ExpectedSize) {
186         DEBUG(( DEBUG_INFO, "%a - Bad policy entry contents!\n", __FUNCTION__ ));
187         PolicyCommmHeader->Result = EFI_INVALID_PARAMETER;
188         break;
189       }
190 
191       PolicyCommmHeader->Result = RegisterVariablePolicy( PolicyEntry );
192       break;
193 
194     case VAR_CHECK_POLICY_COMMAND_DUMP:
195       // Make sure that we're dealing with a reasonable size.
196       // This add should be safe because these are fixed sizes so far.
197       ExpectedSize += sizeof(VAR_CHECK_POLICY_COMM_DUMP_PARAMS) + VAR_CHECK_POLICY_MM_DUMP_BUFFER_SIZE;
198       if (InternalCommBufferSize < ExpectedSize) {
199         DEBUG(( DEBUG_INFO, "%a - Bad comm buffer size! %d < %d\n", __FUNCTION__, InternalCommBufferSize, ExpectedSize ));
200         PolicyCommmHeader->Result = EFI_INVALID_PARAMETER;
201         break;
202       }
203 
204       // Now that we know we've got a valid size, we can fill in the rest of the data.
205       DumpParamsIn = (VAR_CHECK_POLICY_COMM_DUMP_PARAMS*)(InternalPolicyCommmHeader + 1);
206       DumpParamsOut = (VAR_CHECK_POLICY_COMM_DUMP_PARAMS*)(PolicyCommmHeader + 1);
207 
208       // If we're requesting the first page, initialize the cache and get the sizes.
209       if (DumpParamsIn->PageRequested == 0) {
210         if (mPaginationCache != NULL) {
211           FreePool (mPaginationCache);
212           mPaginationCache = NULL;
213         }
214 
215         // Determine what the required size is going to be.
216         DumpParamsOut->TotalSize = 0;
217         DumpParamsOut->PageSize = 0;
218         DumpParamsOut->HasMore = FALSE;
219         SubCommandStatus = DumpVariablePolicy (NULL, &TempSize);
220         if (SubCommandStatus == EFI_BUFFER_TOO_SMALL && TempSize > 0) {
221           mCurrentPaginationCommand = VAR_CHECK_POLICY_COMMAND_DUMP;
222           mPaginationCacheSize = TempSize;
223           DumpParamsOut->TotalSize = TempSize;
224           mPaginationCache = AllocatePool (mPaginationCacheSize);
225           if (mPaginationCache == NULL) {
226             SubCommandStatus = EFI_OUT_OF_RESOURCES;
227           }
228         }
229 
230         // If we've allocated our pagination cache, we're good to cache.
231         if (mPaginationCache != NULL) {
232           SubCommandStatus = DumpVariablePolicy (mPaginationCache, &TempSize);
233         }
234 
235         // Populate the remaining fields and we can boogie.
236         if (!EFI_ERROR (SubCommandStatus) && mPaginationCache != NULL) {
237           DumpParamsOut->HasMore = TRUE;
238         }
239       } else if (mPaginationCache != NULL) {
240         DumpParamsOut->TotalSize = (UINT32)mPaginationCacheSize;
241         DumpOutputBuffer = (UINT8*)(DumpParamsOut + 1);
242 
243         // Make sure that we don't over-index the cache.
244         DumpTotalPages = mPaginationCacheSize / VAR_CHECK_POLICY_MM_DUMP_BUFFER_SIZE;
245         if (mPaginationCacheSize % VAR_CHECK_POLICY_MM_DUMP_BUFFER_SIZE != 0) {
246           DumpTotalPages++;
247         }
248         if (DumpParamsIn->PageRequested > DumpTotalPages) {
249           SubCommandStatus = EFI_INVALID_PARAMETER;
250         } else {
251           // Figure out how far into the page cache we need to go for our next page.
252           // We know the blind subtraction won't be bad because we already checked for page 0.
253           DumpInputBuffer = &mPaginationCache[VAR_CHECK_POLICY_MM_DUMP_BUFFER_SIZE * (DumpParamsIn->PageRequested - 1)];
254           TempSize = VAR_CHECK_POLICY_MM_DUMP_BUFFER_SIZE;
255           // If we're getting the last page, adjust the PageSize.
256           if (DumpParamsIn->PageRequested == DumpTotalPages) {
257             TempSize = mPaginationCacheSize % VAR_CHECK_POLICY_MM_DUMP_BUFFER_SIZE;
258           }
259           CopyMem (DumpOutputBuffer, DumpInputBuffer, TempSize);
260           DumpParamsOut->PageSize = TempSize;
261           // If we just got the last page, settle up the cache.
262           if (DumpParamsIn->PageRequested == DumpTotalPages) {
263             DumpParamsOut->HasMore = FALSE;
264             FreePool (mPaginationCache);
265             mPaginationCache = NULL;
266             mPaginationCacheSize = 0;
267             mCurrentPaginationCommand = 0;
268           // Otherwise, we could do more here.
269           } else {
270             DumpParamsOut->HasMore = TRUE;
271           }
272 
273           // If we made it this far, we're basically good.
274           SubCommandStatus = EFI_SUCCESS;
275         }
276       // If we've requested any other page than 0 and the cache is empty, we must have timed out.
277       } else {
278         DumpParamsOut->TotalSize = 0;
279         DumpParamsOut->PageSize = 0;
280         DumpParamsOut->HasMore = FALSE;
281         SubCommandStatus = EFI_TIMEOUT;
282       }
283 
284       // There's currently no use for this, but it shouldn't be hard to implement.
285       PolicyCommmHeader->Result = SubCommandStatus;
286       break;
287 
288     case VAR_CHECK_POLICY_COMMAND_LOCK:
289       PolicyCommmHeader->Result = LockVariablePolicy();
290       break;
291 
292     default:
293       // Mark unknown requested command as EFI_UNSUPPORTED.
294       DEBUG(( DEBUG_INFO, "%a - Invalid command requested! %d\n", __FUNCTION__, PolicyCommmHeader->Command ));
295       PolicyCommmHeader->Result = EFI_UNSUPPORTED;
296       break;
297   }
298 
299   DEBUG(( DEBUG_VERBOSE, "%a - Command %d returning %r.\n", __FUNCTION__,
300           PolicyCommmHeader->Command, PolicyCommmHeader->Result ));
301 
302   return Status;
303 }
304 
305 
306 /**
307   Constructor function of VarCheckPolicyLib to register VarCheck handler and
308   SW MMI handlers.
309 
310   @retval EFI_SUCCESS       The constructor executed correctly.
311 
312 **/
313 EFI_STATUS
314 EFIAPI
VarCheckPolicyLibCommonConstructor(VOID)315 VarCheckPolicyLibCommonConstructor (
316   VOID
317   )
318 {
319   EFI_STATUS    Status;
320   EFI_HANDLE    DiscardedHandle;
321 
322   // Initialize the business logic with the internal GetVariable handler.
323   Status = InitVariablePolicyLib( VariableServiceGetVariable );
324 
325   // Only proceed with init if the business logic could be initialized.
326   if (!EFI_ERROR( Status )) {
327     // Register the VarCheck handler for SetVariable filtering.
328     // Forward the check to the business logic of the library.
329     VarCheckLibRegisterSetVariableCheckHandler( ValidateSetVariable );
330 
331     // Register the MMI handlers for receiving policy commands.
332     DiscardedHandle = NULL;
333     Status = gMmst->MmiHandlerRegister( VarCheckPolicyLibMmiHandler,
334                                         &gVarCheckPolicyLibMmiHandlerGuid,
335                                         &DiscardedHandle );
336   }
337   // Otherwise, there's not much we can do.
338   else {
339     DEBUG(( DEBUG_ERROR, "%a - Cannot Initialize VariablePolicyLib! %r\n", __FUNCTION__, Status ));
340     ASSERT_EFI_ERROR( Status );
341   }
342 
343   return Status;
344 }
345