CHIPSEC is a “extensible framework for analyzing platform level security of hardware, devices, system firmware, low-level protection mechanisms, and the configuration of various platform components. It contains a set of modules, including simple tests for hardware protections and correct configuration, tests for vulnerabilities in firmware and platform components, security assessment and fuzzing tools for various platform devices and interfaces, and tools acquiring critical firmware and device artifacts”.
CHIPSEC is mostly written in Python for portability and can run on Linux, Windows, Mac OSX, DAL, and the UEFI shell. It is Intel-centric at this time.
The recent update from Python 2 from Python 3 broke a couple of commands. For example, in version 1.8.1, I am getting a TypeError: argument 3 must be str, not bytes exception when I invoke chipsec_util.py spi dump rom.bin from a UEFI shell.
Here is the full error details:
################################################################ CHIPSEC: Platform Hardware Security Assessment Framework ################################################################ [CHIPSEC] Version : 1.8.0 [CHIPSEC] OS : uefi [CHIPSEC] Python : 3.6.8 (64-bit) [CHIPSEC] API mode: using CHIPSEC kernel module API [CHIPSEC] Helper : EfiHelper (None) [CHIPSEC] Platform: Mobile 8th Generation Core Processor (Kabylake U-Quad Core) [CHIPSEC] VID: 8086 [CHIPSEC] DID: 5914 [CHIPSEC] RID: 08 [CHIPSEC] PCH : PCH-U with iHDCP 2.2 Premium [CHIPSEC] VID: 8086 [CHIPSEC] DID: 9D4E [CHIPSEC] RID: 21 [CHIPSEC] Executing command 'spi' with args ['dump', 'rom.bin'] [CHIPSEC] Dumping entire SPI flash memory to 'rom.bin' [CHIPSEC] it may take a few minutes (use DEBUG or VERBOSE logger options to see progress) [CHIPSEC] BIOS region: base = 0x00700000, limit = 0x00FFFFFF [CHIPSEC] Dumping 0x01000000 bytes (to the end of BIOS region) [spi] reading 0x1000000 bytes from SPI at FLA = 0x0 (in 262144 0x40-byte chunks + 0x0-byte remainder) Traceback (most recent call last): File "chipsec_util.py", line 218, in sys.exit( main() ) File "chipsec_util.py", line 214, in main return chipsecUtil.main() File "chipsec_util.py", line 189, in main comm.run() File "FS1:\chipsec\chipsec\utilcmd\spi_cmd.py", line 182, in run self.func() File "FS1:\chipsec\chipsec\utilcmd\spi_cmd.py", line 108, in spi_dump buf = self._spi.read_spi_to_file( 0, spi_size, self.out_file ) File "FS1:\chipsec\chipsec\hal\spi.py", line 564, in read_spi_to_file buf = self.read_spi( spi_fla, data_byte_count ) File "FS1:\chipsec\chipsec\hal\spi.py", line 592, in read_spi cycle_done = self._wait_SPI_flash_cycle_done() File "FS1:\chipsec\chipsec\hal\spi.py", line 507, in _wait_SPI_flash_cycle_done self.spi_reg_write( self.hsfs_off, HSFSTS_CLEAR, 1 ) File "FS1:\chipsec\chipsec\hal\spi.py", line 243, in spi_reg_write return self.mmio.write_MMIO_reg(self.rcba_spi_base, reg, value, size) File "FS1:\chipsec\chipsec\hal\mmio.py", line 112, in write_MMIO_reg self.cs.helper.write_mmio_reg( bar_base, size, value, offset, bar_size ) File "FS1:\chipsec\chipsec\helper\oshelper.py", line 192, in write_mmio_reg ret = self.helper.write_mmio_reg(bar_base +offset, size, value ) File "FS1:\chipsec\chipsec\helper\efi\efihelper.py", line 166, in write_mmio_reg edk2.writemem(phys_address_lo, phys_address_hi, buf, size) TypeError: argument 3 must be str, not bytes
The fix was simple. All that was required was to modify one line of code in write_mmio_reg() which is in …/chipsec/helper/efi/efihelper.py, and also import bytestostring from chipsec.defines in the same file.
Here is the existing version 1.8.1 code for write_mmio_reg:
def write_mmio_reg(self, phys_address, size, value): phys_address_lo = phys_address & 0xFFFFFFFF phys_address_hi = (phys_address >> 32) & 0xFFFFFFFF if size == 4: return edk2.writemem_dword(phys_address_lo, phys_address_hi, value) else: buf = struct.pack(size * "B", value) edk2.writemem(phys_address_lo, phys_address_hi, buf, size)
Here is the modified code that works:
def write_mmio_reg(self, phys_address, size, value): phys_address_lo = phys_address & 0xFFFFFFFF phys_address_hi = (phys_address >> 32) & 0xFFFFFFFF if size == 4: return edk2.writemem_dword(phys_address_lo, phys_address_hi, value) else: buf = bytestostring( struct.pack(size * "B", value) ) edk2.writemem(phys_address_lo, phys_address_hi, buf, size)
What is bytestostring you may ask and why is the change necessary?
Here is the definition of bytestostring from …/chipsec/defines.py:
def bytestostring(mbytes): if isinstance(mbytes, bytes): return mbytes.decode("latin_1") else: return mbytes
Note that the real work is done by the mbytes object’s mbytes.decode method. As an aside, I am not sure why latin_1 was chosen by the developers of CHIPSEC as the decoded string format but it works.
For completeness, some notes about Python 3 TypeError follow. As you are probably aware, Python 3 is a much stricter object-orientated programming language than Python 2. It always checks the type of object you are passing and whether a particular object type supports the operation. If there is a mismatch, Python 3 will throw a TypeError exception error. Note that type constraints are not checked at compile time; therefore operations on an object may fail at runtime if the given object is not of a suitable type.
Looking back at the defective source code, you can see that the line struct.pack(size * “B”, value) returns a bytes object in buf. struct.pack is part of the struct.py module which performs conversions between Python values and C structs represented as Python bytes objects. In this case, “B” is the format character for unsigned char C type (Python – integer type) whose size is 1 byte. However edk2.writemem expects a string for the third argument – not a bytes object. This mismatch of types causes the TypeError exception.
Enjoy!