From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mga06.intel.com (mga06.intel.com [134.134.136.31]) by mx.groups.io with SMTP id smtpd.web09.1639.1581651208405429891 for ; Thu, 13 Feb 2020 19:33:28 -0800 Authentication-Results: mx.groups.io; dkim=missing; spf=pass (domain: intel.com, ip: 134.134.136.31, mailfrom: jiewen.yao@intel.com) X-Amp-Result: SKIPPED(no attachment in message) X-Amp-File-Uploaded: False Received: from fmsmga002.fm.intel.com ([10.253.24.26]) by orsmga104.jf.intel.com with ESMTP/TLS/DHE-RSA-AES256-GCM-SHA384; 13 Feb 2020 19:33:27 -0800 X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="5.70,439,1574150400"; d="scan'208";a="267407864" Received: from fmsmsx103.amr.corp.intel.com ([10.18.124.201]) by fmsmga002.fm.intel.com with ESMTP; 13 Feb 2020 19:33:27 -0800 Received: from fmsmsx162.amr.corp.intel.com (10.18.125.71) by FMSMSX103.amr.corp.intel.com (10.18.124.201) with Microsoft SMTP Server (TLS) id 14.3.439.0; Thu, 13 Feb 2020 19:33:27 -0800 Received: from shsmsx108.ccr.corp.intel.com (10.239.4.97) by fmsmsx162.amr.corp.intel.com (10.18.125.71) with Microsoft SMTP Server (TLS) id 14.3.439.0; Thu, 13 Feb 2020 19:33:26 -0800 Received: from shsmsx102.ccr.corp.intel.com ([169.254.2.126]) by SHSMSX108.ccr.corp.intel.com ([169.254.8.98]) with mapi id 14.03.0439.000; Fri, 14 Feb 2020 11:33:23 +0800 From: "Yao, Jiewen" To: "Wang, Jian J" , "devel@edk2.groups.io" CC: "Zhang, Chao B" , Laszlo Ersek Subject: Re: [PATCH 6/9] SecurityPkg/DxeImageVerificationLib: Differentiate error and search result in IsCertHashFoundInDatabase(CVE-2019-14575) Thread-Topic: [PATCH 6/9] SecurityPkg/DxeImageVerificationLib: Differentiate error and search result in IsCertHashFoundInDatabase(CVE-2019-14575) Thread-Index: AQHV3Ph7rsdlqfMv1UaUc05X+oTmrqgY64tAgABSGWCAAKiyAIAAKExQgAAGWdA= Date: Fri, 14 Feb 2020 03:33:22 +0000 Message-ID: <74D8A39837DF1E4DA445A8C0B3885C503F92F20E@shsmsx102.ccr.corp.intel.com> References: <20200206141933.356-1-jian.j.wang@intel.com> <20200206141933.356-7-jian.j.wang@intel.com> <74D8A39837DF1E4DA445A8C0B3885C503F92CC3F@shsmsx102.ccr.corp.intel.com> <74D8A39837DF1E4DA445A8C0B3885C503F92EC03@shsmsx102.ccr.corp.intel.com> In-Reply-To: Accept-Language: zh-CN, en-US X-MS-Has-Attach: X-MS-TNEF-Correlator: dlp-product: dlpe-windows dlp-version: 11.2.0.6 dlp-reaction: no-action x-originating-ip: [10.239.127.40] MIME-Version: 1.0 Return-Path: jiewen.yao@intel.com Content-Language: en-US Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: quoted-printable Fine. Thanks for the update. > -----Original Message----- > From: Wang, Jian J > Sent: Friday, February 14, 2020 11:32 AM > To: Yao, Jiewen ; devel@edk2.groups.io > Cc: Zhang, Chao B ; Laszlo Ersek > > Subject: RE: [PATCH 6/9] SecurityPkg/DxeImageVerificationLib: Differentia= te > error and search result in IsCertHashFoundInDatabase(CVE-2019-14575) >=20 > Jiewen, >=20 > > -----Original Message----- > > From: Yao, Jiewen > > Sent: Friday, February 14, 2020 8:54 AM > > To: Wang, Jian J ; devel@edk2.groups.io > > Cc: Zhang, Chao B ; Laszlo Ersek > > > > Subject: RE: [PATCH 6/9] SecurityPkg/DxeImageVerificationLib: Different= iate > > error and search result in IsCertHashFoundInDatabase(CVE-2019-14575) > > > > 1) I prefer we do a little bit simple clean up in this series. Just nam= e change. > > Maybe as patch-10. > > >=20 > Sure. I'll add it in v2. >=20 > > 2) When PassTimestampCheck() need to be called? Only Dbx is found? Or e= ven > > the Dbx is broken? > > > > I prefer we need use a consistent rule. > > > > Case 1 in original patch: > > if (!EFI_ERROR (Status) && PassTimestampCheck (AuthData, AuthDataSize, > > > > &RevocationTime)) { > > > > Case 2 in your email: > > > VerifyStatus =3D PassTimestampCheck (AuthData, AuthData= Size, > > > &RevocationTime); > > > if (!VerifyStatus) { > > > > It seems they are not consistent... > > >=20 > Just talked to Chao privately. He mentioned that RevocationTime might not > be valid if Status !=3D EFI_SUCCESS. So we should only call PassTimestamp= Check() > when Status =3D=3D EFI_SUCCESS and IsFound =3D=3D TRUE. >=20 > Here's my new proposal (for case 1, case 2 is similar). >=20 > Status =3D IsCertHashFoundInDatabase (...); > if (EFI_ERROR(Status)) { > // > // Error in searching dbx. Consider it as 'found'. RevocationTime m= ight > // not be valid in such situation. > // > IsForbidden =3D TRUE; > } else if (IsFound) { > // > // Found Cert in dbx successfully. Check the timestamp signature an= d > // signing time to determine if the image can be trusted. > // > if (PassTimestampCheck (AuthData, AuthDataSize, &RevocationTime)) { > IsForbidden =3D FALSE; > // > // Pass DBT check. Continue to check other certs in image signer'= s cert list > against DBX, DBT > // > continue; > } else { > IsForbidden =3D TRUE; > DEBUG((DEBUG_INFO, "DxeImageVerificationLib: Image is signed but > signature failed the timestamp check.\n")); > goto Done; > } > } >=20 > If no objection, I'll include it in v2. >=20 > Regards, > Jian > > Thank you > > Yao Jiewen > > > > > > > -----Original Message----- > > > From: Wang, Jian J > > > Sent: Thursday, February 13, 2020 11:08 PM > > > To: Yao, Jiewen ; devel@edk2.groups.io > > > Cc: Zhang, Chao B ; Laszlo Ersek > > > > > > Subject: RE: [PATCH 6/9] SecurityPkg/DxeImageVerificationLib: Differe= ntiate > > > error and search result in IsCertHashFoundInDatabase(CVE-2019-14575) > > > > > > Jiewen, > > > > > > Thanks for the comments. > > > > > > 1) You're right. IsCertHashFoundInDatabase is quite general and cause > > > confusions between > > > db and dbx situation. Since it's not newly introduced in this patch s= eries, do > you > > > think it's ok > > > to fix it in separate patch series later? Or do you prefer fix it in = this patch > series? > > > I'm ok with > > > both. > > > > > > 2) I checked both code again. I think you're right. Both callings are= for dbx, > any > > > error Status > > > should be taken as IsFound(=3D=3DTRUE). What about following change f= or the > > > second case? > > > Please help double check if any logic hole here. > > > > > > Status =3D IsCertHashFoundInDatabase (...); > > > if (EFI_ERROR (Status) || IsFound) { > > > // > > > // Check the timestamp signature and signing time to de= termine if > the > > > RootCert can be trusted. > > > // > > > VerifyStatus =3D PassTimestampCheck (AuthData, AuthData= Size, > > > &RevocationTime); > > > if (!VerifyStatus) { > > > DEBUG ((...)); > > > } > > > } else { > > > VerifyStatus =3D TRUE; > > > } > > > > > > goto Done; > > > > > > Regards, > > > Jian > > > > > > > -----Original Message----- > > > > From: Yao, Jiewen > > > > Sent: Thursday, February 13, 2020 6:11 PM > > > > To: Wang, Jian J ; devel@edk2.groups.io > > > > Cc: Zhang, Chao B ; Laszlo Ersek > > > > > > > > Subject: RE: [PATCH 6/9] SecurityPkg/DxeImageVerificationLib: > Differentiate > > > > error and search result in IsCertHashFoundInDatabase(CVE-2019-14575= ) > > > > > > > > Comment below: > > > > > > > > 1) I think the function name - IsCertHashFoundInDatabase() and the > > > > implementation { DbxList =3D SignatureList; DbxSize =3D Signat= ureListSize; } > > bring > > > > some confusion to me. > > > > > > > > If this is a *generic* database search function, I recommend we use= a > > generic > > > > name - not use DbxList/DbxSize in the function implementation. > > > > > > > > If the input SignatureList of the function must be *Dbx*, I recomme= nd we > > use > > > > IsCertHashFoundInDbx() as the function name. > > > > > > > > Either change is OK for me. > > > > > > > > 2) Now we have to check 2 output: Status and IsFound in > > > > IsCertHashFoundInDatabase(). > > > > > > > > I am struggling to understand the different between 2 different way= s of > error > > > > handling: > > > > > > > > =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D > > > > Status =3D IsCertHashFoundInDatabase (Cert, CertSize, > > (EFI_SIGNATURE_LIST > > > > *)Data, DataSize, &RevocationTime, &IsFound); > > > > if (EFI_ERROR (Status) || IsFound) { > > > > // > > > > // Check the timestamp signature and signing time to determin= e if the > > > image > > > > can be trusted. > > > > // > > > > IsForbidden =3D TRUE; > > > > if (!EFI_ERROR (Status) && PassTimestampCheck (AuthData, > > AuthDataSize, > > > > &RevocationTime)) { > > > > IsForbidden =3D FALSE; > > > > =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D > > > > > > > > and > > > > > > > > =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D > > > > VerifyStatus =3D FALSE; > > > > // > > > > // Here We still need to check if this RootCert's Hash = is revoked > > > > // > > > > Status =3D IsCertHashFoundInDatabase (RootCert, RootCer= tSize, > > > > (EFI_SIGNATURE_LIST *)DbxData, DbxDataSize, &RevocationTime, > > &IsFound); > > > > if (EFI_ERROR (Status)) { > > > > goto Done; > > > > } > > > > > > > > if (!IsFound) { > > > > VerifyStatus =3D TRUE; > > > > goto Done; > > > > } > > > > > > > > // > > > > // Check the timestamp signature and signing time to de= termine if > the > > > > RootCert can be trusted. > > > > // > > > > VerifyStatus =3D PassTimestampCheck (AuthData, AuthData= Size, > > > > &RevocationTime); > > > > if (!VerifyStatus) { > > > > =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D > > > > > > > > I *believe* the logic behind is same. If so, we can use a consisten= t way to > > > check > > > > the 2 output and decide if PassTimestampCheck() is required. > > > > > > > > Or, can we create a one single function to perform such check for b= oth > > > > IsCertHashFoundInDatabase() and PassTimestampCheck() ? > > > > > > > > If I am wrong, there is *difference* between them. Then I think we = need > > much > > > > better description to help reviewer to catch the difference. > > > > > > > > Thank you > > > > Yao Jiewen > > > > > > > > > > > > > -----Original Message----- > > > > > From: Wang, Jian J > > > > > Sent: Thursday, February 6, 2020 10:20 PM > > > > > To: devel@edk2.groups.io > > > > > Cc: Yao, Jiewen ; Zhang, Chao B > > > > > ; Laszlo Ersek > > > > > Subject: [PATCH 6/9] SecurityPkg/DxeImageVerificationLib: Differe= ntiate > > > error > > > > > and search result in IsCertHashFoundInDatabase(CVE-2019-14575) > > > > > > > > > > REF: https://bugzilla.tianocore.org/show_bug.cgi?id=3D1608 > > > > > > > > > > To avoid false-negative issue in check hash against dbx, both err= or > > > > > condition (as return value) and check result (as out parameter) o= f > > > > > IsCertHashFoundInDatabase() are added. So the caller of this func= tion > > > > > will know exactly if a failure is caused by a black list hit or > > > > > other error happening, and enforce a more secure operation to pre= vent > > > > > secure boot from being bypassed. For a white list check (db), the= re's > > > > > no such necessity. > > > > > > > > > > Cc: Jiewen Yao > > > > > Cc: Chao Zhang > > > > > Signed-off-by: Jian J Wang > > > > > Signed-off-by: Laszlo Ersek > > > > > --- > > > > > .../DxeImageVerificationLib.c | 68 +++++++++++--= ------ > > > > > 1 file changed, 41 insertions(+), 27 deletions(-) > > > > > > > > > > diff --git > > > > > > a/SecurityPkg/Library/DxeImageVerificationLib/DxeImageVerificationLib.c > > > > > > b/SecurityPkg/Library/DxeImageVerificationLib/DxeImageVerificationLib.c > > > > > index 8739d1fa29..a5dfee0f8e 100644 > > > > > --- > > > a/SecurityPkg/Library/DxeImageVerificationLib/DxeImageVerificationLib= .c > > > > > +++ > > > b/SecurityPkg/Library/DxeImageVerificationLib/DxeImageVerificationLib= .c > > > > > @@ -822,22 +822,23 @@ AddImageExeInfo ( > > > > > @param[in] SignatureList Pointer to the Signature List in= forbidden > > > > database. > > > > > > > > > > @param[in] SignatureListSize Size of Signature List. > > > > > > > > > > @param[out] RevocationTime Return the time that the certifi= cate > was > > > > > revoked. > > > > > > > > > > + @param[out] IsFound Search result. Only valid if EFI= _SUCCESS > > > > returned. > > > > > > > > > > > > > > > > > > > > - @return TRUE The certificate hash is found in the forbidden = database. > > > > > > > > > > - @return FALSE The certificate hash is not found in the forbid= den > > database. > > > > > > > > > > + @retval EFI_SUCCESS Finished the search without any = error. > > > > > > > > > > + @retval Others Error occurred in the search of = database. > > > > > > > > > > > > > > > > > > > > **/ > > > > > > > > > > -BOOLEAN > > > > > > > > > > +EFI_STATUS > > > > > > > > > > IsCertHashFoundInDatabase ( > > > > > > > > > > IN UINT8 *Certificate, > > > > > > > > > > IN UINTN CertSize, > > > > > > > > > > IN EFI_SIGNATURE_LIST *SignatureList, > > > > > > > > > > IN UINTN SignatureListSize, > > > > > > > > > > - OUT EFI_TIME *RevocationTime > > > > > > > > > > + OUT EFI_TIME *RevocationTime, > > > > > > > > > > + OUT BOOLEAN *IsFound > > > > > > > > > > ) > > > > > > > > > > { > > > > > > > > > > - BOOLEAN IsFound; > > > > > > > > > > - BOOLEAN Status; > > > > > > > > > > + EFI_STATUS Status; > > > > > > > > > > EFI_SIGNATURE_LIST *DbxList; > > > > > > > > > > UINTN DbxSize; > > > > > > > > > > EFI_SIGNATURE_DATA *CertHash; > > > > > > > > > > @@ -851,21 +852,22 @@ IsCertHashFoundInDatabase ( > > > > > UINT8 *TBSCert; > > > > > > > > > > UINTN TBSCertSize; > > > > > > > > > > > > > > > > > > > > - IsFound =3D FALSE; > > > > > > > > > > + Status =3D EFI_ABORTED; > > > > > > > > > > + *IsFound =3D FALSE; > > > > > > > > > > DbxList =3D SignatureList; > > > > > > > > > > DbxSize =3D SignatureListSize; > > > > > > > > > > HashCtx =3D NULL; > > > > > > > > > > HashAlg =3D HASHALG_MAX; > > > > > > > > > > > > > > > > > > > > if ((RevocationTime =3D=3D NULL) || (DbxList =3D=3D NULL)) { > > > > > > > > > > - return FALSE; > > > > > > > > > > + return EFI_INVALID_PARAMETER; > > > > > > > > > > } > > > > > > > > > > > > > > > > > > > > // > > > > > > > > > > // Retrieve the TBSCertificate from the X.509 Certificate. > > > > > > > > > > // > > > > > > > > > > if (!X509GetTBSCert (Certificate, CertSize, &TBSCert, &TBSCert= Size)) { > > > > > > > > > > - return FALSE; > > > > > > > > > > + return Status; > > > > > > > > > > } > > > > > > > > > > > > > > > > > > > > while ((DbxSize > 0) && (SignatureListSize >=3D DbxList->Signa= tureListSize)) > { > > > > > > > > > > @@ -895,16 +897,13 @@ IsCertHashFoundInDatabase ( > > > > > if (HashCtx =3D=3D NULL) { > > > > > > > > > > goto Done; > > > > > > > > > > } > > > > > > > > > > - Status =3D mHash[HashAlg].HashInit (HashCtx); > > > > > > > > > > - if (!Status) { > > > > > > > > > > + if (!mHash[HashAlg].HashInit (HashCtx)) { > > > > > > > > > > goto Done; > > > > > > > > > > } > > > > > > > > > > - Status =3D mHash[HashAlg].HashUpdate (HashCtx, TBSCert, TBSC= ertSize); > > > > > > > > > > - if (!Status) { > > > > > > > > > > + if (!mHash[HashAlg].HashUpdate (HashCtx, TBSCert, TBSCertSiz= e)) { > > > > > > > > > > goto Done; > > > > > > > > > > } > > > > > > > > > > - Status =3D mHash[HashAlg].HashFinal (HashCtx, CertDigest); > > > > > > > > > > - if (!Status) { > > > > > > > > > > + if (!mHash[HashAlg].HashFinal (HashCtx, CertDigest)) { > > > > > > > > > > goto Done; > > > > > > > > > > } > > > > > > > > > > > > > > > > > > > > @@ -923,7 +922,8 @@ IsCertHashFoundInDatabase ( > > > > > // > > > > > > > > > > // Hash of Certificate is found in forbidden database. > > > > > > > > > > // > > > > > > > > > > - IsFound =3D TRUE; > > > > > > > > > > + Status =3D EFI_SUCCESS; > > > > > > > > > > + *IsFound =3D TRUE; > > > > > > > > > > > > > > > > > > > > // > > > > > > > > > > // Return the revocation time. > > > > > > > > > > @@ -938,12 +938,14 @@ IsCertHashFoundInDatabase ( > > > > > DbxList =3D (EFI_SIGNATURE_LIST *) ((UINT8 *) DbxList + Dbx= List- > > > > > >SignatureListSize); > > > > > > > > > > } > > > > > > > > > > > > > > > > > > > > + Status =3D EFI_SUCCESS; > > > > > > > > > > + > > > > > > > > > > Done: > > > > > > > > > > if (HashCtx !=3D NULL) { > > > > > > > > > > FreePool (HashCtx); > > > > > > > > > > } > > > > > > > > > > > > > > > > > > > > - return IsFound; > > > > > > > > > > + return Status; > > > > > > > > > > } > > > > > > > > > > > > > > > > > > > > /** > > > > > > > > > > @@ -1216,6 +1218,7 @@ IsForbiddenByDbx ( > > > > > { > > > > > > > > > > EFI_STATUS Status; > > > > > > > > > > BOOLEAN IsForbidden; > > > > > > > > > > + BOOLEAN IsFound; > > > > > > > > > > UINT8 *Data; > > > > > > > > > > UINTN DataSize; > > > > > > > > > > EFI_SIGNATURE_LIST *CertList; > > > > > > > > > > @@ -1344,12 +1347,13 @@ IsForbiddenByDbx ( > > > > > // > > > > > > > > > > CertPtr =3D CertPtr + sizeof (UINT32) + CertSize; > > > > > > > > > > > > > > > > > > > > - if (IsCertHashFoundInDatabase (Cert, CertSize, (EFI_SIGNATUR= E_LIST > > > > *)Data, > > > > > DataSize, &RevocationTime)) { > > > > > > > > > > + Status =3D IsCertHashFoundInDatabase (Cert, CertSize, > > > (EFI_SIGNATURE_LIST > > > > > *)Data, DataSize, &RevocationTime, &IsFound); > > > > > > > > > > + if (EFI_ERROR (Status) || IsFound) { > > > > > > > > > > // > > > > > > > > > > // Check the timestamp signature and signing time to deter= mine if the > > > > image > > > > > can be trusted. > > > > > > > > > > // > > > > > > > > > > IsForbidden =3D TRUE; > > > > > > > > > > - if (PassTimestampCheck (AuthData, AuthDataSize, &Revocatio= nTime)) > > { > > > > > > > > > > + if (!EFI_ERROR (Status) && PassTimestampCheck (AuthData, > > > AuthDataSize, > > > > > &RevocationTime)) { > > > > > > > > > > IsForbidden =3D FALSE; > > > > > > > > > > // > > > > > > > > > > // Pass DBT check. Continue to check other certs in imag= e signer's > cert > > > list > > > > > against DBX, DBT > > > > > > > > > > @@ -1392,6 +1396,7 @@ IsAllowedByDb ( > > > > > { > > > > > > > > > > EFI_STATUS Status; > > > > > > > > > > BOOLEAN VerifyStatus; > > > > > > > > > > + BOOLEAN IsFound; > > > > > > > > > > EFI_SIGNATURE_LIST *CertList; > > > > > > > > > > EFI_SIGNATURE_DATA *CertData; > > > > > > > > > > UINTN DataSize; > > > > > > > > > > @@ -1495,17 +1500,26 @@ IsAllowedByDb ( > > > > > // The image is signed and its signature is found in '= db'. > > > > > > > > > > // > > > > > > > > > > if (DbxData !=3D NULL) { > > > > > > > > > > + VerifyStatus =3D FALSE; > > > > > > > > > > // > > > > > > > > > > // Here We still need to check if this RootCert's Ha= sh is revoked > > > > > > > > > > // > > > > > > > > > > - if (IsCertHashFoundInDatabase (RootCert, RootCertSiz= e, > > > > > (EFI_SIGNATURE_LIST *)DbxData, DbxDataSize, &RevocationTime)) { > > > > > > > > > > - // > > > > > > > > > > - // Check the timestamp signature and signing time = to determine > if > > > the > > > > > RootCert can be trusted. > > > > > > > > > > - // > > > > > > > > > > - VerifyStatus =3D PassTimestampCheck (AuthData, Aut= hDataSize, > > > > > &RevocationTime); > > > > > > > > > > - if (!VerifyStatus) { > > > > > > > > > > - DEBUG ((DEBUG_INFO, "DxeImageVerificationLib: Im= age is > > signed > > > > and > > > > > signature is accepted by DB, but its root cert failed the timesta= mp > > check.\n")); > > > > > > > > > > - } > > > > > > > > > > + Status =3D IsCertHashFoundInDatabase (RootCert, Root= CertSize, > > > > > (EFI_SIGNATURE_LIST *)DbxData, DbxDataSize, &RevocationTime, > > > &IsFound); > > > > > > > > > > + if (EFI_ERROR (Status)) { > > > > > > > > > > + goto Done; > > > > > > > > > > + } > > > > > > > > > > + > > > > > > > > > > + if (!IsFound) { > > > > > > > > > > + VerifyStatus =3D TRUE; > > > > > > > > > > + goto Done; > > > > > > > > > > + } > > > > > > > > > > + > > > > > > > > > > + // > > > > > > > > > > + // Check the timestamp signature and signing time to= determine > if > > > the > > > > > RootCert can be trusted. > > > > > > > > > > + // > > > > > > > > > > + VerifyStatus =3D PassTimestampCheck (AuthData, AuthD= ataSize, > > > > > &RevocationTime); > > > > > > > > > > + if (!VerifyStatus) { > > > > > > > > > > + DEBUG ((DEBUG_INFO, "DxeImageVerificationLib: Imag= e is > signed > > > > and > > > > > signature is accepted by DB, but its root cert failed the timesta= mp > > check.\n")); > > > > > > > > > > } > > > > > > > > > > } > > > > > > > > > > > > > > > > > > > > -- > > > > > 2.24.0.windows.2