SeaBIOS调用 vTPM启动过程

196次阅读
一条评论

共计 17032 个字符,预计需要花费 43 分钟才能阅读完成。

SeaBIOS调用 vTPM启动过程

要了解可信启动的过程,不能单单在“边启动,边度量”,都说CRTM是信任源头,但是它又是BIOS的一部分,那么他们之间是怎么度量的呢?度量哪儿呢?度量日志放在哪儿呢?
首先是POST阶段的mianinit函数->platform_hardware_setup->tpm_setup()

tpm_setup

对于tpm_setup,用于初始化可信计算模块。

void
tpm_setup(void)
{
    if (!CONFIG_TCGBIOS)
        return;

    //探测tpm2设备
    int ret = tpm_tpm2_probe();
    if (ret) {
        //探测tpm1.2设备
        ret = tpm_tcpa_probe();
        if (ret)
            return;
    }

    //获取TPM版本 u8 TPM_version 并且调用相应驱动的init方法,tis_init或者crb_init
    TPM_version = tpmhw_probe();
    if (TPM_version == TPM_VERSION_NONE)
        return;

    dprintf(DEBUG_tcg,
            "TCGBIOS: Detected a TPM %s.\n",
             (TPM_version == TPM_VERSION_1_2) ? "1.2" : "2");

    TPM_working = 1;

    if (runningOnXen())
        return;

    //初始化TPM
    ret = tpm_startup();
    if (ret)
        return;

    //度量smbios
    tpm_smbios_measure();
    //度量EV_ACTION事件,扩展进入PCR 2寄存器
    tpm_add_action(2, "Start Option ROM Scan");
}

可以看到,他先获取了平台上的TPM版本,然后在进行TPM初始化,接着度量smbios添加度量日志。我们看一下细节,它是怎样获取到TPM版本的?以TPM2为例。

static int
tpm_tpm2_probe(void)
{
    //从acpi表中获取标识符
    struct tpm2_descriptor_rev2 *tpm2 = find_acpi_table(TPM2_SIGNATURE);
    if (!tpm2)
        return -1;

    if (tpm2->length < 76)
        return -1;

    dprintf(DEBUG_tcg, "TCGBIOS: TPM2: LASA = %p, LAML = %u\n",
            (u8 *)(long)tpm2->log_area_start_address,
            tpm2->log_area_minimum_length);

    //设置日志区域
    return tpm_set_log_area((u8*)(long)tpm2->log_area_start_address,
                            tpm2->log_area_minimum_length);
}

可以看到,从acpi表中获取TPM2_SIGNATURE标识的TPM2描述符,找到之后就判断长度是否符合要求,然后就调用tpm_set_log_area函数,该函数将设置TPM的日志区域并返回0以表示成功。

tpmhw_probe

该函数获取TPM版本 TPM_version 并且调用相应驱动的init方法,tis_init或者crb_init。

TPMVersion
tpmhw_probe(void)
{
    unsigned int i;
    for (i = 0; i < TPM_NUM_DRIVERS; i++) {
        struct tpm_driver *td = &tpm_drivers[i];
        //调用相应的probe方法
        if (td->probe() != 0) {
            //调用相应的tis_init或者crb_init方法
            td->init();
            TPMHW_driver_to_use = i;
            return td->get_tpm_version();
        }
    }
    return TPM_VERSION_NONE;
}

在qemu-tpm-2.12中,所有的与driver相关的代码,在seaBIOS中的src/hw/tpm_drivers.c之中。给出tpm_drivers的定义,可以看到tpm驱动分为tis和crb,crb只支持2.0。

struct tpm_driver tpm_drivers[TPM_NUM_DRIVERS] = {
    [TIS_DRIVER_IDX] =
        {
            .timeouts      = NULL,
            .durations     = NULL,
            .set_timeouts  = set_timeouts,
            .probe         = tis_probe,
            .get_tpm_version = tis_get_tpm_version,
            .init          = tis_init,
            .activate      = tis_activate,
            .ready         = tis_ready,
            .senddata      = tis_senddata,
            .readresp      = tis_readresp,
            .waitdatavalid = tis_waitdatavalid,
            .waitrespready = tis_waitrespready,
        },
    [CRB_DRIVER_IDX] =
        {
            .timeouts      = NULL,
            .durations     = NULL,
            .set_timeouts  = set_timeouts,
            .probe         = crb_probe,
            .get_tpm_version = crb_get_tpm_version,
            .init          = crb_init,
            .activate      = crb_activate,
            .ready         = crb_ready,
            .senddata      = crb_senddata,
            .readresp      = crb_readresp,
            .waitdatavalid = crb_waitdatavalid,
            .waitrespready = crb_waitrespready,
        },
};

tpm_startup

static int
tpm_startup(void)
{
    switch (TPM_version) {
    case TPM_VERSION_1_2:
        return tpm12_startup();
    case TPM_VERSION_2:
        return tpm20_startup();
    }
    return -1;
}

我们都以TPM2.0为例,在确认TPM版本之后,初始化TPM。

static int
tpm20_startup(void)
{
    //设置超时时间
    tpm20_set_timeouts();

    //发送TPM2_CC_Startup命令,以清除TPM设备并返回一个结果,该结果存储在ret变量中
    int ret = tpm_simple_cmd(0, TPM2_CC_Startup,
                             2, TPM2_SU_CLEAR, TPM_DURATION_TYPE_SHORT);

    dprintf(DEBUG_tcg, "TCGBIOS: Return value from sending TPM2_CC_Startup(SU_CLEAR) = 0x%08x\n",
            ret);

    //在coreboot已经启用的情况下,但是我们没有使用这里不用看
    if (CONFIG_COREBOOT && ret == TPM2_RC_INITIALIZE)
        /* with other firmware on the system the TPM may already have been
         * initialized
         */
        ret = 0;

    if (ret)
        goto err_exit;

    //发送TPM自检命令
    ret = tpm_simple_cmd(0, TPM2_CC_SelfTest,
                         1, TPM2_YES, TPM_DURATION_TYPE_LONG);

    dprintf(DEBUG_tcg, "TCGBIOS: Return value from sending TPM2_CC_SelfTest = 0x%08x\n",
            ret);

    if (ret)
        goto err_exit;

    //获取相应的pcrbanks
    ret = tpm20_get_pcrbanks();
    if (ret)
        goto err_exit;

    //写入相应的efi事件结构
    ret = tpm20_write_EfiSpecIdEventStruct();
    if (ret)
        goto err_exit;

    return 0;

//失败处理    
err_exit:
    dprintf(DEBUG_tcg, "TCGBIOS: TPM malfunctioning (line %d).\n", __LINE__);

    tpm_set_failure();
    return -1;
}

tpm_simple_cmd

发送简单的TPM命令,如果使用swtpm作为TPM那么会通过qemu后端驱动发送出去。

static int
tpm_simple_cmd(u8 locty, u32 ordinal
               , int param_size, u16 param, enum tpmDurationType to_t)
{
    struct {
        struct tpm_req_header trqh;
        u16 param;
    } PACKED req = {
        .trqh.totlen = cpu_to_be32(sizeof(req.trqh) + param_size),
        .trqh.ordinal = cpu_to_be32(ordinal),
        .param = param_size == 2 ? cpu_to_be16(param) : param,
    };
    switch (TPM_version) {
    case TPM_VERSION_1_2:
        req.trqh.tag = cpu_to_be16(TPM_TAG_RQU_CMD);
        break;
    case TPM_VERSION_2:
        req.trqh.tag = cpu_to_be16(TPM2_ST_NO_SESSIONS);
        break;
    }

    u8 obuffer[64];
    struct tpm_rsp_header *trsh = (void*)obuffer;
    u32 obuffer_len = sizeof(obuffer);
    memset(obuffer, 0x0, sizeof(obuffer));

    //组装好请求后,发送请求。
    int ret = tpmhw_transmit(locty, &req.trqh, obuffer, &obuffer_len, to_t);
    ret = ret ? -1 : be32_to_cpu(trsh->errcode);
    dprintf(DEBUG_tcg, "Return from tpm_simple_cmd(%x, %x) = %x\n",
            ordinal, param, ret);
    return ret;
}

可看到tpmhw_transmit方法才是发送命令的关键。

tpmhw_transmit

int
tpmhw_transmit(u8 locty, struct tpm_req_header *req,
               void *respbuffer, u32 *respbufferlen,
               enum tpmDurationType to_t)
{
    if (TPMHW_driver_to_use == TPM_INVALID_DRIVER)
        return -1;

    struct tpm_driver *td = &tpm_drivers[TPMHW_driver_to_use];

    //函数用于激活TPM设备的TIS接口,请求并等待访问权限,如果访问权限已被获取,则该函数返回0,否则返回非零结果表示激活失败。
    u32 irc = td->activate(locty);
    if (irc != 0) {
        /* tpm could not be activated */
        return -1;
    }

    //发送数据
    irc = td->senddata((void*)req, be32_to_cpu(req->totlen));
    if (irc != 0)
        return -1;

    //在超时时间内等待状态寄存器值被设置,等待返回数据
    irc = td->waitdatavalid();
    if (irc != 0)
        return -1;

    //在超时时间内等待数据寄存器值被设置,可以读取数据
    irc = td->waitrespready(to_t);
    if (irc != 0)
        return -1;

    //读取返回响应数据
    irc = td->readresp(respbuffer, respbufferlen);
    if (irc != 0 ||
        *respbufferlen < sizeof(struct tpm_rsp_header))
        return -1;

    td->ready();

    return 0;
}

可以看到发送命令分为6个主要函数,activate、senddata、waitdatavalid、waitrespready、readresp以及ready。

tis_activate

TPM(Trusted Platform Module)是一个专门的安全硬件模块,它用于在计算设备中提供硬件级别的安全功能,例如密钥生成、加密、解密和数字签名。TPM通常用于确保计算平台的完整性和认证。

TPM的Localities是一种访问控制机制,用于在TPM内部区分不同安全级别的访问权限。Localities的概念是为了支持多种安全执行环境(例如BIOS、操作系统、虚拟化平台等)共享TPM资源,同时确保这些环境之间的隔离和安全性。

TPM中有五个不同的Localities,编号为0到4。它们的安全级别是递增的,即Localities 0具有最低的安全级别,而Localities 4具有最高的安全级别。这些不同的Localities可以用于控制对TPM资源的访问。例如,某些敏感操作可能只允许在具有较高Localities级别的环境中执行。

总结一下,TPM的Localities是一种区分不同安全级别访问权限的机制,用于支持多种安全执行环境共享TPM资源,同时保证环境之间的隔离和安全性。

主要作用就是激活对应的locality

//u8类型的locty,表示要激活的locality编号(0到4之间的整数)
static u32 tis_activate(u8 locty)
{
    if (!CONFIG_TCGBIOS)
        return 0;

    u32 rc = 0;
    u8 acc;
    int l;
    u32 timeout_a = tpm_drivers[TIS_DRIVER_IDX].timeouts[TIS_TIMEOUT_TYPE_A];

    //函数检查当前locality(locty)是否已被激活。如果没有被激活,它将释放所有正在使用的locality(从4到0),以便将当前locality设置为活动状态。
    if (!(readb(TIS_REG(locty, TIS_REG_ACCESS)) &
          TIS_ACCESS_ACTIVE_LOCALITY)) {
        /* release locality in use top-downwards */
        for (l = 4; l >= 0; l--)
            writeb(TIS_REG(l, TIS_REG_ACCESS),
                   TIS_ACCESS_ACTIVE_LOCALITY);
    }

    /* request access to locality */
    //请求访问指定的locality(locty),通过向TIS_REG_ACCESS寄存器写入TIS_ACCESS_REQUEST_USE值
    writeb(TIS_REG(locty, TIS_REG_ACCESS), TIS_ACCESS_REQUEST_USE);

    //函数读取TIS_REG_ACCESS寄存器的值,检查已请求的locality是否已激活。
    //如果成功激活,函数将TIS_REG_STS寄存器设置为TIS_STS_COMMAND_READY,并调用tis_wait_sts函数等待状态寄存器的状态变为TIS_STS_COMMAND_READY。
    acc = readb(TIS_REG(locty, TIS_REG_ACCESS));
    if ((acc & TIS_ACCESS_ACTIVE_LOCALITY)) {
        writeb(TIS_REG(locty, TIS_REG_STS), TIS_STS_COMMAND_READY);
        rc = tis_wait_sts(locty, timeout_a,
                          TIS_STS_COMMAND_READY, TIS_STS_COMMAND_READY);
    }

    return rc;
}
tis-senddata

tis驱动的主要发送函数,我们看一下是如何实现的?

static u32 tis_senddata(const u8 *const data, u32 len)
{
    if (!CONFIG_TCGBIOS)
        return 0;

    u32 rc = 0;
    u32 offset = 0;
    u32 end_loop = 0;
    u16 burst = 0;
    u8 locty = tis_find_active_locality();
    u32 timeout_d = tpm_drivers[TIS_DRIVER_IDX].timeouts[TIS_TIMEOUT_TYPE_D];
    u32 end = timer_calc_usec(timeout_d);

    do {
        while (burst == 0) {
            //循环从TIS_REG_STS读取要发送的字节数
               burst = readl(TIS_REG(locty, TIS_REG_STS)) >> 8;
            if (burst == 0) {
                if (timer_check(end)) {
                    warn_timeout();
                    break;
                }
                yield();
            }
        }

        if (burst == 0) {
            rc = TCG_RESPONSE_TIMEOUT;
            break;
        }

        while (1) {
            //循环向TIS_REG_DATA_FIFO中写入数据相应的数据
            writeb(TIS_REG(locty, TIS_REG_DATA_FIFO), data[offset++]);
            burst--;

            if (burst == 0 || offset == len)
                break;
        }

        if (offset == len)
            end_loop = 1;
    } while (end_loop == 0);

    return rc;
}

总结:就是从TIS_REG_STS中获取要发送的字节数,然后将数据写入TIS_REG_DATA_FIFO之中。

tis_waitdatavalid

这个函数在超时时间内,等待返回数据,如果状态为没有被重新设置就会返回错误。

static u32 tis_waitdatavalid(void)
{
    if (!CONFIG_TCGBIOS)
        return 0;

    u32 rc = 0;
    u8 locty = tis_find_active_locality();
    u32 timeout_c = tpm_drivers[TIS_DRIVER_IDX].timeouts[TIS_TIMEOUT_TYPE_C];

    //tis_wait_sts() 函数等待 TPM 状态寄存器中指定的状态位(在本例中为 TIS_STS_VALID)被设置为可用,在指定的超时期限内设置状态位时返回 0。
    if (tis_wait_sts(locty, timeout_c, TIS_STS_VALID, TIS_STS_VALID) != 0)
        rc = 1;

    return rc;
}
tis_waitrespready

在TIS_REG_STS被设置可用之后,将其设置为TIS_STS_TPM_GO,在超时期限内,tis_wait_sts() 函数等待 TPM 状态寄存器中指定的状态位(在本例中为 TIS_STS_DATA_AVAILABLE)被设置,在指定的超时期限内设置状态位时返回 0。如果在超时期限内状态位未被设置,则 tis_wait_sts() 返回非零的错误代码。

static u32 tis_waitrespready(enum tpmDurationType to_t)
{
    if (!CONFIG_TCGBIOS)
        return 0;

    u32 rc = 0;
    u8 locty = tis_find_active_locality();
    u32 timeout = tpm_drivers[TIS_DRIVER_IDX].durations[to_t];

    writeb(TIS_REG(locty ,TIS_REG_STS), TIS_STS_TPM_GO);

    //超时期限内等待TIS_STS_DATA_AVAILABLE为数据可用
    if (tis_wait_sts(locty, timeout,
                     TIS_STS_DATA_AVAILABLE, TIS_STS_DATA_AVAILABLE) != 0)
        rc = 1;

    return rc;
}
tis_readresp

就是从TIS_REG_DATA_FIFO中读取数据。

static u32 tis_readresp(u8 *buffer, u32 *len)
{
    if (!CONFIG_TCGBIOS)
        return 0;

    u32 rc = 0;
    u32 offset = 0;
    u32 sts;
    u8 locty = tis_find_active_locality();

    while (offset < *len) {
        //读取数据TIS_REG_DATA_FIFO
        buffer[offset] = readb(TIS_REG(locty, TIS_REG_DATA_FIFO));
        offset++;
        sts = readb(TIS_REG(locty, TIS_REG_STS));
        /* data left ? */
        if ((sts & TIS_STS_DATA_AVAILABLE) == 0)
            break;
    }

    *len = offset;

    return rc;
}
tis_ready

将locty设置为activate之后的状态。这样就不用下次继续activate了。

static u32 tis_ready(void)
{
    if (!CONFIG_TCGBIOS)
        return 0;

    u32 rc = 0;
    u8 locty = tis_find_active_locality();
    u32 timeout_b = tpm_drivers[TIS_DRIVER_IDX].timeouts[TIS_TIMEOUT_TYPE_B];

    //恢复TIS_REG_STS寄存器值为TIS_STS_COMMAND_READY,代表命令准备好了
    writeb(TIS_REG(locty, TIS_REG_STS), TIS_STS_COMMAND_READY);
    rc = tis_wait_sts(locty, timeout_b,
                      TIS_STS_COMMAND_READY, TIS_STS_COMMAND_READY);

    return rc;
}

tpm20_get_pcrbanks

该函数定义了一个名为bufferu8类型数组,长度为128,用于存储从TPM获取到的数据。其次调用tpm20_getcapability获取PCR信息,TCG官方解释如下:

TPM_CAP_PCRS是TPM(Trusted Platform Module,可信平台模块)的一种Capability(能力),用于返回当前分配给PCR(Platform Configuration Registers)的信息。该命令的参数property应为零。当TPM收到此命令时,它将始终返回完整的PCR分配,并将moreData设置为NO

返回的信息是一个TPML_PCR_SELECTION结构体,其中包含了每个PCR bank中已分配的PCR的信息。对于每个已实现的PCR bank,该结果中必须包含一个TPMS_PCR_SELECTION结构体。对于每个已实现的哈希算法,该结果中也可以包含一个TPMS_PCR_SELECTION结构体。

通过使用TPM_CAP_PCRS命令,可以查询当前的PCR分配情况,以便在系统中进行更有效的安全策略实施。

SeaBIOS调用 vTPM启动过程

struct tpm2_res_getcapability {
    struct tpm_rsp_header hdr;
    u8 moreData;
    u32 capability;
    u8 data[0]; /* capability dependent data */
} PACKED;

struct tpms_pcr_selection {
    u16 hashAlg;
    u8 sizeOfSelect;
    u8 pcrSelect[0];
} PACKED;

struct tpml_pcr_selection {
    u32 count;
    struct tpms_pcr_selection selections[0];
} PACKED;

static int
tpm20_get_pcrbanks(void)
{
    //用于存储从TPM获取到的数据
    u8 buffer[128];
    struct tpm2_res_getcapability *trg =
      (struct tpm2_res_getcapability *)&buffer;

    //获取PCR,参数property应为零,TPM收到此命令时,它将始终返回完整的PCR分配,并将moreData设置为NO
    int ret = tpm20_getcapability(TPM2_CAP_PCRS, 0, 8, &trg->hdr,
                                  sizeof(buffer));
    if (ret)
        return ret;

    /* defend against (broken) TPM sending packets that are too short */
    u32 resplen = be32_to_cpu(trg->hdr.totlen);
    if (resplen <= offsetof(struct tpm2_res_getcapability, data))
        return -1;

    u32 size = resplen - offsetof(struct tpm2_res_getcapability, data);
    /* we need a valid tpml_pcr_selection up to and including sizeOfSelect */
    if (size < offsetof(struct tpml_pcr_selection, selections) +
               offsetof(struct tpms_pcr_selection, pcrSelect))
        return -1;

    //全局变量,存放PCR bank
    tpm20_pcr_selection = malloc_high(size);
    if (tpm20_pcr_selection) {
        //返回TPML_PCR_SELECTION结构体
        memcpy(tpm20_pcr_selection, &trg->data, size);
        tpm20_pcr_selection_size = size;
    } else {
        warn_noalloc();
        ret = -1;
    }

    return ret;
}

tpm20_getcapability

向TPM发送TPM2_CC_GetCapability请求,返回当前TPM的能力描述信息。上面调用的就是返回对应的PCR属性以及对应的数量。下图时TCG规范中的可以使用TPM2_CC_GetCapability获取信息的命令。

SeaBIOS调用 vTPM启动过程

static int
tpm20_getcapability(u32 capability, u32 property, u32 count,
                    struct tpm_rsp_header *rsp, u32 rsize)
{
    struct tpm2_req_getcapability trg = {
        .hdr.tag = cpu_to_be16(TPM2_ST_NO_SESSIONS),
        .hdr.totlen = cpu_to_be32(sizeof(trg)),
        .hdr.ordinal = cpu_to_be32(TPM2_CC_GetCapability),
        .capability = cpu_to_be32(capability),
        .property = cpu_to_be32(property),
        .propertycount = cpu_to_be32(count),
    };

    u32 resp_size = rsize;
    int ret = tpmhw_transmit(0, &trg.hdr, rsp, &resp_size,
                             TPM_DURATION_TYPE_SHORT);
    ret = (ret ||
           rsize < be32_to_cpu(rsp->totlen)) ? -1 : be32_to_cpu(rsp->errcode);

    dprintf(DEBUG_tcg, "TCGBIOS: Return value from sending TPM2_CC_GetCapability = 0x%08x\n",
            ret);

    return ret;
}

tpm20_write_EfiSpecIdEventStruct

该函数用于添加在描述摘要格式的日志开头添加一条,该日志添加到了ACPI日志中,这个事件包含了TPM所支持的所有hash算法的列表,以及每个hash算法对应的hash大小。

  1. 检查tpm20_pcr_selection,这是TPM返回的PCR选择结构,包含了所选PCR及对应的hash算法信息。如果这个结构不存在或格式错误,函数返回失败。
  2. 填充EfiSpecIdEventStruct事件结构。这个结构包含hash算法列表和大小信息。
  3. 计算EfiSpecIdEventStruct事件结构的总大小,包括大小字段本身和 DigestSizes数组。
  4. 创建一个tpm_log_event结构,设置事件类型为EV_NO_ACTION。
  5. 调用tpm_log_event函数,将EfiSpecIdEventStruct事件记录到ACPI日志中。
static int
tpm20_write_EfiSpecIdEventStruct(void)
{
    if (!tpm20_pcr_selection)
        return -1;

    struct tpms_pcr_selection *sel = tpm20_pcr_selection->selections;
    void *nsel, *end = (void*)tpm20_pcr_selection + tpm20_pcr_selection_size;

    u32 count;
    for (count = 0; count < be32_to_cpu(tpm20_pcr_selection->count); count++) {
        u8 sizeOfSelect = sel->sizeOfSelect;

        nsel = (void*)sel + sizeof(*sel) + sizeOfSelect;
        if (nsel > end)
            break;

        int hsize = tpm20_get_hash_buffersize(be16_to_cpu(sel->hashAlg));
        if (hsize < 0) {
            dprintf(DEBUG_tcg, "TPM is using an unsupported hash: %d\n",
                    be16_to_cpu(sel->hashAlg));
            return -1;
        }

        event.hdr.digestSizes[count].algorithmId = be16_to_cpu(sel->hashAlg);
        event.hdr.digestSizes[count].digestSize = hsize;

        sel = nsel;
    }

    if (sel != end) {
        dprintf(DEBUG_tcg, "Malformed pcr selection structure fron TPM\n");
        return -1;
    }

    event.hdr.numberOfAlgorithms = count;
    int event_size = offsetof(struct TCG_EfiSpecIdEventStruct
                              , digestSizes[count]);
    u32 *vendorInfoSize = (void*)&event + event_size;
    *vendorInfoSize = 0;
    event_size += sizeof(*vendorInfoSize);

    struct tpm_log_entry le = {
        .hdr.eventtype = EV_NO_ACTION,
    };
    return tpm_log_event(&le.hdr, SHA1_BUFSIZE, &event, event_size);
}

tpm_smbios_measure

这个函数的目的是度量SMBIOS表,并记录相应的日志信息。

static void
tpm_smbios_measure(void)
{
    //使用SHA1作为默认Hash算法
    struct pcctes pcctes = {
        .eventid = 1,
        .eventdatasize = SHA1_BUFSIZE,
    };
    //SMBiosAddr地址在maininit过程中platform_hardware_setup中qemu_platform_setup已经被赋值了
    struct smbios_entry_point *sep = SMBiosAddr;

    dprintf(DEBUG_tcg, "TCGBIOS: SMBIOS at %p\n", sep);

    if (!sep)
        return;

    sha1((const u8 *)sep->structure_table_address,
         sep->structure_table_length, pcctes.digest);
    //将SMBIOS表的度量值扩展进入对应PCR 1寄存器中,并记录日志。
    tpm_add_measurement_to_log(1,
                               EV_EVENT_TAG,
                               (const char *)&pcctes, sizeof(pcctes),
                               (u8 *)&pcctes, sizeof(pcctes));
}

tpm_add_measurement_to_log

在函数中,完成对相应PCR的扩展,并记录日志到ACPI日志中。

/*
 * Add a measurement to the log; the data at data_seg:data/length are
 * appended to the TCG_PCClientPCREventStruct
 *
 * Input parameters:
 *  pcrindex   : which PCR to extend
 *  event_type : type of event; specs section on 'Event Types'
 *  event       : pointer to info (e.g., string) to be added to log as-is
 *  event_length: length of the event
 *  hashdata    : pointer to the data to be hashed
 *  hashdata_length: length of the data to be hashed
 */
static void
tpm_add_measurement_to_log(u32 pcrindex, u32 event_type,
                           const char *event, u32 event_length,
                           const u8 *hashdata, u32 hashdata_length)
{
    if (!tpm_is_working())
        return;

    u8 hash[SHA1_BUFSIZE];
    sha1(hashdata, hashdata_length, hash);

    struct tpm_log_entry le = {
        .hdr.pcrindex = pcrindex,
        .hdr.eventtype = event_type,
    };
    int digest_len = tpm_build_digest(&le, hash, 1);
    if (digest_len < 0)
        return;
    //进行TPM扩展
    int ret = tpm_extend(&le, digest_len);
    if (ret) {
        tpm_set_failure();
        return;
    }
    tpm_build_digest(&le, hash, 0);
    //记录日志
    tpm_log_event(&le.hdr, digest_len, event, event_length);
}

tpm_extend

就是调用tpmhw_transmit进行TPM扩展命令的发送,进行TPM扩展。

tpm_add_action

调用放:tpm_add_action(2, "Start Option ROM Scan");

我们看一下该函数:添加了对EV_ACTION事件的度量。并将它扩展到PCR 2寄存器。其实底层也是调用tpm_add_measurement_to_log方法。

// Add an EV_ACTION measurement to the list of measurements
static void
tpm_add_action(u32 pcrIndex, const char *string)
{
    u32 len = strlen(string);
    tpm_add_measurement_to_log(pcrIndex, EV_ACTION,
                               string, len, (u8 *)string, len);
}

tpm_menu

接着TPM相关的操作是,在post阶段->maininit->interactiva_bootmenu,显示TPM配置菜单,包括两个操作,clear TPM和改变PCR banks。

tpm_prepboot

这个函数是接着要执行的,post阶段->prepareboot()->tpm_prepboot(),准备要进入boot阶段了执行的,获取授权认证密钥。

void
tpm_prepboot(void)
{
    if (!CONFIG_TCGBIOS)
        return;

    switch (TPM_version) {
    case TPM_VERSION_1_2:
        if (TPM_has_physical_presence)
            tpm_simple_cmd(0, TPM_ORD_PhysicalPresence,
                           2, TPM_PP_NOT_PRESENT_LOCK, TPM_DURATION_TYPE_SHORT);
        break;
    case TPM_VERSION_2:
        tpm20_prepboot();
        break;
    }

    tpm_add_action(4, "Calling INT 19h");
    tpm_add_event_separators();
}

tpm20_prepboot如下:先获取随机数,然后获取授权密钥。

static void
tpm20_prepboot(void)
{
    int ret = tpm20_stirrandom();
    if (ret)
         goto err_exit;

    u8 auth[20];
    //获取随机数
    ret = tpm20_getrandom(&auth[0], sizeof(auth));
    if (ret)
        goto err_exit;

    //获取授权密钥
    ret = tpm20_hierarchychangeauth(auth);
    if (ret)
        goto err_exit;

    return;

err_exit:
    dprintf(DEBUG_tcg, "TCGBIOS: TPM malfunctioning (line %d).\n", __LINE__);

    tpm_set_failure();
}

tpm_add_event_separators

向PCRS0-7中添加事件分隔符,以便记录系统启动过程中的事件。

/*
 * Add event separators for PCRs 0 to 7; specs on 'Measuring Boot Events'
 */
static void
tpm_add_event_separators(void)
{
    static const u8 evt_separator[] = {0xff,0xff,0xff,0xff};
    u32 pcrIndex;
    for (pcrIndex = 0; pcrIndex <= 7; pcrIndex++)
        //tpm_add_measurement_to_log函数会计算事件的哈希值,并将其添加到PCR寄存器中。
        tpm_add_measurement_to_log(pcrIndex, EV_SEPARATOR,
                                   NULL, 0,
                                   evt_separator,
                                   sizeof(evt_separator));

tpm_add_bcv

在boot阶段,调用13号中断将磁盘数据读入内存后,调用TPM设备对MBR进行度量。调用流程:boot阶段,do_boot->boot_disk->tpm_add_bcv;

//MBR结构
struct mbr_s {
    u8 code[440];
    // 0x01b8
    u32 diskseg;
    // 0x01bc
    u16 null;
    // 0x01be
    struct partition_s partitions[4];
    // 0x01fe
    u16 signature;
} PACKED; 

tpm_add_bcv(bootdrv, MAKE_FLATPTR(bootseg, 0), 512);

看看度量代码:

void
tpm_add_bcv(u32 bootdrv, const u8 *addr, u32 length)
{
    if (!tpm_is_working())
        return;

    //如果小于512字节,直接返回MBR一般都是第一个扇区512字节,前440字节一般是代码
    if (length < 0x200)
        return;

    const char *string = "Booting BCV device 00h (Floppy)";
    if (bootdrv == 0x80)
        string = "Booting BCV device 80h (HDD)";
    //添加事件度量,往PCR 4中扩展
    tpm_add_action(4, string);

    /* specs: see section 'Hard Disk Device or Hard Disk-Like Devices' */
    /* equivalent to: dd if=/dev/hda ibs=1 count=440 | sha1sum */
    string = "MBR";
    // MBR放在了PCR 寄存器4中
    tpm_add_measurement_to_log(4, EV_IPL,
                               string, strlen(string),
                               addr, 0x1b8);

    /* equivalent to: dd if=/dev/hda ibs=1 count=72 skip=440 | sha1sum */
    //512字节的后面72字节分区表,放在了PCR 5寄存器中
    string = "MBR PARTITION_TABLE";
    tpm_add_measurement_to_log(5, EV_IPL_PARTITION_DATA,
                               string, strlen(string),
                               addr + 0x1b8, 0x48);
}
正文完
 2
landery
版权声明:本站原创文章,由 landery 2023-06-13发表,共计17032字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
评论(一条评论)
landery 评论达人 LV.1
2023-08-15 16:16:03 回复

测试一下评论

 Windows  Chrome  内网IP