Home | History | Annotate | Download | only in AuthVariableLib
      1 /** @file
      2   Implement authentication services for the authenticated variables.
      3 
      4   Caution: This module requires additional review when modified.
      5   This driver will have external input - variable data. It may be input in SMM mode.
      6   This external input must be validated carefully to avoid security issue like
      7   buffer overflow, integer overflow.
      8   Variable attribute should also be checked to avoid authentication bypass.
      9      The whole SMM authentication variable design relies on the integrity of flash part and SMM.
     10   which is assumed to be protected by platform.  All variable code and metadata in flash/SMM Memory
     11   may not be modified without authorization. If platform fails to protect these resources,
     12   the authentication service provided in this driver will be broken, and the behavior is undefined.
     13 
     14 Copyright (c) 2015 - 2016, Intel Corporation. All rights reserved.<BR>
     15 This program and the accompanying materials
     16 are licensed and made available under the terms and conditions of the BSD License
     17 which accompanies this distribution.  The full text of the license may be found at
     18 http://opensource.org/licenses/bsd-license.php
     19 
     20 THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
     21 WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
     22 
     23 **/
     24 
     25 #include "AuthServiceInternal.h"
     26 
     27 ///
     28 /// Global database array for scratch
     29 ///
     30 UINT8    *mPubKeyStore;
     31 UINT32   mPubKeyNumber;
     32 UINT32   mMaxKeyNumber;
     33 UINT32   mMaxKeyDbSize;
     34 UINT8    *mCertDbStore;
     35 UINT32   mMaxCertDbSize;
     36 UINT32   mPlatformMode;
     37 UINT8    mVendorKeyState;
     38 
     39 EFI_GUID mSignatureSupport[] = {EFI_CERT_SHA1_GUID, EFI_CERT_SHA256_GUID, EFI_CERT_RSA2048_GUID, EFI_CERT_X509_GUID};
     40 
     41 //
     42 // Hash context pointer
     43 //
     44 VOID  *mHashCtx = NULL;
     45 
     46 VARIABLE_ENTRY_PROPERTY mAuthVarEntry[] = {
     47   {
     48     &gEfiSecureBootEnableDisableGuid,
     49     EFI_SECURE_BOOT_ENABLE_NAME,
     50     {
     51       VAR_CHECK_VARIABLE_PROPERTY_REVISION,
     52       0,
     53       VARIABLE_ATTRIBUTE_NV_BS,
     54       sizeof (UINT8),
     55       sizeof (UINT8)
     56     }
     57   },
     58   {
     59     &gEfiCustomModeEnableGuid,
     60     EFI_CUSTOM_MODE_NAME,
     61     {
     62       VAR_CHECK_VARIABLE_PROPERTY_REVISION,
     63       0,
     64       VARIABLE_ATTRIBUTE_NV_BS,
     65       sizeof (UINT8),
     66       sizeof (UINT8)
     67     }
     68   },
     69   {
     70     &gEfiVendorKeysNvGuid,
     71     EFI_VENDOR_KEYS_NV_VARIABLE_NAME,
     72     {
     73       VAR_CHECK_VARIABLE_PROPERTY_REVISION,
     74       VAR_CHECK_VARIABLE_PROPERTY_READ_ONLY,
     75       VARIABLE_ATTRIBUTE_NV_BS_RT_AT,
     76       sizeof (UINT8),
     77       sizeof (UINT8)
     78     }
     79   },
     80   {
     81     &gEfiAuthenticatedVariableGuid,
     82     AUTHVAR_KEYDB_NAME,
     83     {
     84       VAR_CHECK_VARIABLE_PROPERTY_REVISION,
     85       VAR_CHECK_VARIABLE_PROPERTY_READ_ONLY,
     86       VARIABLE_ATTRIBUTE_NV_BS_RT_AW,
     87       sizeof (UINT8),
     88       MAX_UINTN
     89     }
     90   },
     91   {
     92     &gEfiCertDbGuid,
     93     EFI_CERT_DB_NAME,
     94     {
     95       VAR_CHECK_VARIABLE_PROPERTY_REVISION,
     96       VAR_CHECK_VARIABLE_PROPERTY_READ_ONLY,
     97       VARIABLE_ATTRIBUTE_NV_BS_RT_AT,
     98       sizeof (UINT32),
     99       MAX_UINTN
    100     }
    101   },
    102   {
    103     &gEfiCertDbGuid,
    104     EFI_CERT_DB_VOLATILE_NAME,
    105     {
    106       VAR_CHECK_VARIABLE_PROPERTY_REVISION,
    107       VAR_CHECK_VARIABLE_PROPERTY_READ_ONLY,
    108       VARIABLE_ATTRIBUTE_BS_RT_AT,
    109       sizeof (UINT32),
    110       MAX_UINTN
    111     }
    112   },
    113 };
    114 
    115 VOID **mAuthVarAddressPointer[10];
    116 
    117 AUTH_VAR_LIB_CONTEXT_IN *mAuthVarLibContextIn = NULL;
    118 
    119 /**
    120   Initialization for authenticated varibale services.
    121   If this initialization returns error status, other APIs will not work
    122   and expect to be not called then.
    123 
    124   @param[in]  AuthVarLibContextIn   Pointer to input auth variable lib context.
    125   @param[out] AuthVarLibContextOut  Pointer to output auth variable lib context.
    126 
    127   @retval EFI_SUCCESS               Function successfully executed.
    128   @retval EFI_INVALID_PARAMETER     If AuthVarLibContextIn == NULL or AuthVarLibContextOut == NULL.
    129   @retval EFI_OUT_OF_RESOURCES      Fail to allocate enough resource.
    130   @retval EFI_UNSUPPORTED           Unsupported to process authenticated variable.
    131 
    132 **/
    133 EFI_STATUS
    134 EFIAPI
    135 AuthVariableLibInitialize (
    136   IN  AUTH_VAR_LIB_CONTEXT_IN   *AuthVarLibContextIn,
    137   OUT AUTH_VAR_LIB_CONTEXT_OUT  *AuthVarLibContextOut
    138   )
    139 {
    140   EFI_STATUS            Status;
    141   UINT8                 VarValue;
    142   UINT32                VarAttr;
    143   UINT8                 *Data;
    144   UINTN                 DataSize;
    145   UINTN                 CtxSize;
    146   UINT8                 SecureBootMode;
    147   UINT8                 SecureBootEnable;
    148   UINT8                 CustomMode;
    149   UINT32                ListSize;
    150 
    151   if ((AuthVarLibContextIn == NULL) || (AuthVarLibContextOut == NULL)) {
    152     return EFI_INVALID_PARAMETER;
    153   }
    154 
    155   mAuthVarLibContextIn = AuthVarLibContextIn;
    156 
    157   //
    158   // Initialize hash context.
    159   //
    160   CtxSize   = Sha256GetContextSize ();
    161   mHashCtx  = AllocateRuntimePool (CtxSize);
    162   if (mHashCtx == NULL) {
    163     return EFI_OUT_OF_RESOURCES;
    164   }
    165 
    166   //
    167   // Reserve runtime buffer for public key database. The size excludes variable header and name size.
    168   //
    169   mMaxKeyDbSize = (UINT32) (mAuthVarLibContextIn->MaxAuthVariableSize - sizeof (AUTHVAR_KEYDB_NAME));
    170   mMaxKeyNumber = mMaxKeyDbSize / sizeof (AUTHVAR_KEY_DB_DATA);
    171   mPubKeyStore  = AllocateRuntimePool (mMaxKeyDbSize);
    172   if (mPubKeyStore == NULL) {
    173     return EFI_OUT_OF_RESOURCES;
    174   }
    175 
    176   //
    177   // Reserve runtime buffer for certificate database. The size excludes variable header and name size.
    178   // Use EFI_CERT_DB_VOLATILE_NAME size since it is longer.
    179   //
    180   mMaxCertDbSize = (UINT32) (mAuthVarLibContextIn->MaxAuthVariableSize - sizeof (EFI_CERT_DB_VOLATILE_NAME));
    181   mCertDbStore   = AllocateRuntimePool (mMaxCertDbSize);
    182   if (mCertDbStore == NULL) {
    183     return EFI_OUT_OF_RESOURCES;
    184   }
    185 
    186   //
    187   // Check "AuthVarKeyDatabase" variable's existence.
    188   // If it doesn't exist, create a new one with initial value of 0 and EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS set.
    189   //
    190   Status = AuthServiceInternalFindVariable (
    191              AUTHVAR_KEYDB_NAME,
    192              &gEfiAuthenticatedVariableGuid,
    193              (VOID **) &Data,
    194              &DataSize
    195              );
    196   if (EFI_ERROR (Status)) {
    197     VarAttr       = EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_RUNTIME_ACCESS | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS;
    198     VarValue      = 0;
    199     mPubKeyNumber = 0;
    200     Status        = AuthServiceInternalUpdateVariable (
    201                       AUTHVAR_KEYDB_NAME,
    202                       &gEfiAuthenticatedVariableGuid,
    203                       &VarValue,
    204                       sizeof(UINT8),
    205                       VarAttr
    206                       );
    207     if (EFI_ERROR (Status)) {
    208       return Status;
    209     }
    210   } else {
    211     //
    212     // Load database in global variable for cache.
    213     //
    214     ASSERT ((DataSize != 0) && (Data != NULL));
    215     //
    216     // "AuthVarKeyDatabase" is an internal variable. Its DataSize is always ensured not to exceed mPubKeyStore buffer size(See definition before)
    217     //  Therefore, there is no memory overflow in underlying CopyMem.
    218     //
    219     CopyMem (mPubKeyStore, (UINT8 *) Data, DataSize);
    220     mPubKeyNumber = (UINT32) (DataSize / sizeof (AUTHVAR_KEY_DB_DATA));
    221   }
    222 
    223   Status = AuthServiceInternalFindVariable (EFI_PLATFORM_KEY_NAME, &gEfiGlobalVariableGuid, (VOID **) &Data, &DataSize);
    224   if (EFI_ERROR (Status)) {
    225     DEBUG ((EFI_D_INFO, "Variable %s does not exist.\n", EFI_PLATFORM_KEY_NAME));
    226   } else {
    227     DEBUG ((EFI_D_INFO, "Variable %s exists.\n", EFI_PLATFORM_KEY_NAME));
    228   }
    229 
    230   //
    231   // Create "SetupMode" variable with BS+RT attribute set.
    232   //
    233   if (EFI_ERROR (Status)) {
    234     mPlatformMode = SETUP_MODE;
    235   } else {
    236     mPlatformMode = USER_MODE;
    237   }
    238   Status = AuthServiceInternalUpdateVariable (
    239              EFI_SETUP_MODE_NAME,
    240              &gEfiGlobalVariableGuid,
    241              &mPlatformMode,
    242              sizeof(UINT8),
    243              EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS
    244              );
    245   if (EFI_ERROR (Status)) {
    246     return Status;
    247   }
    248 
    249   //
    250   // Create "SignatureSupport" variable with BS+RT attribute set.
    251   //
    252   Status  = AuthServiceInternalUpdateVariable (
    253               EFI_SIGNATURE_SUPPORT_NAME,
    254               &gEfiGlobalVariableGuid,
    255               mSignatureSupport,
    256               sizeof(mSignatureSupport),
    257               EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS
    258               );
    259   if (EFI_ERROR (Status)) {
    260     return Status;
    261   }
    262 
    263   //
    264   // If "SecureBootEnable" variable exists, then update "SecureBoot" variable.
    265   // If "SecureBootEnable" variable is SECURE_BOOT_ENABLE and in USER_MODE, Set "SecureBoot" variable to SECURE_BOOT_MODE_ENABLE.
    266   // If "SecureBootEnable" variable is SECURE_BOOT_DISABLE, Set "SecureBoot" variable to SECURE_BOOT_MODE_DISABLE.
    267   //
    268   SecureBootEnable = SECURE_BOOT_DISABLE;
    269   Status = AuthServiceInternalFindVariable (EFI_SECURE_BOOT_ENABLE_NAME, &gEfiSecureBootEnableDisableGuid, (VOID **) &Data, &DataSize);
    270   if (!EFI_ERROR (Status)) {
    271     if (mPlatformMode == USER_MODE){
    272       SecureBootEnable = *(UINT8 *) Data;
    273     }
    274   } else if (mPlatformMode == USER_MODE) {
    275     //
    276     // "SecureBootEnable" not exist, initialize it in USER_MODE.
    277     //
    278     SecureBootEnable = SECURE_BOOT_ENABLE;
    279     Status = AuthServiceInternalUpdateVariable (
    280                EFI_SECURE_BOOT_ENABLE_NAME,
    281                &gEfiSecureBootEnableDisableGuid,
    282                &SecureBootEnable,
    283                sizeof (UINT8),
    284                EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS
    285                );
    286     if (EFI_ERROR (Status)) {
    287       return Status;
    288     }
    289   }
    290 
    291   //
    292   // Create "SecureBoot" variable with BS+RT attribute set.
    293   //
    294   if (SecureBootEnable == SECURE_BOOT_ENABLE && mPlatformMode == USER_MODE) {
    295     SecureBootMode = SECURE_BOOT_MODE_ENABLE;
    296   } else {
    297     SecureBootMode = SECURE_BOOT_MODE_DISABLE;
    298   }
    299   Status = AuthServiceInternalUpdateVariable (
    300              EFI_SECURE_BOOT_MODE_NAME,
    301              &gEfiGlobalVariableGuid,
    302              &SecureBootMode,
    303              sizeof (UINT8),
    304              EFI_VARIABLE_RUNTIME_ACCESS | EFI_VARIABLE_BOOTSERVICE_ACCESS
    305              );
    306   if (EFI_ERROR (Status)) {
    307     return Status;
    308   }
    309 
    310   DEBUG ((EFI_D_INFO, "Variable %s is %x\n", EFI_SETUP_MODE_NAME, mPlatformMode));
    311   DEBUG ((EFI_D_INFO, "Variable %s is %x\n", EFI_SECURE_BOOT_MODE_NAME, SecureBootMode));
    312   DEBUG ((EFI_D_INFO, "Variable %s is %x\n", EFI_SECURE_BOOT_ENABLE_NAME, SecureBootEnable));
    313 
    314   //
    315   // Initialize "CustomMode" in STANDARD_SECURE_BOOT_MODE state.
    316   //
    317   CustomMode = STANDARD_SECURE_BOOT_MODE;
    318   Status = AuthServiceInternalUpdateVariable (
    319              EFI_CUSTOM_MODE_NAME,
    320              &gEfiCustomModeEnableGuid,
    321              &CustomMode,
    322              sizeof (UINT8),
    323              EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS
    324              );
    325   if (EFI_ERROR (Status)) {
    326     return Status;
    327   }
    328 
    329   DEBUG ((EFI_D_INFO, "Variable %s is %x\n", EFI_CUSTOM_MODE_NAME, CustomMode));
    330 
    331   //
    332   // Check "certdb" variable's existence.
    333   // If it doesn't exist, then create a new one with
    334   // EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS set.
    335   //
    336   Status = AuthServiceInternalFindVariable (
    337              EFI_CERT_DB_NAME,
    338              &gEfiCertDbGuid,
    339              (VOID **) &Data,
    340              &DataSize
    341              );
    342   if (EFI_ERROR (Status)) {
    343     VarAttr  = EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_RUNTIME_ACCESS | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS;
    344     ListSize = sizeof (UINT32);
    345     Status   = AuthServiceInternalUpdateVariable (
    346                  EFI_CERT_DB_NAME,
    347                  &gEfiCertDbGuid,
    348                  &ListSize,
    349                  sizeof (UINT32),
    350                  VarAttr
    351                  );
    352     if (EFI_ERROR (Status)) {
    353       return Status;
    354     }
    355   } else {
    356     //
    357     // Clean up Certs to make certDB & Time based auth variable consistent
    358     //
    359     Status = CleanCertsFromDb();
    360     if (EFI_ERROR (Status)) {
    361       DEBUG ((EFI_D_ERROR, "Clean up CertDB fail! Status %x\n", Status));
    362       return Status;
    363     }
    364   }
    365 
    366   //
    367   // Create "certdbv" variable with RT+BS+AT set.
    368   //
    369   VarAttr  = EFI_VARIABLE_RUNTIME_ACCESS | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS;
    370   ListSize = sizeof (UINT32);
    371   Status   = AuthServiceInternalUpdateVariable (
    372                EFI_CERT_DB_VOLATILE_NAME,
    373                &gEfiCertDbGuid,
    374                &ListSize,
    375                sizeof (UINT32),
    376                VarAttr
    377                );
    378   if (EFI_ERROR (Status)) {
    379     return Status;
    380   }
    381 
    382   //
    383   // Check "VendorKeysNv" variable's existence and create "VendorKeys" variable accordingly.
    384   //
    385   Status = AuthServiceInternalFindVariable (EFI_VENDOR_KEYS_NV_VARIABLE_NAME, &gEfiVendorKeysNvGuid, (VOID **) &Data, &DataSize);
    386   if (!EFI_ERROR (Status)) {
    387     mVendorKeyState = *(UINT8 *)Data;
    388   } else {
    389     //
    390     // "VendorKeysNv" not exist, initialize it in VENDOR_KEYS_VALID state.
    391     //
    392     mVendorKeyState = VENDOR_KEYS_VALID;
    393     Status = AuthServiceInternalUpdateVariable (
    394                EFI_VENDOR_KEYS_NV_VARIABLE_NAME,
    395                &gEfiVendorKeysNvGuid,
    396                &mVendorKeyState,
    397                sizeof (UINT8),
    398                EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS
    399                );
    400     if (EFI_ERROR (Status)) {
    401       return Status;
    402     }
    403   }
    404 
    405   //
    406   // Create "VendorKeys" variable with BS+RT attribute set.
    407   //
    408   Status = AuthServiceInternalUpdateVariable (
    409              EFI_VENDOR_KEYS_VARIABLE_NAME,
    410              &gEfiGlobalVariableGuid,
    411              &mVendorKeyState,
    412              sizeof (UINT8),
    413              EFI_VARIABLE_RUNTIME_ACCESS | EFI_VARIABLE_BOOTSERVICE_ACCESS
    414              );
    415   if (EFI_ERROR (Status)) {
    416     return Status;
    417   }
    418 
    419   DEBUG ((EFI_D_INFO, "Variable %s is %x\n", EFI_VENDOR_KEYS_VARIABLE_NAME, mVendorKeyState));
    420 
    421   AuthVarLibContextOut->StructVersion = AUTH_VAR_LIB_CONTEXT_OUT_STRUCT_VERSION;
    422   AuthVarLibContextOut->StructSize = sizeof (AUTH_VAR_LIB_CONTEXT_OUT);
    423   AuthVarLibContextOut->AuthVarEntry = mAuthVarEntry;
    424   AuthVarLibContextOut->AuthVarEntryCount = ARRAY_SIZE (mAuthVarEntry);
    425   mAuthVarAddressPointer[0] = (VOID **) &mPubKeyStore;
    426   mAuthVarAddressPointer[1] = (VOID **) &mCertDbStore;
    427   mAuthVarAddressPointer[2] = (VOID **) &mHashCtx;
    428   mAuthVarAddressPointer[3] = (VOID **) &mAuthVarLibContextIn;
    429   mAuthVarAddressPointer[4] = (VOID **) &(mAuthVarLibContextIn->FindVariable),
    430   mAuthVarAddressPointer[5] = (VOID **) &(mAuthVarLibContextIn->FindNextVariable),
    431   mAuthVarAddressPointer[6] = (VOID **) &(mAuthVarLibContextIn->UpdateVariable),
    432   mAuthVarAddressPointer[7] = (VOID **) &(mAuthVarLibContextIn->GetScratchBuffer),
    433   mAuthVarAddressPointer[8] = (VOID **) &(mAuthVarLibContextIn->CheckRemainingSpaceForConsistency),
    434   mAuthVarAddressPointer[9] = (VOID **) &(mAuthVarLibContextIn->AtRuntime),
    435   AuthVarLibContextOut->AddressPointer = mAuthVarAddressPointer;
    436   AuthVarLibContextOut->AddressPointerCount = ARRAY_SIZE (mAuthVarAddressPointer);
    437 
    438   return Status;
    439 }
    440 
    441 /**
    442   Process variable with EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS/EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS set.
    443 
    444   @param[in] VariableName           Name of the variable.
    445   @param[in] VendorGuid             Variable vendor GUID.
    446   @param[in] Data                   Data pointer.
    447   @param[in] DataSize               Size of Data.
    448   @param[in] Attributes             Attribute value of the variable.
    449 
    450   @retval EFI_SUCCESS               The firmware has successfully stored the variable and its data as
    451                                     defined by the Attributes.
    452   @retval EFI_INVALID_PARAMETER     Invalid parameter.
    453   @retval EFI_WRITE_PROTECTED       Variable is write-protected.
    454   @retval EFI_OUT_OF_RESOURCES      There is not enough resource.
    455   @retval EFI_SECURITY_VIOLATION    The variable is with EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS
    456                                     or EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACESS
    457                                     set, but the AuthInfo does NOT pass the validation
    458                                     check carried out by the firmware.
    459   @retval EFI_UNSUPPORTED           Unsupported to process authenticated variable.
    460 
    461 **/
    462 EFI_STATUS
    463 EFIAPI
    464 AuthVariableLibProcessVariable (
    465   IN CHAR16         *VariableName,
    466   IN EFI_GUID       *VendorGuid,
    467   IN VOID           *Data,
    468   IN UINTN          DataSize,
    469   IN UINT32         Attributes
    470   )
    471 {
    472   EFI_STATUS        Status;
    473 
    474   if (CompareGuid (VendorGuid, &gEfiGlobalVariableGuid) && (StrCmp (VariableName, EFI_PLATFORM_KEY_NAME) == 0)){
    475     Status = ProcessVarWithPk (VariableName, VendorGuid, Data, DataSize, Attributes, TRUE);
    476   } else if (CompareGuid (VendorGuid, &gEfiGlobalVariableGuid) && (StrCmp (VariableName, EFI_KEY_EXCHANGE_KEY_NAME) == 0)) {
    477     Status = ProcessVarWithPk (VariableName, VendorGuid, Data, DataSize, Attributes, FALSE);
    478   } else if (CompareGuid (VendorGuid, &gEfiImageSecurityDatabaseGuid) &&
    479              ((StrCmp (VariableName, EFI_IMAGE_SECURITY_DATABASE)  == 0) ||
    480               (StrCmp (VariableName, EFI_IMAGE_SECURITY_DATABASE1) == 0) ||
    481               (StrCmp (VariableName, EFI_IMAGE_SECURITY_DATABASE2) == 0)
    482              )) {
    483     Status = ProcessVarWithPk (VariableName, VendorGuid, Data, DataSize, Attributes, FALSE);
    484     if (EFI_ERROR (Status)) {
    485       Status = ProcessVarWithKek (VariableName, VendorGuid, Data, DataSize, Attributes);
    486     }
    487   } else {
    488     Status = ProcessVariable (VariableName, VendorGuid, Data, DataSize, Attributes);
    489   }
    490 
    491   return Status;
    492 }
    493