common/recipes-utils/vboot-utils/files/measure.py (441 lines of code) (raw):
#!/usr/bin/env python3
#
# Copyright 2018-present Facebook. All Rights Reserved.
#
# This program file is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; version 2 of the License.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
# for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program in a file named COPYING; if not, write to the
# Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor,
# Boston, MA 02110-1301 USA
#
import argparse
import hashlib
import json
import os
import subprocess
import sys
import traceback
from image_meta import FBOBMCImageMeta
from measure_func import (
measure_spl,
measure_keystore,
measure_uboot,
measure_rcv_uboot,
measure_uboot_env,
measure_blank_uboot_env,
measure_vbs,
measure_os,
)
from tpm_event_log import (
TPMEventLog,
InvalidTPMEventLog,
MalformedTPMEventLog,
print_tpm_event_log_debug_suggestion,
)
from vboot_common import (
EC_EXCEPTION,
EC_SUCCESS,
EC_MEASURE_FAIL_BASE,
EC_TPM_EVENT_LOG_BAD,
VBS_ERROR_TYPE_OFFSET,
VBS_ERROR_CODE_OFFSET,
read_vbs,
)
MBOOT_CHECK_VERSION = "6"
def read_tpm2_pcr(algo, pcr_id):
if os.access("/usr/bin/tpm2_pcrread", os.X_OK):
cmd = ["/usr/bin/tpm2_pcrread", f"{algo}:{pcr_id}"]
tpm2_pcr_reading = subprocess.check_output(cmd).decode("utf-8")
tpm2_pcr = bytes.fromhex(tpm2_pcr_reading.split()[3][2:])
else:
# fallback to TPM2 3.x tool
cmd = ["/usr/bin/tpm2_pcrlist", "-L", f"{algo}:{pcr_id}"]
tpm2_pcr_reading = subprocess.check_output(cmd).decode("utf-8")
tpm2_pcr = bytes.fromhex(tpm2_pcr_reading.split()[4])
return tpm2_pcr
ATTEST_COMPONENTS = [
"spl",
"key-store",
"u-boot",
"rec-u-boot",
"os",
"rec-os",
"blank-u-boot-env",
]
OS_SUB_COMPNAME = ["kernel", "ramdisk", "fdt"]
RECCOVERY_OS_SUB_COMPNAME = ["rec-" + c for c in OS_SUB_COMPNAME]
def print_attest_list(fw_ver, raw_sha1_hashes, raw_sha256_hashes, comp):
if "os" == comp or "rec-os" == comp:
attlist = dict()
subcomp_name = OS_SUB_COMPNAME if "os" == comp else RECCOVERY_OS_SUB_COMPNAME
for subcomp_idx, subcomp in enumerate(subcomp_name):
attlist[subcomp] = {
"hashes": {
"sha1": raw_sha1_hashes[comp][subcomp_idx].hex(),
"sha256": raw_sha256_hashes[comp][subcomp_idx].hex(),
},
"metadata": {
"name": comp + "." + subcomp,
"version": fw_ver,
},
"command_lines": [],
}
else:
attlist = {
comp: {
"hashes": {
"sha1": raw_sha1_hashes[comp].hex(),
"sha256": raw_sha256_hashes[comp].hex(),
},
"metadata": {
"name": comp,
"version": fw_ver,
},
"command_lines": [],
},
}
if args.json:
print(json.dumps(attlist))
else:
for c in attlist.keys():
k = attlist[c]["metadata"]["name"] + "(sha1)"
v = attlist[c]["hashes"]["sha1"]
print(f"{k:27}:{v}")
k = attlist[c]["metadata"]["name"] + "(sha256)"
v = attlist[c]["hashes"]["sha256"]
print(f"{k:27}:{v}")
def gen_attest_allowlists(flash0_meta, flash1_meta):
raw_sha1_hashes = {
"spl": measure_spl("sha1", flash0_meta, True),
"key-store": measure_keystore("sha1", flash1_meta, True),
"u-boot": measure_uboot("sha1", flash1_meta, args.recal, True),
"rec-u-boot": measure_rcv_uboot("sha1", flash0_meta, True),
"os": measure_os("sha1", flash1_meta, args.recal, True),
"rec-os": measure_os("sha1", flash0_meta, args.recal, True),
"blank-u-boot-env": measure_blank_uboot_env("sha1", flash1_meta, True),
}
raw_sha256_hashes = {
"spl": measure_spl("sha256", flash0_meta, True),
"key-store": measure_keystore("sha256", flash1_meta, True),
"u-boot": measure_uboot("sha256", flash1_meta, args.recal, True),
"rec-u-boot": measure_rcv_uboot("sha256", flash0_meta, True),
"os": measure_os("sha256", flash1_meta, args.recal, True),
"rec-os": measure_os("sha256", flash0_meta, args.recal, True),
"blank-u-boot-env": measure_blank_uboot_env("sha256", flash1_meta, True),
}
if "ALL" in args.components:
args.components = ATTEST_COMPONENTS
for comp in args.components:
print_attest_list(
flash0_meta.meta["version_infos"]["fw_ver"],
raw_sha1_hashes,
raw_sha256_hashes,
comp,
)
return EC_SUCCESS
def get_all_measures(flash0_meta, flash1_meta):
return [
{ # SPL
"component": "spl",
"pcr_id": 0,
"algo": args.algo,
"expect": measure_spl(args.algo, flash0_meta).hex(),
"measure": "NA",
},
{ # key-store
"component": "key-store",
"pcr_id": 1,
"algo": args.algo,
"expect": measure_keystore(args.algo, flash1_meta).hex(),
"measure": "NA",
},
{ # u-boot
"component": "u-boot",
"pcr_id": 2,
"algo": args.algo,
"expect": measure_uboot(args.algo, flash1_meta, args.recal).hex(),
"measure": "NA",
},
{ # Recovery u-boot
"component": "rec-u-boot",
"pcr_id": 2,
"algo": args.algo,
"expect": measure_rcv_uboot(args.algo, flash0_meta).hex(),
"measure": "NA",
},
{ # u-boot-env
"component": "u-boot-env",
"pcr_id": 3,
"algo": args.algo,
"expect": measure_uboot_env(args.algo, flash1_meta).hex(),
"measure": "NA",
},
{ # blank-u-boot-env
"component": "blank-u-boot-env",
"pcr_id": 3,
"algo": args.algo,
"expect": measure_blank_uboot_env(args.algo, flash1_meta).hex(),
"measure": "NA",
},
{ # vbs
"component": "vbs",
"pcr_id": 5,
"algo": args.algo,
"expect": measure_vbs(args.algo).hex() if args.tpm else "NA",
"measure": "NA",
},
{ # os
"component": "os",
"pcr_id": 9,
"algo": args.algo,
"expect": measure_os(args.algo, flash1_meta, args.recal).hex(),
"measure": "NA",
},
{ # recovery-os
"component": "recovery-os",
"pcr_id": 9,
"algo": args.algo,
"expect": measure_os(args.algo, flash0_meta, args.recal).hex(),
"measure": "NA",
},
]
def get_vboot_success_measures(flash0_meta, flash1_meta):
return [
{ # SPL
"component": "spl",
"pcr_id": 0,
"algo": args.algo,
"expect": measure_spl(args.algo, flash0_meta).hex(),
"measure": "NA",
},
{ # key-store
"component": "key-store",
"pcr_id": 1,
"algo": args.algo,
"expect": measure_keystore(args.algo, flash1_meta).hex(),
"measure": "NA",
},
{ # u-boot
"component": "u-boot",
"pcr_id": 2,
"algo": args.algo,
"expect": measure_uboot(args.algo, flash1_meta, args.recal).hex(),
"measure": "NA",
},
{ # vbs
"component": "vbs",
"pcr_id": 5,
"algo": args.algo,
"expect": measure_vbs(args.algo).hex() if args.tpm else "NA",
"measure": "NA",
},
{ # os
"component": "os",
"pcr_id": 9,
"algo": args.algo,
"expect": measure_os(args.algo, flash1_meta, args.recal).hex(),
"measure": "NA",
},
]
def get_vboot_os_invalid_measures(flash0_meta, flash1_meta):
return [
{ # SPL
"component": "spl",
"pcr_id": 0,
"algo": args.algo,
"expect": measure_spl(args.algo, flash0_meta).hex(),
"measure": "NA",
},
{ # key-store
"component": "key-store",
"pcr_id": 1,
"algo": args.algo,
"expect": measure_keystore(args.algo, flash1_meta).hex(),
"measure": "NA",
},
{ # u-boot
"component": "u-boot",
"pcr_id": 2,
"algo": args.algo,
"expect": measure_uboot(args.algo, flash1_meta, args.recal).hex(),
"measure": "NA",
},
{ # vbs
"component": "vbs",
"pcr_id": 5,
"algo": args.algo,
"expect": measure_vbs(args.algo).hex() if args.tpm else "NA",
"measure": "NA",
},
{ # recovery-os
"component": "recovery-os",
"pcr_id": 9,
"algo": args.algo,
"expect": measure_os(args.algo, flash0_meta, args.recal).hex(),
"measure": "NA",
},
]
def get_vboot_general_fail_measures(flash0_meta, flash1_meta):
return [
{ # SPL
"component": "spl",
"pcr_id": 0,
"algo": args.algo,
"expect": measure_spl(args.algo, flash0_meta).hex(),
"measure": "NA",
},
{ # key-store
"component": "key-store",
"pcr_id": 1,
"algo": args.algo,
"expect": measure_keystore(args.algo, flash1_meta).hex(),
"measure": "NA",
},
{ # Recovery u-boot
"component": "rec-u-boot",
"pcr_id": 2,
"algo": args.algo,
"expect": measure_rcv_uboot(args.algo, flash0_meta).hex(),
"measure": "NA",
},
{ # vbs
"component": "vbs",
"pcr_id": 5,
"algo": args.algo,
"expect": measure_vbs(args.algo).hex() if args.tpm else "NA",
"measure": "NA",
},
{ # recovery-os
"component": "recovery-os",
"pcr_id": 9,
"algo": args.algo,
"expect": measure_os(args.algo, flash0_meta, args.recal).hex(),
"measure": "NA",
},
]
def get_vboot_status():
vbs_data = read_vbs()
return vbs_data[VBS_ERROR_TYPE_OFFSET], vbs_data[VBS_ERROR_CODE_OFFSET]
def get_need_checked_measures(flash0_meta, flash1_meta):
# not running on BMC we are calculating expected measurement
# of the input image
if not args.tpm:
return get_all_measures(flash0_meta, flash1_meta)
# running on BMC we are doing measurement checking
# get the measures need to check based on vboot status
vbs_err_type, vbs_err_code = get_vboot_status()
if vbs_err_type == 0 and vbs_err_code == 0:
return get_vboot_success_measures(flash0_meta, flash1_meta)
if vbs_err_type == 6 and vbs_err_code == 60:
return get_vboot_os_invalid_measures(flash0_meta, flash1_meta)
return get_vboot_general_fail_measures(flash0_meta, flash1_meta)
def main():
flash0_meta = None
flash1_meta = None
try:
if args.image:
flash0_meta = FBOBMCImageMeta(args.image)
flash1_meta = FBOBMCImageMeta(args.image)
else:
flash0_meta = FBOBMCImageMeta(args.flash0)
flash1_meta = FBOBMCImageMeta(args.flash1)
except Exception:
# now just bypass and let the invalid flasX_meta be None
# we will catch the error later when we need measure it
pass
# Only get the raw hash of components
if args.components:
return gen_attest_allowlists(flash0_meta, flash1_meta)
# load TPM event log
tpm_event_log = None
if args.event_log != "none":
try:
tpm_event_log = TPMEventLog(flash0_meta, flash1_meta, args.ef)
except Exception as e:
if args.event_log in ["sram", "file"]:
# fail for explicit loading
raise e
if tpm_event_log:
mboot_measures = tpm_event_log.replay_measure(args.ce, args.recal)
else:
mboot_measures = get_need_checked_measures(flash0_meta, flash1_meta)
if args.tpm:
for measure in mboot_measures:
measure["measure"] = read_tpm2_pcr(measure["algo"], measure["pcr_id"]).hex()
if args.json:
print(json.dumps(mboot_measures))
else:
for measure in mboot_measures:
print(
f'{measure["pcr_id"]:2d}:[{measure["expect"]}] ({measure["component"]})'
)
print(f' [{measure["measure"]}] (pcr{measure["pcr_id"]})')
ret = EC_SUCCESS
if args.tpm:
for measure in mboot_measures:
if measure["measure"] != measure["expect"]:
ret = EC_MEASURE_FAIL_BASE + measure["pcr_id"]
# Print the event log
if args.pe:
if tpm_event_log:
tpm_event_log.print_events(args.json)
elif not args.json:
print("=== TPM event log is not used ===")
return ret
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Measurement and compare with PCRs",
formatter_class=argparse.RawTextHelpFormatter,
)
parser.add_argument(
"-a",
"--algo",
help="hash algorithm",
choices=hashlib.algorithms_available,
default="sha256",
)
parser.add_argument(
"--version",
action="version",
version="%(prog)s-v{}".format(MBOOT_CHECK_VERSION),
)
parser.add_argument(
"-0", "--flash0", help="flash0 device or image", default="/dev/flash0"
)
parser.add_argument(
"-1", "--flash1", help="flash1 device or image", default="/dev/flash1"
)
parser.add_argument("-i", "--image", help="single image")
parser.add_argument("-j", "--json", help="output as JSON", action="store_true")
parser.add_argument("-t", "--tpm", help="read tpm pcr also", action="store_true")
parser.add_argument(
"-r",
"--recal",
help="""recalculate hash, this is normally used on dev-server.
Be very careful to do this on BMC, it will took more than 30 mins
to recaludate hash for each measures.
If you really would like to try. Please BE PATIENT!!!
Take a coffee and check back after 30 mins.
""",
action="store_true",
)
parser.add_argument(
"-c",
"--components",
help="output raw hash(measure) value of specified components",
nargs="*",
choices=["ALL"] + ATTEST_COMPONENTS,
)
parser.add_argument(
"-e",
"--event-log",
nargs="?",
choices=["sram", "file", "none", "auto"],
default="auto",
help="""Specify TPM event log loading mode, default [auto]:
sram - Load TPM event log from sram at well-known SRAM location
AST2500: 0x1E728800, AST2600: 0x10015000
file - Load from event log binary file specified with --ef <event-file>
auto - Load from sram if well-formed event log exists
none - Don't load TPM event log
""",
)
parser.add_argument(
"--ef",
help="When -e file, specify the event log file, ignored otherwise",
)
parser.add_argument(
"--pe",
action="store_true",
help="Print the loaded TPM event log also",
)
parser.add_argument(
"--ce",
help="Check TPM event log against image when print or use it",
action="store_true",
)
args = parser.parse_args()
if args.event_log == "file" and args.ef is None:
parser.error("--event-log=file, please provide event file with --ef")
else:
args.ef = None if args.event_log != "file" else args.ef
try:
sys.exit(main())
except (InvalidTPMEventLog, MalformedTPMEventLog) as e:
print(e)
print_tpm_event_log_debug_suggestion()
sys.exit(EC_TPM_EVENT_LOG_BAD)
except Exception as e:
print("Exception: %s" % (str(e)))
traceback.print_exc()
sys.exit(EC_EXCEPTION)