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:
- Try to sign with non-existing EC key
- Create that key
- 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:
- After the first signing fails, the
context.loadKey.prepare_state
is left asPREPARE_LOAD_KEY_WAIT_FOR_KEY
. At the same time, thecontext.loadKey.path_list
contains a dangling pointer to freed data. - Creating of the key affects only the
context.loadKey.path_list
, but after the execution it still contains a dangling pointer to freed data. - The second signing starts with
context.loadKey.prepare_state = PREPARE_LOAD_KEY_WAIT_FOR_KEY
, instead ofPREPARE_LOAD_KEY_INIT
, so thecontext.loadKey.path_list
is not allocated. But it's freed at the end ofPREPARE_LOAD_KEY_WAIT_FOR_KEY
. So, esentially, a double free happens withcontext.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.