summaryrefslogtreecommitdiffstats
path: root/MdeModulePkg/Library/VarCheckPolicyLib/VarCheckPolicyLib.c
blob: 14e1904e96d366106053a0ca4058d79918ea0114 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
/** @file -- VarCheckPolicyLib.c
This is a NULL library instance that leverages the VarCheck interface
and the business logic behind the VariablePolicy code to make its decisions.

Copyright (c) Microsoft Corporation.
SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include <Library/VarCheckLib.h>
#include <Library/BaseLib.h>
#include <Library/DebugLib.h>
#include <Library/SafeIntLib.h>
#include <Library/MmServicesTableLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/MemoryAllocationLib.h>

#include <Protocol/MmCommunication.h>

#include <Protocol/VariablePolicy.h>
#include <Library/VariablePolicyLib.h>

#include <Guid/VarCheckPolicyMmi.h>

#include "VarCheckPolicyLib.h"

//================================================
// As a VarCheck library, we're linked into the VariableServices
// and may not be able to call them indirectly. To get around this,
// use the internal GetVariable function to query the variable store.
//================================================
EFI_STATUS
EFIAPI
VariableServiceGetVariable (
  IN      CHAR16            *VariableName,
  IN      EFI_GUID          *VendorGuid,
  OUT     UINT32            *Attributes OPTIONAL,
  IN OUT  UINTN             *DataSize,
  OUT     VOID              *Data
  );


UINT8     mSecurityEvalBuffer[VAR_CHECK_POLICY_MM_COMM_BUFFER_SIZE];

// Pagination Cache Variables
UINT8       *mPaginationCache = NULL;
UINTN       mPaginationCacheSize = 0;
UINT32      mCurrentPaginationCommand = 0;


/**
  MM Communication Handler to recieve commands from the DXE protocol for
  Variable Policies. This communication channel is used to register new policies
  and poll and toggle the enforcement of variable policies.

  @param[in]      DispatchHandle      All parameters standard to MM communications convention.
  @param[in]      RegisterContext     All parameters standard to MM communications convention.
  @param[in,out]  CommBuffer          All parameters standard to MM communications convention.
  @param[in,out]  CommBufferSize      All parameters standard to MM communications convention.

  @retval     EFI_SUCCESS
  @retval     EFI_INVALID_PARAMETER   CommBuffer or CommBufferSize is null pointer.
  @retval     EFI_INVALID_PARAMETER   CommBuffer size is wrong.
  @retval     EFI_INVALID_PARAMETER   Revision or signature don't match.

**/
STATIC
EFI_STATUS
EFIAPI
VarCheckPolicyLibMmiHandler (
  IN     EFI_HANDLE                   DispatchHandle,
  IN     CONST VOID                   *RegisterContext,
  IN OUT VOID                         *CommBuffer,
  IN OUT UINTN                        *CommBufferSize
  )
{
  UINTN                                     InternalCommBufferSize;
  VOID                                      *InternalCommBuffer;
  EFI_STATUS                                Status;
  EFI_STATUS                                SubCommandStatus;
  VAR_CHECK_POLICY_COMM_HEADER              *PolicyCommmHeader;
  VAR_CHECK_POLICY_COMM_HEADER              *InternalPolicyCommmHeader;
  VAR_CHECK_POLICY_COMM_IS_ENABLED_PARAMS   *IsEnabledParams;
  VAR_CHECK_POLICY_COMM_DUMP_PARAMS         *DumpParamsIn;
  VAR_CHECK_POLICY_COMM_DUMP_PARAMS         *DumpParamsOut;
  UINT8                                     *DumpInputBuffer;
  UINT8                                     *DumpOutputBuffer;
  UINTN                                     DumpTotalPages;
  VARIABLE_POLICY_ENTRY                     *PolicyEntry;
  UINTN                                     ExpectedSize;
  UINT32                                    TempSize;

  Status = EFI_SUCCESS;

  //
  // Validate some input parameters.
  //
  // If either of the pointers are NULL, we can't proceed.
  if (CommBuffer == NULL || CommBufferSize == NULL) {
    DEBUG(( DEBUG_INFO, "%a - Invalid comm buffer pointers!\n", __FUNCTION__ ));
    return EFI_INVALID_PARAMETER;
  }
  // Make sure that the buffer does not overlap SMM.
  // This should be covered by the SmiManage infrastructure, but just to be safe...
  InternalCommBufferSize = *CommBufferSize;
  if (InternalCommBufferSize > VAR_CHECK_POLICY_MM_COMM_BUFFER_SIZE ||
      !VarCheckPolicyIsBufferOutsideValid((UINTN)CommBuffer, (UINT64)InternalCommBufferSize)) {
    DEBUG ((DEBUG_ERROR, "%a - Invalid CommBuffer supplied! 0x%016lX[0x%016lX]\n", __FUNCTION__, CommBuffer, InternalCommBufferSize));
    return EFI_INVALID_PARAMETER;
  }
  // If the size does not meet a minimum threshold, we cannot proceed.
  ExpectedSize = sizeof(VAR_CHECK_POLICY_COMM_HEADER);
  if (InternalCommBufferSize < ExpectedSize) {
    DEBUG(( DEBUG_INFO, "%a - Bad comm buffer size! %d < %d\n", __FUNCTION__, InternalCommBufferSize, ExpectedSize ));
    return EFI_INVALID_PARAMETER;
  }

  //
  // Before proceeding any further, copy the buffer internally so that we can compare
  // without worrying about TOCTOU.
  //
  InternalCommBuffer = &mSecurityEvalBuffer[0];
  CopyMem(InternalCommBuffer, CommBuffer, InternalCommBufferSize);
  PolicyCommmHeader = CommBuffer;
  InternalPolicyCommmHeader = InternalCommBuffer;
  // Check the revision and the signature of the comm header.
  if (InternalPolicyCommmHeader->Signature != VAR_CHECK_POLICY_COMM_SIG ||
      InternalPolicyCommmHeader->Revision != VAR_CHECK_POLICY_COMM_REVISION) {
    DEBUG(( DEBUG_INFO, "%a - Signature or revision are incorrect!\n", __FUNCTION__ ));
    // We have verified the buffer is not null and have enough size to hold Result field.
    PolicyCommmHeader->Result = EFI_INVALID_PARAMETER;
    return EFI_SUCCESS;
  }

  // If we're in the middle of a paginated dump and any other command is sent,
  // pagination cache must be cleared.
  if (mPaginationCache != NULL && InternalPolicyCommmHeader->Command != mCurrentPaginationCommand) {
    FreePool (mPaginationCache);
    mPaginationCache = NULL;
    mPaginationCacheSize = 0;
    mCurrentPaginationCommand = 0;
  }

  //
  // Now we can process the command as it was sent.
  //
  PolicyCommmHeader->Result = EFI_ABORTED;    // Set a default return for incomplete commands.
  switch(InternalPolicyCommmHeader->Command) {
    case VAR_CHECK_POLICY_COMMAND_DISABLE:
      PolicyCommmHeader->Result = DisableVariablePolicy();
      break;

    case VAR_CHECK_POLICY_COMMAND_IS_ENABLED:
      // Make sure that we're dealing with a reasonable size.
      // This add should be safe because these are fixed sizes so far.
      ExpectedSize += sizeof(VAR_CHECK_POLICY_COMM_IS_ENABLED_PARAMS);
      if (InternalCommBufferSize < ExpectedSize) {
        DEBUG(( DEBUG_INFO, "%a - Bad comm buffer size! %d < %d\n", __FUNCTION__, InternalCommBufferSize, ExpectedSize ));
        PolicyCommmHeader->Result = EFI_INVALID_PARAMETER;
        break;
      }

      // Now that we know we've got a valid size, we can fill in the rest of the data.
      IsEnabledParams = (VAR_CHECK_POLICY_COMM_IS_ENABLED_PARAMS*)((UINT8*)CommBuffer + sizeof(VAR_CHECK_POLICY_COMM_HEADER));
      IsEnabledParams->State = IsVariablePolicyEnabled();
      PolicyCommmHeader->Result = EFI_SUCCESS;
      break;

    case VAR_CHECK_POLICY_COMMAND_REGISTER:
      // Make sure that we're dealing with a reasonable size.
      // This add should be safe because these are fixed sizes so far.
      ExpectedSize += sizeof(VARIABLE_POLICY_ENTRY);
      if (InternalCommBufferSize < ExpectedSize) {
        DEBUG(( DEBUG_INFO, "%a - Bad comm buffer size! %d < %d\n", __FUNCTION__, InternalCommBufferSize, ExpectedSize ));
        PolicyCommmHeader->Result = EFI_INVALID_PARAMETER;
        break;
      }

      // At the very least, we can assume that we're working with a valid policy entry.
      // Time to compare its internal size.
      PolicyEntry = (VARIABLE_POLICY_ENTRY*)((UINT8*)InternalCommBuffer + sizeof(VAR_CHECK_POLICY_COMM_HEADER));
      if (PolicyEntry->Version != VARIABLE_POLICY_ENTRY_REVISION ||
          PolicyEntry->Size < sizeof(VARIABLE_POLICY_ENTRY) ||
          EFI_ERROR(SafeUintnAdd(sizeof(VAR_CHECK_POLICY_COMM_HEADER), PolicyEntry->Size, &ExpectedSize)) ||
          InternalCommBufferSize < ExpectedSize) {
        DEBUG(( DEBUG_INFO, "%a - Bad policy entry contents!\n", __FUNCTION__ ));
        PolicyCommmHeader->Result = EFI_INVALID_PARAMETER;
        break;
      }

      PolicyCommmHeader->Result = RegisterVariablePolicy( PolicyEntry );
      break;

    case VAR_CHECK_POLICY_COMMAND_DUMP:
      // Make sure that we're dealing with a reasonable size.
      // This add should be safe because these are fixed sizes so far.
      ExpectedSize += sizeof(VAR_CHECK_POLICY_COMM_DUMP_PARAMS) + VAR_CHECK_POLICY_MM_DUMP_BUFFER_SIZE;
      if (InternalCommBufferSize < ExpectedSize) {
        DEBUG(( DEBUG_INFO, "%a - Bad comm buffer size! %d < %d\n", __FUNCTION__, InternalCommBufferSize, ExpectedSize ));
        PolicyCommmHeader->Result = EFI_INVALID_PARAMETER;
        break;
      }

      // Now that we know we've got a valid size, we can fill in the rest of the data.
      DumpParamsIn = (VAR_CHECK_POLICY_COMM_DUMP_PARAMS*)(InternalPolicyCommmHeader + 1);
      DumpParamsOut = (VAR_CHECK_POLICY_COMM_DUMP_PARAMS*)(PolicyCommmHeader + 1);

      // If we're requesting the first page, initialize the cache and get the sizes.
      if (DumpParamsIn->PageRequested == 0) {
        if (mPaginationCache != NULL) {
          FreePool (mPaginationCache);
          mPaginationCache = NULL;
        }

        // Determine what the required size is going to be.
        DumpParamsOut->TotalSize = 0;
        DumpParamsOut->PageSize = 0;
        DumpParamsOut->HasMore = FALSE;
        SubCommandStatus = DumpVariablePolicy (NULL, &TempSize);
        if (SubCommandStatus == EFI_BUFFER_TOO_SMALL && TempSize > 0) {
          mCurrentPaginationCommand = VAR_CHECK_POLICY_COMMAND_DUMP;
          mPaginationCacheSize = TempSize;
          DumpParamsOut->TotalSize = TempSize;
          mPaginationCache = AllocatePool (mPaginationCacheSize);
          if (mPaginationCache == NULL) {
            SubCommandStatus = EFI_OUT_OF_RESOURCES;
          }
        }

        // If we've allocated our pagination cache, we're good to cache.
        if (mPaginationCache != NULL) {
          SubCommandStatus = DumpVariablePolicy (mPaginationCache, &TempSize);
        }

        // Populate the remaining fields and we can boogie.
        if (!EFI_ERROR (SubCommandStatus) && mPaginationCache != NULL) {
          DumpParamsOut->HasMore = TRUE;
        }
      } else if (mPaginationCache != NULL) {
        DumpParamsOut->TotalSize = (UINT32)mPaginationCacheSize;
        DumpOutputBuffer = (UINT8*)(DumpParamsOut + 1);

        // Make sure that we don't over-index the cache.
        DumpTotalPages = mPaginationCacheSize / VAR_CHECK_POLICY_MM_DUMP_BUFFER_SIZE;
        if (mPaginationCacheSize % VAR_CHECK_POLICY_MM_DUMP_BUFFER_SIZE != 0) {
          DumpTotalPages++;
        }
        if (DumpParamsIn->PageRequested > DumpTotalPages) {
          SubCommandStatus = EFI_INVALID_PARAMETER;
        } else {
          // Figure out how far into the page cache we need to go for our next page.
          // We know the blind subtraction won't be bad because we already checked for page 0.
          DumpInputBuffer = &mPaginationCache[VAR_CHECK_POLICY_MM_DUMP_BUFFER_SIZE * (DumpParamsIn->PageRequested - 1)];
          TempSize = VAR_CHECK_POLICY_MM_DUMP_BUFFER_SIZE;
          // If we're getting the last page, adjust the PageSize.
          if (DumpParamsIn->PageRequested == DumpTotalPages) {
            TempSize = mPaginationCacheSize % VAR_CHECK_POLICY_MM_DUMP_BUFFER_SIZE;
          }
          CopyMem (DumpOutputBuffer, DumpInputBuffer, TempSize);
          DumpParamsOut->PageSize = TempSize;
          // If we just got the last page, settle up the cache.
          if (DumpParamsIn->PageRequested == DumpTotalPages) {
            DumpParamsOut->HasMore = FALSE;
            FreePool (mPaginationCache);
            mPaginationCache = NULL;
            mPaginationCacheSize = 0;
            mCurrentPaginationCommand = 0;
          // Otherwise, we could do more here.
          } else {
            DumpParamsOut->HasMore = TRUE;
          }

          // If we made it this far, we're basically good.
          SubCommandStatus = EFI_SUCCESS;
        }
      // If we've requested any other page than 0 and the cache is empty, we must have timed out.
      } else {
        DumpParamsOut->TotalSize = 0;
        DumpParamsOut->PageSize = 0;
        DumpParamsOut->HasMore = FALSE;
        SubCommandStatus = EFI_TIMEOUT;
      }

      // There's currently no use for this, but it shouldn't be hard to implement.
      PolicyCommmHeader->Result = SubCommandStatus;
      break;

    case VAR_CHECK_POLICY_COMMAND_LOCK:
      PolicyCommmHeader->Result = LockVariablePolicy();
      break;

    default:
      // Mark unknown requested command as EFI_UNSUPPORTED.
      DEBUG(( DEBUG_INFO, "%a - Invalid command requested! %d\n", __FUNCTION__, PolicyCommmHeader->Command ));
      PolicyCommmHeader->Result = EFI_UNSUPPORTED;
      break;
  }

  DEBUG(( DEBUG_VERBOSE, "%a - Command %d returning %r.\n", __FUNCTION__,
          PolicyCommmHeader->Command, PolicyCommmHeader->Result ));

  return Status;
}


/**
  Constructor function of VarCheckPolicyLib to register VarCheck handler and
  SW MMI handlers.

  @retval EFI_SUCCESS       The constructor executed correctly.

**/
EFI_STATUS
EFIAPI
VarCheckPolicyLibCommonConstructor (
  VOID
  )
{
  EFI_STATUS    Status;
  EFI_HANDLE    DiscardedHandle;

  // Initialize the business logic with the internal GetVariable handler.
  Status = InitVariablePolicyLib( VariableServiceGetVariable );

  // Only proceed with init if the business logic could be initialized.
  if (!EFI_ERROR( Status )) {
    // Register the VarCheck handler for SetVariable filtering.
    // Forward the check to the business logic of the library.
    VarCheckLibRegisterSetVariableCheckHandler( ValidateSetVariable );

    // Register the MMI handlers for receiving policy commands.
    DiscardedHandle = NULL;
    Status = gMmst->MmiHandlerRegister( VarCheckPolicyLibMmiHandler,
                                        &gVarCheckPolicyLibMmiHandlerGuid,
                                        &DiscardedHandle );
  }
  // Otherwise, there's not much we can do.
  else {
    DEBUG(( DEBUG_ERROR, "%a - Cannot Initialize VariablePolicyLib! %r\n", __FUNCTION__, Status ));
    ASSERT_EFI_ERROR( Status );
  }

  return Status;
}