tpm2-software/tpm2-tss

FApi terminates with SIGSEGV when signing with non existing key

G1gg1L3s opened this issue · 1 comments

Hello! I was testing some flows of key creation with FApi and caught a bug with the following scenario:

  1. Try to sign with non-existing EC key
  2. Create that key
  3. Try to sign with it again

After it the library terminates with SIGSEGV because it tries to free an invalid state.

I've reproduced it with debian VM (using microsoft simulator and tabrmd) and debian dockerfile (with swtpm). In both cases I tested tss stack from the debian packages. Additionally, I compiled the latest version from the sources on the VM and the bug still persists.

I'm attaching the dockerfile so you can reproduce the bug with minimal effort.

Dockerfile
FROM debian:bookworm

ARG DEBIAN_FRONTEND=noninteractive

# Install tss stack
RUN apt update && apt-get install -y gdb build-essential tss2 libtss2-dev \
    libtss2-fapi1 tpm2-tools swtpm

# Set tcti to swtpm and disable ek certificate verification
RUN cat <<EOF > /etc/tpm2-tss/fapi-config.json
{
     "profile_name": "P_ECCP256SHA256",
     "profile_dir": "/etc/tpm2-tss/fapi-profiles/",
     "user_dir": "~/.local/share/tpm2-tss/user/keystore",
     "system_dir": "/var/lib/tpm2-tss/system/keystore",
     "tcti": "swtpm:host=127.0.0.1,port=2321",
     "system_pcrs" : [],
     "log_dir" : "/run/tpm2-tss/eventlog/",
     "ek_cert_less": "yes"
}
EOF

# Create minimal reproducible example
# Resource handling is omitted.
RUN cat <<EOF > fapi_sigsegv.c
#include <stdio.h>
#include <assert.h>

#include <tss2/tss2_fapi.h>
#include <tss2/tss2_common.h>

int main()
{
    TSS2_RC r;
    FAPI_CONTEXT *ctx = NULL;

    r = Fapi_Initialize(&ctx, NULL);
    assert(r == TSS2_RC_SUCCESS);

    uint8_t digest[32] = {0};
    uint8_t *signature = NULL;
    size_t signature_size = 0;
    char *public_key = NULL;
    char *cert = NULL;

    printf("\n>> Signing with non existing key\n");
    r = Fapi_Sign(ctx, "/P_ECCP256SHA256/HS/SRK/non_existing_key", NULL, digest, 32, &signature, &signature_size, &public_key, &cert);
    assert(r != TSS2_RC_SUCCESS);

    printf("\n>> Creating the key\n");
    r = Fapi_CreateKey(ctx, "/P_ECCP256SHA256/HS/SRK/non_existing_key", "sign", NULL, NULL);
    assert(r == TSS2_RC_SUCCESS);

    printf("\n>> Signing with the key\n");
    r = Fapi_Sign(ctx, "/P_ECCP256SHA256/HS/SRK/non_existing_key", NULL, digest, 32, &signature, &signature_size, &public_key, &cert);
    assert(r == TSS2_RC_SUCCESS);

    return 0;
}
EOF

RUN gcc fapi_sigsegv.c -ltss2-fapi -o fapi_sigsegv

RUN cat <<EOF > ./entrypoint.sh
#!/bin/bash

echo "> Starting swtpm"
mkdir /tmp/myvtpm
swtpm socket --tpmstate dir=/tmp/myvtpm --tpm2 --ctrl type=tcp,port=2322 \
   --server type=tcp,port=2321 --flags not-need-init &

echo
echo "> FAPI Provisioning"
tss2_provision

echo "> Running example in GDB"
gdb -ex "run" ./fapi_sigsegv
EOF

RUN chmod +x ./entrypoint.sh

ENTRYPOINT ["./entrypoint.sh"]

I've dived into the code a bit and I can say that the issue may be with the state of the state machine after an error:

  1. After the first signing fails, the context.loadKey.prepare_state is left as PREPARE_LOAD_KEY_WAIT_FOR_KEY. At the same time, the context.loadKey.path_list contains a dangling pointer to freed data.
  2. Creating of the key affects only the context.loadKey.path_list, but after the execution it still contains a dangling pointer to freed data.
  3. The second signing starts with context.loadKey.prepare_state = PREPARE_LOAD_KEY_WAIT_FOR_KEY, instead of PREPARE_LOAD_KEY_INIT, so the context.loadKey.path_list is not allocated. But it's freed at the end of PREPARE_LOAD_KEY_WAIT_FOR_KEY. So, esentially, a double free happens with context.loadKey.path_list.

I hope this info will be helpful!

@G1gg1L3s Thank you for debugging the problem. I also checked other sub state machines for this problem and created a PR.