From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mga01.intel.com (mga01.intel.com [192.55.52.88]) by mx.groups.io with SMTP id smtpd.web12.4452.1578621489873102039 for ; Thu, 09 Jan 2020 17:58:09 -0800 Authentication-Results: mx.groups.io; dkim=missing; spf=pass (domain: intel.com, ip: 192.55.52.88, mailfrom: aaron.li@intel.com) X-Amp-Result: SKIPPED(no attachment in message) X-Amp-File-Uploaded: False Received: from fmsmga001.fm.intel.com ([10.253.24.23]) by fmsmga101.fm.intel.com with ESMTP/TLS/DHE-RSA-AES256-GCM-SHA384; 09 Jan 2020 17:58:09 -0800 X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="5.69,414,1571727600"; d="scan'208";a="229624932" Received: from songpeng.ccr.corp.intel.com ([10.239.158.44]) by fmsmga001.fm.intel.com with ESMTP; 09 Jan 2020 17:58:08 -0800 From: aaron.li@intel.com To: devel@edk2.groups.io Cc: Bob Feng , Liming Gao Subject: [PATCH v1] BaseTools/Capsule: Add capsule dependency support Date: Fri, 10 Jan 2020 09:57:35 +0800 Message-Id: <20200110015735.28076-1-aaron.li@intel.com> X-Mailer: git-send-email 2.18.0.windows.1 REF: https://bugzilla.tianocore.org/show_bug.cgi?id=2412 Capsule generate tool support encode capsule dependencies through '-j' command with a JSON file. To enable dependency feature, "Dependencies" field for each payload in JSON file is required. The value of "Dependencies" field is C style infix notation expression. For example: "Dependencies":"72E2945A-00DA-448E-9AA7-075AD840F9D4 > 0x00000001" The relation of Dependency Expression Opcode in UEFI2.8 chap 23.2 and infix notation expression value is as follows: +-----------------------------+--------------------------+ | OPCODE | INFIX EXPRESSION VALUE | +-----------------------------+--------------------------+ | 0x00 (PUSH_GUID) | {GUID} | | 0x01 (PUSH_VERSION) | {UINT32} | | 0x02 (DECLEAR_VERSION_NAME} | DECLEAR "{VERSION_NAME}" | | 0x03 (AND) | && | | 0x04 (OR) | || | | 0x05 (NOT) | ~ | | 0x06 (TRUE) | TRUE | | 0x07 (FALSE) | FALSE | | 0x08 (EQ) | == | | 0x09 (GT) | > | | 0x0A (GTE) | >= | | 0x0B (LT) | < | | 0x0C (LTE) | <= | +-----------------------------+--------------------------+ Cc: Bob Feng Cc: Liming Gao Signed-off-by: Aaron Li --- BaseTools/Source/Python/Capsule/GenerateCapsule.py | 62 ++- BaseTools/Source/Python/Common/Uefi/Capsule/CapsuleDependency.py | 409 ++++++++++++++++++++ 2 files changed, 464 insertions(+), 7 deletions(-) diff --git a/BaseTools/Source/Python/Capsule/GenerateCapsule.py b/BaseTools/Source/Python/Capsule/GenerateCapsule.py index 6838beb682..a8de988253 100644 --- a/BaseTools/Source/Python/Capsule/GenerateCapsule.py +++ b/BaseTools/Source/Python/Capsule/GenerateCapsule.py @@ -31,6 +31,7 @@ import json from Common.Uefi.Capsule.UefiCapsuleHeader import UefiCapsuleHeaderClass from Common.Uefi.Capsule.FmpCapsuleHeader import FmpCapsuleHeaderClass from Common.Uefi.Capsule.FmpAuthHeader import FmpAuthHeaderClass +from Common.Uefi.Capsule.CapsuleDependency import CapsuleDependencyClass from Common.Edk2.Capsule.FmpPayloadHeader import FmpPayloadHeaderClass # @@ -306,6 +307,7 @@ if __name__ == '__main__': OpenSslOtherPublicCertFile = ConvertJsonValue (Config, 'OpenSslOtherPublicCertFile', os.path.expandvars, Required = False, Default = None, Open = True) OpenSslTrustedPublicCertFile = ConvertJsonValue (Config, 'OpenSslTrustedPublicCertFile', os.path.expandvars, Required = False, Default = None, Open = True) SigningToolPath = ConvertJsonValue (Config, 'SigningToolPath', os.path.expandvars, Required = False, Default = None) + DepexExp = ConvertJsonValue (Config, 'Dependencies', str, Required = False, Default = None) # # Read binary input file @@ -330,7 +332,8 @@ if __name__ == '__main__': OpenSslSignerPrivateCertFile, OpenSslOtherPublicCertFile, OpenSslTrustedPublicCertFile, - SigningToolPath + SigningToolPath, + DepexExp )) def GenerateOutputJson (PayloadJsonDescriptorList): @@ -348,7 +351,8 @@ if __name__ == '__main__': "OpenSslSignerPrivateCertFile": str(PayloadDescriptor.OpenSslSignerPrivateCertFile), "OpenSslOtherPublicCertFile": str(PayloadDescriptor.OpenSslOtherPublicCertFile), "OpenSslTrustedPublicCertFile": str(PayloadDescriptor.OpenSslTrustedPublicCertFile), - "SigningToolPath": str(PayloadDescriptor.SigningToolPath) + "SigningToolPath": str(PayloadDescriptor.SigningToolPath), + "Dependencies" : str(PayloadDescriptor.DepexExp) }for PayloadDescriptor in PayloadJsonDescriptorList ] } @@ -424,7 +428,8 @@ if __name__ == '__main__': OpenSslSignerPrivateCertFile = None, OpenSslOtherPublicCertFile = None, OpenSslTrustedPublicCertFile = None, - SigningToolPath = None + SigningToolPath = None, + DepexExp = None ): self.Payload = Payload self.Guid = Guid @@ -438,6 +443,7 @@ if __name__ == '__main__': self.OpenSslOtherPublicCertFile = OpenSslOtherPublicCertFile self.OpenSslTrustedPublicCertFile = OpenSslTrustedPublicCertFile self.SigningToolPath = SigningToolPath + self.DepexExp = DepexExp self.UseSignTool = self.SignToolPfxFile is not None self.UseOpenSsl = (self.OpenSslSignerPrivateCertFile is not None and @@ -446,6 +452,7 @@ if __name__ == '__main__': self.AnyOpenSsl = (self.OpenSslSignerPrivateCertFile is not None or self.OpenSslOtherPublicCertFile is not None or self.OpenSslTrustedPublicCertFile is not None) + self.UseDependency = self.DepexExp is not None def Validate(self, args): if self.UseSignTool and self.AnyOpenSsl: @@ -544,7 +551,8 @@ if __name__ == '__main__': args.OpenSslSignerPrivateCertFile, args.OpenSslOtherPublicCertFile, args.OpenSslTrustedPublicCertFile, - args.SigningToolPath + args.SigningToolPath, + None )) for SinglePayloadDescriptor in PayloadDescriptorList: try: @@ -564,6 +572,12 @@ if __name__ == '__main__': except: print ('GenerateCapsule: error: can not encode FMP Payload Header') sys.exit (1) + if SinglePayloadDescriptor.UseDependency: + CapsuleDependency.Payload = Result + CapsuleDependency.DepexExp = SinglePayloadDescriptor.DepexExp + Result = CapsuleDependency.Encode () + if args.Verbose: + CapsuleDependency.DumpInfo () if SinglePayloadDescriptor.UseOpenSsl or SinglePayloadDescriptor.UseSignTool: # # Sign image with 64-bit MonotonicCount appended to end of image @@ -657,7 +671,8 @@ if __name__ == '__main__': args.OpenSslSignerPrivateCertFile, args.OpenSslOtherPublicCertFile, args.OpenSslTrustedPublicCertFile, - args.SigningToolPath + args.SigningToolPath, + None )) # # Perform additional verification on payload descriptors @@ -700,7 +715,8 @@ if __name__ == '__main__': PayloadDescriptorList[Index].OpenSslSignerPrivateCertFile, PayloadDescriptorList[Index].OpenSslOtherPublicCertFile, PayloadDescriptorList[Index].OpenSslTrustedPublicCertFile, - PayloadDescriptorList[Index].SigningToolPath + PayloadDescriptorList[Index].SigningToolPath, + None )) else: PayloadDescriptorList[0].Payload = FmpCapsuleHeader.GetFmpCapsuleImageHeader (0).Payload @@ -708,6 +724,7 @@ if __name__ == '__main__': if Index > 0: PayloadDecodeFile = FmpCapsuleHeader.GetFmpCapsuleImageHeader (Index).Payload PayloadDescriptorList.append (PayloadDescriptor (PayloadDecodeFile, + None, None, None, None, @@ -736,7 +753,8 @@ if __name__ == '__main__': PayloadDescriptorList[Index].OpenSslSignerPrivateCertFile, PayloadDescriptorList[Index].OpenSslOtherPublicCertFile, PayloadDescriptorList[Index].OpenSslTrustedPublicCertFile, - PayloadDescriptorList[Index].SigningToolPath + PayloadDescriptorList[Index].SigningToolPath, + None )) JsonIndex = 0 for SinglePayloadDescriptor in PayloadDescriptorList: @@ -782,6 +800,23 @@ if __name__ == '__main__': if args.Verbose: print ('--------') print ('No EFI_FIRMWARE_IMAGE_AUTHENTICATION') + + PayloadSignature = struct.unpack (' +# SPDX-License-Identifier: BSD-2-Clause-Patent +# +import struct +import json +import sys +import uuid +import re + +''' +CapsuleDependency +''' + +class OpConvert (object): + def __init__ (self): + # Opcode: (OperandSize, PackSize, PackFmt, EncodeConvert, DecodeConvert) + self._DepexOperations = {0x00: (16, 16, 's', self.Str2Guid, self.Guid2Str), + 0x01: (4, 1, 'I', self.Str2Uint, self.Uint2Str), + 0x02: (1, 0, 's', self.Str2Utf8, self.Byte2Str), + } + + def Str2Uint (self, Data): + try: + Value = int (Data, 16) + except: + Message = '{Data} is not a valid integer value.'.format (Data = Data) + raise ValueError (Message) + if Value < 0 or Value > 0xFFFFFFFF: + Message = '{Data} is not an UINT32.'.format (Data = Data) + raise ValueError (Message) + return Value + + def Uint2Str (self, Data): + if Data < 0 or Data > 0xFFFFFFFF: + Message = '{Data} is not an UINT32.'.format (Data = Data) + raise ValueError (Message) + return "0x{Data:08x}".format (Data = Data) + + def Str2Guid (self, Data): + try: + Guid = uuid.UUID (Data) + except: + Message = '{Data} is not a valid registry format GUID value.'.format (Data = Data) + raise ValueError (Message) + return Guid.bytes_le + + def Guid2Str (self, Data): + try: + Guid = uuid.UUID (bytes_le = Data) + except: + Message = '{Data} is not a valid binary format GUID value.'.format (Data = Data) + raise ValueError (Message) + return str (Guid).upper () + + def Str2Utf8 (self, Data): + if isinstance (Data, str): + return Data.encode ('utf-8') + else: + Message = '{Data} is not a valid string.'.format (Data = Data) + raise ValueError (Message) + + def Byte2Str (self, Data): + if isinstance (Data, bytes): + if Data[-1:] == b'\x00': + return str (Data[:-1], 'utf-8') + else: + return str (Data, 'utf-8') + else: + Message = '{Data} is not a valid binary string.'.format (Data = Data) + raise ValueError (Message) + + def OpEncode (self, Opcode, Operand = None): + BinTemp = struct.pack ('': [4, 0x09, 2], + '>=': [4, 0x0A, 2], + '<': [4, 0x0B, 2], + '<=': [4, 0x0C, 2], + } + + def __init__ (self): + self.Payload = b'' + self._DepexExp = None + self._DepexList = [] + self._DepexDump = [] + self.Depex = b'' + self._Valid = False + self._DepexSize = 0 + self._opReferenceReverse = {v[1] : k for k, v in self._opReference.items ()} + self.OpConverter = OpConvert () + + @property + def DepexExp (self): + return self._DepexExp + + @DepexExp.setter + def DepexExp (self, DepexExp = ''): + if isinstance (DepexExp, str): + DepexExp = re.sub (r'\n',r' ',DepexExp) + DepexExp = re.sub (r'\(',r' ( ',DepexExp) + DepexExp = re.sub (r'\)',r' ) ',DepexExp) + DepexExp = re.sub (r'~',r' ~ ',DepexExp) + self._DepexList = re.findall(r"[^\s\"\']+|\"[^\"]*\"|\'[^\']*\'",DepexExp) + self._DepexExp = " ".join(self._DepexList) + + else: + Msg = 'Input Depex Expression is not valid string.' + raise ValueError (Msg) + + def IsValidOperator (self, op): + return op in self._opReference.keys () + + def IsValidUnaryOperator (self, op): + return op in self._opReference.keys () and self._opReference[op][2] == 1 + + def IsValidBinocularOperator (self, op): + return op in self._opReference.keys () and self._opReference[op][2] == 2 + + def IsValidGuid (self, operand): + try: + uuid.UUID (operand) + except: + return False + return True + + def IsValidVersion (self, operand): + try: + Value = int (operand, 16) + if Value < 0 or Value > 0xFFFFFFFF: + return False + except: + return False + return True + + def IsValidBoolean (self, operand): + try: + return operand.upper () in ['TRUE', 'FALSE'] + except: + return False + + def IsValidOperand (self, operand): + return self.IsValidVersion (operand) or self.IsValidGuid (operand) or self.IsValidBoolean (operand) + + def IsValidString (self, operand): + return operand[0] == "\"" and operand[-1] == "\"" and len(operand) >= 2 + + # Check if priority of current operater is greater than pervious op + def PriorityNotGreater (self, prevOp, currOp): + return self._opReference[currOp][0] <= self._opReference[prevOp][0] + + def ValidateDepex (self): + OpList = self._DepexList + + i = 0 + while i < len (OpList): + Op = OpList[i] + + if Op == 'DECLARE': + i += 1 + if i >= len (OpList): + Msg = 'No more Operand after {Op}.'.format (Op = OpList[i-1]) + raise IndexError (Msg) + # Check valid string + if not self.IsValidString(OpList[i]): + Msg = '{Operand} after {Op} is not a valid expression input.'.format (Operand = OpList[i], Op = OpList[i-1]) + raise ValueError (Msg) + + elif Op == '(': + # Expression cannot end with ( + if i == len (OpList) - 1: + Msg = 'Expression cannot end with \'(\'' + raise ValueError (Msg) + # The previous op after '(' cannot be a binocular operator + if self.IsValidBinocularOperator (OpList[i+1]) : + Msg = '{Op} after \'(\' is not a valid expression input.'.format (Op = OpList[i+1]) + raise ValueError (Msg) + + elif Op == ')': + # Expression cannot start with ) + if i == 0: + Msg = 'Expression cannot start with \')\'' + raise ValueError (Msg) + # The previous op before ')' cannot be an operator + if self.IsValidOperator (OpList[i-1]): + Msg = '{Op} before \')\' is not a valid expression input.'.format (Op = OpList[i-1]) + raise ValueError (Msg) + # The next op after ')' cannot be operand or unary operator + if (i + 1) < len (OpList) and (self.IsValidOperand (OpList[i+1]) or self.IsValidUnaryOperator (OpList[i+1])): + Msg = '{Op} after \')\' is not a valid expression input.'.format (Op = OpList[i+1]) + raise ValueError (Msg) + + elif self.IsValidOperand (Op): + # The next expression of operand cannot be operand or unary operator + if (i + 1) < len (OpList) and (self.IsValidOperand (OpList[i+1]) or self.IsValidUnaryOperator (OpList[i+1])): + Msg = '{Op} after {PrevOp} is not a valid expression input.'.format (Op = OpList[i+1], PrevOp = Op) + raise ValueError (Msg) + + elif self.IsValidOperator (Op): + # The next op of operator cannot binocular operator + if (i + 1) < len (OpList) and self.IsValidBinocularOperator (OpList[i+1]): + Msg = '{Op} after {PrevOp} is not a valid expression input.'.format (Op = OpList[i+1], PrevOp = Op) + raise ValueError (Msg) + # The first op can not be binocular operator + if i == 0 and self.IsValidBinocularOperator (Op): + Msg = 'Expression cannot start with an operator {Op}.'.format (Op = Op) + raise ValueError (Msg) + # The last op can not be operator + if i == len (OpList) - 1: + Msg = 'Expression cannot ended with an operator {Op}.'.format (Op = Op) + raise ValueError (Msg) + # The next op of unary operator cannot be guid / version + if self.IsValidUnaryOperator (Op) and (self.IsValidGuid (OpList[i+1]) or self.IsValidVersion (OpList[i+1])): + Msg = '{Op} after {PrevOp} is not a valid expression input.'.format (Op = OpList[i+1], PrevOp = Op) + raise ValueError (Msg) + + else: + Msg = '{Op} is not a valid expression input.'.format (Op = Op) + raise ValueError (Msg) + i += 1 + + def Encode (self): + # initialize + self.Depex = b'' + self._DepexDump = [] + OperandStack = [] + OpeartorStack = [] + OpList = self._DepexList + + self.ValidateDepex () + + # convert + i = 0 + while i < len (OpList): + Op = OpList[i] + if Op == 'DECLARE': + # This declare next expression value is a VERSION_STRING + i += 1 + self.Depex += self.OpConverter.OpEncode (0x02, OpList[i][1:-1]) + + elif Op == '(': + OpeartorStack.append (Op) + + elif Op == ')': + while (OpeartorStack and OpeartorStack[-1] != '('): + Operator = OpeartorStack.pop () + self.Depex += self.OpConverter.OpEncode (self._opReference[Operator][1]) + try: + OpeartorStack.pop () # pop out '(' + except: + Msg = 'Pop out \'(\' failed, too many \')\'' + raise ValueError (Msg) + + elif self.IsValidGuid (Op): + if not OperandStack: + OperandStack.append (self.OpConverter.OpEncode (0x00, Op)) + else: + # accroding to uefi spec 2.8, the guid/version operands is a reversed order in firmware comparison. + self.Depex += self.OpConverter.OpEncode (0x00, Op) + self.Depex += OperandStack.pop () + + elif self.IsValidVersion (Op): + if not OperandStack: + OperandStack.append (self.OpConverter.OpEncode (0x01, Op)) + else: + # accroding to uefi spec 2.8, the guid/version operands is a reversed order in firmware comparison. + self.Depex += self.OpConverter.OpEncode (0x01, Op) + self.Depex += OperandStack.pop () + + elif self.IsValidBoolean (Op): + if Op.upper () == 'FALSE': + self.Depex += self.OpConverter.OpEncode (0x07) + elif Op.upper () == 'TRUE': + self.Depex += self.OpConverter.OpEncode (0x06) + + elif self.IsValidOperator (Op): + while (OpeartorStack and OpeartorStack[-1] != '(' and self.PriorityNotGreater (OpeartorStack[-1], Op)): + Operator = OpeartorStack.pop () + self.Depex += self.OpConverter.OpEncode (self._opReference[Operator][1]) + OpeartorStack.append (Op) + + i += 1 + + while OpeartorStack: + Operator = OpeartorStack.pop () + if Operator == '(': + Msg = 'Too many \'(\'.' + raise ValueError (Msg) + self.Depex += self.OpConverter.OpEncode (self._opReference[Operator][1]) + self.Depex += self.OpConverter.OpEncode (0x0D) + + self._Valid = True + self._DepexSize = len (self.Depex) + return self.Depex + self.Payload + + def Decode (self, Buffer): + # initialize + self.Depex = Buffer + OperandStack = [] + DepexLen = 0 + + while True: + Opcode, Operand, OperandSize = self.OpConverter.OpDecode (Buffer[DepexLen:]) + DepexLen += OperandSize + 1 + + if Opcode == 0x0D: + break + + elif Opcode == 0x02: + if not OperandStack: + OperandStack.append ('DECLARE \"{String}\"'.format (String = Operand)) + else: + PrevOperand = OperandStack.pop () + OperandStack.append ('{Operand} DECLARE \"{String}\"'.format (Operand = PrevOperand, String = Operand)) + + elif Opcode in [0x00, 0x01]: + OperandStack.append (Operand) + + elif Opcode == 0x06: + OperandStack.append ('TRUE') + + elif Opcode == 0x07: + OperandStack.append ('FALSE') + + elif self.IsValidOperator (self._opReferenceReverse[Opcode]): + Operator = self._opReferenceReverse[Opcode] + if self.IsValidUnaryOperator (self._opReferenceReverse[Opcode]) and len (OperandStack) >= 1: + Oprand = OperandStack.pop () + OperandStack.append (' ( {Operator} {Oprand} )'.format (Operator = Operator, Oprand = Oprand)) + elif self.IsValidBinocularOperator (self._opReferenceReverse[Opcode]) and len (OperandStack) >= 2: + Oprand1 = OperandStack.pop () + Oprand2 = OperandStack.pop () + OperandStack.append (' ( {Oprand1} {Operator} {Oprand2} )'.format (Operator = Operator, Oprand1 = Oprand1, Oprand2 = Oprand2)) + else: + Msg = 'No enough Operands for {Opcode:02X}.'.format (Opcode = Opcode) + raise ValueError (Msg) + + else: + Msg = '{Opcode:02X} is not a valid OpCode.'.format (Opcode = Opcode) + raise ValueError (Msg) + + self.DepexExp = OperandStack[0].strip (' ') + self.Payload = Buffer[DepexLen:] + self._Valid = True + self._DepexSize = DepexLen + return self.Payload + + + def DumpInfo (self): + DepexLen = 0 + Opcode = None + Buffer = self.Depex + + if self._Valid == True: + print ('EFI_FIRMWARE_IMAGE_DEP.Dependencies = {') + while Opcode != 0x0D: + Opcode, Operand, OperandSize = self.OpConverter.OpDecode (Buffer[DepexLen:]) + DepexLen += OperandSize + 1 + if Operand: + print (' {Opcode:02X}, {Operand},'.format (Opcode = Opcode, Operand = Operand)) + else: + print (' {Opcode:02X},'.format (Opcode = Opcode)) + print ('}') + + print ('sizeof (EFI_FIRMWARE_IMAGE_DEP.Dependencies) = {Size:08X}'.format (Size = self._DepexSize)) + print ('sizeof (Payload) = {Size:08X}'.format (Size = len (self.Payload))) -- 2.18.0.windows.1