diff mbox

[V8,14/14] Allow to provide inital TPM state

Message ID 20110831143625.419705972@linux.vnet.ibm.com
State New
Headers show

Commit Message

Stefan Berger Aug. 31, 2011, 2:36 p.m. UTC
This patch adds a -tpm ...,initstate=...,... command line option to the
TPM's existing options and enables the TPM to be initialized with an
existing state blob. This in turn allows us to simulate TPM manufacturing
and equip the TPM with an endorsement key, certificates and initialize its
NVRAM areas etc.. This step is typically done during manufacturng of the TPM
and/or the (physical) machine.

The initial state can be passed either as file or via a file descriptor. The
encoding of the state can either be binary or in form of a base64-encoded
blob surrounded by tags indicating the start and end.

The intial state can be produced through a yet-to-be-published tpm-authoring
tool.

Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com>

---
 hw/tpm_builtin.c |  123 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 qemu-config.c    |   12 +++++
 qemu-options.hx  |   40 ++++++++++++++++-
 3 files changed, 172 insertions(+), 3 deletions(-)

Comments

Michael S. Tsirkin Sept. 1, 2011, 6:10 p.m. UTC | #1
On Wed, Aug 31, 2011 at 10:36:05AM -0400, Stefan Berger wrote:
> This patch adds a -tpm ...,initstate=...,... command line option to the
> TPM's existing options and enables the TPM to be initialized with an
> existing state blob. This in turn allows us to simulate TPM manufacturing
> and equip the TPM with an endorsement key, certificates and initialize its
> NVRAM areas etc.. This step is typically done during manufacturng of the TPM
> and/or the (physical) machine.
> 
> The initial state can be passed either as file or via a file descriptor. The
> encoding of the state can either be binary or in form of a base64-encoded
> blob surrounded by tags indicating the start and end.
> 
> The intial state can be produced through a yet-to-be-published tpm-authoring
> tool.
> 
> Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com>

I am guessing we get the base64 format from tpmlib?

> ---
>  hw/tpm_builtin.c |  123 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-
>  qemu-config.c    |   12 +++++
>  qemu-options.hx  |   40 ++++++++++++++++-
>  3 files changed, 172 insertions(+), 3 deletions(-)
> 
> Index: qemu-git/hw/tpm_builtin.c
> ===================================================================
> --- qemu-git.orig/hw/tpm_builtin.c
> +++ qemu-git/hw/tpm_builtin.c
> @@ -170,9 +170,16 @@ static struct enckey {
>      AES_KEY tpm_dec_key;
>  } enckey;
>  
> +static int tpm_initstatefd = -1;
> +static bool tpm_initstate_bin;
> +

This hardcodes assumption of a single backend.

>  static int tpm_builtin_load_sized_data_from_bs(BlockDriverState *bs,
>                                                 enum BSEntryType be,
>                                                 TPMSizedBuffer *tsb);
> +static TPM_RESULT tpm_builtin_get_initial_state(unsigned char **data,
> +                                                uint32_t *length,
> +                                                size_t tpm_number,
> +                                                const char *name);
>  
>  
>  
> @@ -1269,7 +1276,7 @@ static TPM_RESULT tpm_builtin_nvram_load
>          *length = permanent_state.size;
>  
>          if (*length == 0) {
> -            rc = TPM_RETRY;
> +            rc = tpm_builtin_get_initial_state(data, length, tpm_number, name);
>          } else {
>              /* keep a copy of the last permanent state */
>              rc = TPM_Malloc(data, *length);
> @@ -1452,6 +1459,94 @@ static TPM_RESULT tpm_builtin_io_getphys
>  }
>  
>  
> +static TPM_RESULT tpm_builtin_get_initial_state(unsigned char **data,
> +                                                uint32_t *length,
> +                                                size_t tpm_number,
> +                                                const char *name)
> +{
> +    TPM_RESULT rc = TPM_RETRY;
> +    uint32_t allocated = 0;
> +    int len, flags;
> +    unsigned char buf[1024];
> +    unsigned char *result = NULL;
> +    size_t result_len;
> +
> +    if (tpm_initstatefd >= 0) {
> +        *data = NULL;
> +        *length = 0;
> +
> +        flags = fcntl(tpm_initstatefd, F_GETFL);
> +        if (flags < 0 ||
> +            fcntl(tpm_initstatefd, F_SETFL, flags & ~O_NONBLOCK) < 0) {
> +            return TPM_FAIL;
> +        }
> +
> +        while (TRUE) {
> +            len = read(tpm_initstatefd, buf, sizeof(buf));
> +
> +            if (len > 0) {
> +                if (len > allocated - *length) {
> +                    allocated = *length + len + 1024;
> +                    if (TPM_Realloc(data, allocated) != TPM_SUCCESS) {
> +                        goto err_exit;
> +                    }
> +                }
> +                memcpy(&(*data)[*length], buf, len);
> +                *length += len;
> +                (*data)[*length] = 0;
> +            } else if (len == 0) {
> +                rc = TPM_SUCCESS;
> +                break;
> +            } else if (len < 0) {
> +                if (errno == EINTR) {
> +                    continue;
> +                }
> +                goto err_exit;
> +            }
> +        }
> +
> +        if (*data == NULL) {
> +            /* nothing read */
> +            rc = TPM_FAIL;
> +            goto err_exit;
> +        }
> +
> +        if (!tpm_initstate_bin) {
> +            if (TPMLIB_DecodeBlob((char *)*data, TPMLIB_BLOB_TYPE_INITSTATE,
> +                                  &result, &result_len) != TPM_SUCCESS) {
> +                goto err_exit;
> +            }
> +            TPM_Free(*data);
> +            *data = result;
> +            *length = result_len;
> +            result = NULL;
> +        }
> +        /* sanity check for the size of the blob */
> +        if (*length > tpmlib_get_prop(TPMPROP_TPM_MAX_NV_SPACE)) {
> +            goto err_exit;
> +        }

Do we really have to hand-craft file reading?
How large is TPMPROP_TPM_MAX_NV_SPACE?
If not too large, we can just allocate that
and do a single fread call?

Or, we rely on glib now - can we use 
g_io_channel_read_to_end () or something like that?



> +        /* have it written into the BlockStorage */
> +        rc = tpm_builtin_nvram_storedata(*data, *length, tpm_number, name);

What if that backend is compiled-out?
link will fail?

> +        if (rc != TPM_SUCCESS) {
> +            goto err_exit;
> +        }
> +    }
> +
> +norm_exit:
> +    close(tpm_initstatefd);
> +    tpm_initstatefd = -1;
> +
> +    return rc;
> +
> +err_exit:
> +    TPM_Free(*data);
> +    *data = NULL;
> +    *length = 0;
> +    TPM_Free(result);
> +
> +    goto norm_exit;
> +}
> +
>  /*****************************************************************/
>  
>  
> @@ -1748,6 +1843,7 @@ static TPMBackend *tpm_builtin_create(Qe
>      const char *value;
>      unsigned char keyvalue[256/8];
>      int keysize = sizeof(keyvalue);
> +    unsigned int offset;
>  
>      driver = g_malloc(sizeof(TPMBackend));
>      if (!driver) {
> @@ -1801,6 +1897,28 @@ static TPMBackend *tpm_builtin_create(Qe
>          enckey.enctype = BS_DIR_ENCTYPE_NONE;
>      }
>  
> +    value = qemu_opt_get(opts, "initstate");
> +    if (value) {
> +        offset = 0;
> +
> +        if (!strncmp(value, "bin:", 4)) {
> +            tpm_initstate_bin = true;
> +            offset = 4;
> +        } else if (!strncmp(value, "base64:", 7)) {
> +            tpm_initstate_bin = false;
> +            offset = 7;
> +        }
> +
> +        if (sscanf(&value[offset], "fd:%d", &tpm_initstatefd) != 1) {
> +            tpm_initstatefd = open(&value[offset], O_RDONLY);
> +            if (tpm_initstatefd < 0) {
> +                fprintf(stderr, "tpm: could not open file '%s' for reading.\n",
> +                        value);
> +                goto err_exit;
> +            }
> +        }
> +    }
> +

Separate options for fd and for file mode would be better.

>      return driver;
>  
>  err_exit:
> @@ -1816,6 +1934,9 @@ static void tpm_builtin_destroy(TPMBacke
>      g_free(driver->id);
>      g_free(driver->model);
>      g_free(driver);
> +
> +    close(tpm_initstatefd);
> +    tpm_initstatefd = -1;
>  }
>  
>  
> Index: qemu-git/qemu-config.c
> ===================================================================
> --- qemu-git.orig/qemu-config.c
> +++ qemu-git/qemu-config.c
> @@ -527,6 +527,12 @@ static QemuOptsList qemu_tpmdev_opts = {
>              .type = QEMU_OPT_STRING,
>              .help = "Data encryption key",
>          },
> +        {
> +            .name = "initstate",
> +            .type = QEMU_OPT_STRING,
> +            .help = "File or file descriptor for reading initial TPM state "
> +                    "from",
> +        },
>          { /* end of list */ }
>      },
>  };
> @@ -556,6 +562,12 @@ static QemuOptsList qemu_tpm_opts = {
>              .type = QEMU_OPT_STRING,
>              .help = "Data encryption key",
>          },
> +        {
> +            .name = "initstate",
> +            .type = QEMU_OPT_STRING,
> +            .help = "File or file descriptor for reading initial TPM state "
> +                    "from",
> +        },
>          { /* end of list */ }
>      },
>  };

I think description should document the magic bin:/base64: etc strings,
or better get rid of them.

> Index: qemu-git/qemu-options.hx
> ===================================================================
> --- qemu-git.orig/qemu-options.hx
> +++ qemu-git/qemu-options.hx
> @@ -1767,8 +1767,10 @@ DEFHEADING(TPM device options:)
>  DEF("tpm", HAS_ARG, QEMU_OPTION_tpm, \
>      "" \
>      "-tpm builtin,path=<path>[,model=<model>][,key=<aes key>]\n" \
> +    "     [,initstate=[bin:|base64:]fd:<fd>|<path>]\n" \
>      "                enable a builtin TPM with state in file in path\n" \
>      "                and encrypt the TPM's state with the given AES key\n" \
> +    "                initstate= path to initial state of TPM; default is base64\n" \
>      "-tpm null       enable a TPM null driver that responds with a fault\n" \
>      "                message to every TPM request\n" \
>      "-tpm model=?    to list available TPM device models\n" \
> @@ -1800,7 +1802,7 @@ Use ? to print all available TPM backend
>  qemu -tpmdev ?
>  @end example
>  
> -@item -tpmdev builtin ,id=@var{id}, path=@var{path} [,key=@var{key}]
> +@item -tpmdev builtin ,id=@var{id}, path=@var{path} [,key=@var{key}] [,initstate=@var{path}]
>  
>  Creates an instance of the built-in TPM.
>  
> @@ -1830,6 +1832,40 @@ using AES-CBC encryption scheme supply t
>  -tpmdev builtin,id=tpm0,path=<path_to_qcow2>,key=aes-cbc:0x1234567890abcdef01234567890abcdef -device tpm-tis,tpmdev=tpm0
>  @end example
>  
> +@option{initstate} specifies the path to a file containing the initial
> +state of the TPM. It can be used to provide the TPM with an EK and certificates
> +for the EK, TPM and Platform. Since the file contains binary data that
> +have to conform to the TPM's layout of data, it must have been created using
> +an approriate authoring tool.
> +
> +The initstate option allows to provide a binary state blob or one that is
> +encode in base 64. The base64-encode state blob must have the format
> +
> +@example
> +-----BEGIN INITSTATE-----
> +<base 64 encoded state>
> +-----END INITSTATE-----
> +@end example
> +
> +The initstate option is only effective when Qemu is started with blank
> +state.
> +
> +The initstate option supports several formats:
> +
> +@table @option
> + @item  [base64:]<path_to_blob>
> + Provide the path to the TPM's initial state blob in base64 format.
> + @item  bin:<path to blob>
> + Provide the path to the TPM's initial state blob in binary format.
> + @item  [base64:]fd:<fd>
> + Provide the base64 formatted initial state via a file descriptor to read from.
> + @item  bin:fd:<fd>
> + Provide the binary initial state via a file descriptor to read from.

The command line is non standard.  E.g. what if the path starts with fd?

> +@end table
> +
> +@option{initstate} is optional.
> +
> +
>  @item -tpmdev null
>  
>  Creates an instance of a TPM null driver that responds to every command
> @@ -1840,7 +1876,7 @@ with a fault message.
>  The short form of a TPM device option is:
>  @table @option
>  
> -@item -tpm @var{backend-type}, path=@var{path} [,model=@var{model}] [,key=@var{key}]
> +@item -tpm @var{backend-type}, path=@var{path} [,model=@var{model}] [,key=@var{key}] [,initstate=@var{path}]
>  @findex -tpm
>  
>  @option{model} specifies the device model. The default device model is a
>
Michael S. Tsirkin Sept. 1, 2011, 7:01 p.m. UTC | #2
On Thu, Sep 01, 2011 at 09:10:24PM +0300, Michael S. Tsirkin wrote:
> Do we really have to hand-craft file reading?
> How large is TPMPROP_TPM_MAX_NV_SPACE?
> If not too large, we can just allocate that
> and do a single fread call?
> 
> Or, we rely on glib now - can we use 
> g_io_channel_read_to_end () or something like that?

Or scanf 'a' conversion - it seems to be a gnu extension
but we rely on gcc anyway.
Stefan Berger Sept. 2, 2011, 3 a.m. UTC | #3
On 09/01/2011 02:10 PM, Michael S. Tsirkin wrote:
> On Wed, Aug 31, 2011 at 10:36:05AM -0400, Stefan Berger wrote:
>> This patch adds a -tpm ...,initstate=...,... command line option to the
>> TPM's existing options and enables the TPM to be initialized with an
>> existing state blob. This in turn allows us to simulate TPM manufacturing
>> and equip the TPM with an endorsement key, certificates and initialize its
>> NVRAM areas etc.. This step is typically done during manufacturng of the TPM
>> and/or the (physical) machine.
>>
>> The initial state can be passed either as file or via a file descriptor. The
>> encoding of the state can either be binary or in form of a base64-encoded
>> blob surrounded by tags indicating the start and end.
>>
>> The intial state can be produced through a yet-to-be-published tpm-authoring
>> tool.
>>
>> Signed-off-by: Stefan Berger<stefanb@linux.vnet.ibm.com>
> I am guessing we get the base64 format from tpmlib?
The tpm-authoring tool uses a libtpms-based TPM to create this initial 
state.

In libvirt I (would like to) have the possibility to execute an external 
script as part of the start of the VM that can create this initial 
state. The output of that program is pipe()ed into Qemu via a 
filedescriptor, where libvirt sets up the pipe and passed one end to the 
program and the other to Qemu. This external script can display other 
information but the data between the begin and end marker of the 
base64-encode state are critical and are finally digested by this patch.
>> ---
>>   hw/tpm_builtin.c |  123 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-
>>   qemu-config.c    |   12 +++++
>>   qemu-options.hx  |   40 ++++++++++++++++-
>>   3 files changed, 172 insertions(+), 3 deletions(-)
>>
>> Index: qemu-git/hw/tpm_builtin.c
>> ===================================================================
>> --- qemu-git.orig/hw/tpm_builtin.c
>> +++ qemu-git/hw/tpm_builtin.c
>> @@ -170,9 +170,16 @@ static struct enckey {
>>       AES_KEY tpm_dec_key;
>>   } enckey;
>>
>> +static int tpm_initstatefd = -1;
>> +static bool tpm_initstate_bin;
>> +
> This hardcodes assumption of a single backend.
>
Yes, there's typically a single TPM in a system.
>>   static int tpm_builtin_load_sized_data_from_bs(BlockDriverState *bs,
>>                                                  enum BSEntryType be,
>>                                                  TPMSizedBuffer *tsb);
>> +static TPM_RESULT tpm_builtin_get_initial_state(unsigned char **data,
>> +                                                uint32_t *length,
>> +                                                size_t tpm_number,
>> +                                                const char *name);
>>
>>
>>
>> @@ -1269,7 +1276,7 @@ static TPM_RESULT tpm_builtin_nvram_load
>>           *length = permanent_state.size;
>>
>>           if (*length == 0) {
>> -            rc = TPM_RETRY;
>> +            rc = tpm_builtin_get_initial_state(data, length, tpm_number, name);
>>           } else {
>>               /* keep a copy of the last permanent state */
>>               rc = TPM_Malloc(data, *length);
>> @@ -1452,6 +1459,94 @@ static TPM_RESULT tpm_builtin_io_getphys
>>   }
>>
>>
>> +static TPM_RESULT tpm_builtin_get_initial_state(unsigned char **data,
>> +                                                uint32_t *length,
>> +                                                size_t tpm_number,
>> +                                                const char *name)
>> +{
>> +    TPM_RESULT rc = TPM_RETRY;
>> +    uint32_t allocated = 0;
>> +    int len, flags;
>> +    unsigned char buf[1024];
>> +    unsigned char *result = NULL;
>> +    size_t result_len;
>> +
>> +    if (tpm_initstatefd>= 0) {
>> +        *data = NULL;
>> +        *length = 0;
>> +
>> +        flags = fcntl(tpm_initstatefd, F_GETFL);
>> +        if (flags<  0 ||
>> +            fcntl(tpm_initstatefd, F_SETFL, flags&  ~O_NONBLOCK)<  0) {
>> +            return TPM_FAIL;
>> +        }
>> +
>> +        while (TRUE) {
>> +            len = read(tpm_initstatefd, buf, sizeof(buf));
>> +
>> +            if (len>  0) {
>> +                if (len>  allocated - *length) {
>> +                    allocated = *length + len + 1024;
>> +                    if (TPM_Realloc(data, allocated) != TPM_SUCCESS) {
>> +                        goto err_exit;
>> +                    }
>> +                }
>> +                memcpy(&(*data)[*length], buf, len);
>> +                *length += len;
>> +                (*data)[*length] = 0;
>> +            } else if (len == 0) {
>> +                rc = TPM_SUCCESS;
>> +                break;
>> +            } else if (len<  0) {
>> +                if (errno == EINTR) {
>> +                    continue;
>> +                }
>> +                goto err_exit;
>> +            }
>> +        }
>> +
>> +        if (*data == NULL) {
>> +            /* nothing read */
>> +            rc = TPM_FAIL;
>> +            goto err_exit;
>> +        }
>> +
>> +        if (!tpm_initstate_bin) {
>> +            if (TPMLIB_DecodeBlob((char *)*data, TPMLIB_BLOB_TYPE_INITSTATE,
>> +&result,&result_len) != TPM_SUCCESS) {
>> +                goto err_exit;
>> +            }
>> +            TPM_Free(*data);
>> +            *data = result;
>> +            *length = result_len;
>> +            result = NULL;
>> +        }
>> +        /* sanity check for the size of the blob */
>> +        if (*length>  tpmlib_get_prop(TPMPROP_TPM_MAX_NV_SPACE)) {
>> +            goto err_exit;
>> +        }
> Do we really have to hand-craft file reading?
> How large is TPMPROP_TPM_MAX_NV_SPACE?
> If not too large, we can just allocate that
> and do a single fread call?
Yes, I could do that, too.
> Or, we rely on glib now - can we use
> g_io_channel_read_to_end () or something like that?
>
>
GIOStatus   g_io_channel_read_to_end        (GIOChannel *channel,
                                              gchar **str_return,
                                              gsize *length,
                                              GError **error);

I'd rather not use it. Presumably it uses g_malloc() to internally 
allocate the str_return. However, the buffer we are allocating in this 
function is given to the libtpms, which in turn will call plain free() 
on it. I don't want this to cause a problem if not g_free() is called on 
this buffer.

>> +        /* have it written into the BlockStorage */
>> +        rc = tpm_builtin_nvram_storedata(*data, *length, tpm_number, name);
> What if that backend is compiled-out?
> link will fail?
No, the called function is in the same file.
>> +        if (rc != TPM_SUCCESS) {
>> +            goto err_exit;
>> +        }
>> +    }
>> +
>> +norm_exit:
>> +    close(tpm_initstatefd);
>> +    tpm_initstatefd = -1;
>> +
>> +    return rc;
>> +
>> +err_exit:
>> +    TPM_Free(*data);
>> +    *data = NULL;
>> +    *length = 0;
>> +    TPM_Free(result);
>> +
>> +    goto norm_exit;
>> +}
>> +
>>   /*****************************************************************/
>>
>>
>> @@ -1748,6 +1843,7 @@ static TPMBackend *tpm_builtin_create(Qe
>>       const char *value;
>>       unsigned char keyvalue[256/8];
>>       int keysize = sizeof(keyvalue);
>> +    unsigned int offset;
>>
>>       driver = g_malloc(sizeof(TPMBackend));
>>       if (!driver) {
>> @@ -1801,6 +1897,28 @@ static TPMBackend *tpm_builtin_create(Qe
>>           enckey.enctype = BS_DIR_ENCTYPE_NONE;
>>       }
>>
>> +    value = qemu_opt_get(opts, "initstate");
>> +    if (value) {
>> +        offset = 0;
>> +
>> +        if (!strncmp(value, "bin:", 4)) {
>> +            tpm_initstate_bin = true;
>> +            offset = 4;
>> +        } else if (!strncmp(value, "base64:", 7)) {
>> +            tpm_initstate_bin = false;
>> +            offset = 7;
>> +        }
>> +
>> +        if (sscanf(&value[offset], "fd:%d",&tpm_initstatefd) != 1) {
>> +            tpm_initstatefd = open(&value[offset], O_RDONLY);
>> +            if (tpm_initstatefd<  0) {
>> +                fprintf(stderr, "tpm: could not open file '%s' for reading.\n",
>> +                        value);
>> +                goto err_exit;
>> +            }
>> +        }
>> +    }
>> +
> Separate options for fd and for file mode would be better.
>
initstate_fd=base64:<file descriptor>
initstate_fd=bin:<file descriptor>
initstate=base64:<path to file>
initstate=bin:<path to file>

Along these lines?

>>       return driver;
>>
>>   err_exit:
>> @@ -1816,6 +1934,9 @@ static void tpm_builtin_destroy(TPMBacke
>>       g_free(driver->id);
>>       g_free(driver->model);
>>       g_free(driver);
>> +
>> +    close(tpm_initstatefd);
>> +    tpm_initstatefd = -1;
>>   }
>>
>>
>> Index: qemu-git/qemu-config.c
>> ===================================================================
>> --- qemu-git.orig/qemu-config.c
>> +++ qemu-git/qemu-config.c
>> @@ -527,6 +527,12 @@ static QemuOptsList qemu_tpmdev_opts = {
>>               .type = QEMU_OPT_STRING,
>>               .help = "Data encryption key",
>>           },
>> +        {
>> +            .name = "initstate",
>> +            .type = QEMU_OPT_STRING,
>> +            .help = "File or file descriptor for reading initial TPM state "
>> +                    "from",
>> +        },
>>           { /* end of list */ }
>>       },
>>   };
>> @@ -556,6 +562,12 @@ static QemuOptsList qemu_tpm_opts = {
>>               .type = QEMU_OPT_STRING,
>>               .help = "Data encryption key",
>>           },
>> +        {
>> +            .name = "initstate",
>> +            .type = QEMU_OPT_STRING,
>> +            .help = "File or file descriptor for reading initial TPM state "
>> +                    "from",
>> +        },
>>           { /* end of list */ }
>>       },
>>   };
> I think description should document the magic bin:/base64: etc strings,
> or better get rid of them.
>
I would like to keep both formats...
>> Index: qemu-git/qemu-options.hx
>> ===================================================================
>> --- qemu-git.orig/qemu-options.hx
>> +++ qemu-git/qemu-options.hx
>> @@ -1767,8 +1767,10 @@ DEFHEADING(TPM device options:)
>>   DEF("tpm", HAS_ARG, QEMU_OPTION_tpm, \
>>       "" \
>>       "-tpm builtin,path=<path>[,model=<model>][,key=<aes key>]\n" \
>> +    "     [,initstate=[bin:|base64:]fd:<fd>|<path>]\n" \
>>       "                enable a builtin TPM with state in file in path\n" \
>>       "                and encrypt the TPM's state with the given AES key\n" \
>> +    "                initstate= path to initial state of TPM; default is base64\n" \
>>       "-tpm null       enable a TPM null driver that responds with a fault\n" \
>>       "                message to every TPM request\n" \
>>       "-tpm model=?    to list available TPM device models\n" \
>> @@ -1800,7 +1802,7 @@ Use ? to print all available TPM backend
>>   qemu -tpmdev ?
>>   @end example
>>
>> -@item -tpmdev builtin ,id=@var{id}, path=@var{path} [,key=@var{key}]
>> +@item -tpmdev builtin ,id=@var{id}, path=@var{path} [,key=@var{key}] [,initstate=@var{path}]
>>
>>   Creates an instance of the built-in TPM.
>>
>> @@ -1830,6 +1832,40 @@ using AES-CBC encryption scheme supply t
>>   -tpmdev builtin,id=tpm0,path=<path_to_qcow2>,key=aes-cbc:0x1234567890abcdef01234567890abcdef -device tpm-tis,tpmdev=tpm0
>>   @end example
>>
>> +@option{initstate} specifies the path to a file containing the initial
>> +state of the TPM. It can be used to provide the TPM with an EK and certificates
>> +for the EK, TPM and Platform. Since the file contains binary data that
>> +have to conform to the TPM's layout of data, it must have been created using
>> +an approriate authoring tool.
>> +
>> +The initstate option allows to provide a binary state blob or one that is
>> +encode in base 64. The base64-encode state blob must have the format
>> +
>> +@example
>> +-----BEGIN INITSTATE-----
>> +<base 64 encoded state>
>> +-----END INITSTATE-----
>> +@end example
>> +
>> +The initstate option is only effective when Qemu is started with blank
>> +state.
>> +
>> +The initstate option supports several formats:
>> +
>> +@table @option
>> + @item  [base64:]<path_to_blob>
>> + Provide the path to the TPM's initial state blob in base64 format.
>> + @item  bin:<path to blob>
>> + Provide the path to the TPM's initial state blob in binary format.
>> + @item  [base64:]fd:<fd>
>> + Provide the base64 formatted initial state via a file descriptor to read from.
>> + @item  bin:fd:<fd>
>> + Provide the binary initial state via a file descriptor to read from.
> The command line is non standard.  E.g. what if the path starts with fd?
>
Yes, that's a problem. Above would require bin:file:<path to blob> to be 
understood.

    Stefan
Michael S. Tsirkin Sept. 4, 2011, 4:38 p.m. UTC | #4
On Thu, Sep 01, 2011 at 11:00:56PM -0400, Stefan Berger wrote:
> >Do we really have to hand-craft file reading?
> >How large is TPMPROP_TPM_MAX_NV_SPACE?
> >If not too large, we can just allocate that
> >and do a single fread call?
> Yes, I could do that, too.
> >Or, we rely on glib now - can we use
> >g_io_channel_read_to_end () or something like that?
> >
> >
> GIOStatus   g_io_channel_read_to_end        (GIOChannel *channel,
>                                              gchar **str_return,
>                                              gsize *length,
>                                              GError **error);
> 
> I'd rather not use it. Presumably it uses g_malloc() to internally
> allocate the str_return. However, the buffer we are allocating in
> this function is given to the libtpms, which in turn will call plain
> free() on it. I don't want this to cause a problem if not g_free()
> is called on this buffer.

Well, copy the data and malloc, or do something else,
must be easier than this manual loop.

> >>+
> >Separate options for fd and for file mode would be better.
> >
> initstate_fd=base64:<file descriptor>
> initstate_fd=bin:<file descriptor>
> initstate=base64:<path to file>
> initstate=bin:<path to file>
> 
> Along these lines?

No

initstate_fd=<file descriptor>
initstate_base64=on/off (or base64/bin if you really expect
	more formats in the future)

and use qemu routines to get the fd so they can be
passed through the monitor later ...


> Yes, that's a problem. Above would require bin:file:<path to blob>
> to be understood.
> 
>    Stefan

So avoid it. Give each option it's own flag.
Stefan Berger Sept. 7, 2011, 2:45 a.m. UTC | #5
On 09/04/2011 12:38 PM, Michael S. Tsirkin wrote:
> On Thu, Sep 01, 2011 at 11:00:56PM -0400, Stefan Berger wrote:
>
> initstate_fd=<file descriptor>
> initstate_base64=on/off (or base64/bin if you really expect
> 	more formats in the future)
>
> and use qemu routines to get the fd so they can be
> passed through the monitor later ...
>
I suppose you mean monitor_get_fd(). That functions seems to only be 
used by net.c so far and if  understand the chain of functions correctly 
that are called with the monitor as parameter it helps in hotplugging 
net devices ? For the TPM I would like to *not* have support for 
hotplugging since that device is supposed to be soldered to the 
motherboard and needs to be initialized through a command sequence by 
the (v)BIOS, so it has to be present early on during machine startup.

   Stefan
Michael S. Tsirkin Sept. 7, 2011, 11:23 a.m. UTC | #6
On Tue, Sep 06, 2011 at 10:45:34PM -0400, Stefan Berger wrote:
> On 09/04/2011 12:38 PM, Michael S. Tsirkin wrote:
> >On Thu, Sep 01, 2011 at 11:00:56PM -0400, Stefan Berger wrote:
> >
> >initstate_fd=<file descriptor>
> >initstate_base64=on/off (or base64/bin if you really expect
> >	more formats in the future)
> >
> >and use qemu routines to get the fd so they can be
> >passed through the monitor later ...
> >
> I suppose you mean monitor_get_fd(). That functions seems to only be
> used by net.c so far and if  understand the chain of functions
> correctly that are called with the monitor as parameter it helps in
> hotplugging net devices ? For the TPM I would like to *not* have
> support for hotplugging since that device is supposed to be soldered
> to the motherboard and needs to be initialized through a command
> sequence by the (v)BIOS, so it has to be present early on during
> machine startup.
> 
>   Stefan

Fine, but let's reuse common functions and save code duplication,
especially parsing functions.
Stefan Berger Sept. 7, 2011, 1:51 p.m. UTC | #7
On 09/07/2011 07:23 AM, Michael S. Tsirkin wrote:
> On Tue, Sep 06, 2011 at 10:45:34PM -0400, Stefan Berger wrote:
>> On 09/04/2011 12:38 PM, Michael S. Tsirkin wrote:
>>> On Thu, Sep 01, 2011 at 11:00:56PM -0400, Stefan Berger wrote:
>>>
>>> initstate_fd=<file descriptor>
>>> initstate_base64=on/off (or base64/bin if you really expect
>>> 	more formats in the future)
>>>
>>> and use qemu routines to get the fd so they can be
>>> passed through the monitor later ...
>>>
>> I suppose you mean monitor_get_fd(). That functions seems to only be
>> used by net.c so far and if  understand the chain of functions
>> correctly that are called with the monitor as parameter it helps in
>> hotplugging net devices ? For the TPM I would like to *not* have
>> support for hotplugging since that device is supposed to be soldered
>> to the motherboard and needs to be initialized through a command
>> sequence by the (v)BIOS, so it has to be present early on during
>> machine startup.
>>
>>    Stefan
> Fine, but let's reuse common functions and save code duplication,
> especially parsing functions.
>
When parsing the command line there's no Monitor being passed around. So 
in case of 'net' net_handle_fd_param() (net.c)  ends up not invoking 
monitor_get_fd() but the else branch where strtol() is used to convert 
the fd. Now I won't call net_handle_fd_param() but could introduce 
tpm_handle_fd_param() also calling strtol(). Though that would not make 
me call a common function but duplicating the code there... I don't know 
of another function handling the parsing of fd's. Is there one ? If not, 
I'll also just fall back to using strtol().

   Stefan
Michael S. Tsirkin Sept. 7, 2011, 1:57 p.m. UTC | #8
On Wed, Sep 07, 2011 at 09:51:00AM -0400, Stefan Berger wrote:
> On 09/07/2011 07:23 AM, Michael S. Tsirkin wrote:
> >On Tue, Sep 06, 2011 at 10:45:34PM -0400, Stefan Berger wrote:
> >>On 09/04/2011 12:38 PM, Michael S. Tsirkin wrote:
> >>>On Thu, Sep 01, 2011 at 11:00:56PM -0400, Stefan Berger wrote:
> >>>
> >>>initstate_fd=<file descriptor>
> >>>initstate_base64=on/off (or base64/bin if you really expect
> >>>	more formats in the future)
> >>>
> >>>and use qemu routines to get the fd so they can be
> >>>passed through the monitor later ...
> >>>
> >>I suppose you mean monitor_get_fd(). That functions seems to only be
> >>used by net.c so far and if  understand the chain of functions
> >>correctly that are called with the monitor as parameter it helps in
> >>hotplugging net devices ? For the TPM I would like to *not* have
> >>support for hotplugging since that device is supposed to be soldered
> >>to the motherboard and needs to be initialized through a command
> >>sequence by the (v)BIOS, so it has to be present early on during
> >>machine startup.
> >>
> >>   Stefan
> >Fine, but let's reuse common functions and save code duplication,
> >especially parsing functions.
> >
> When parsing the command line there's no Monitor being passed
> around. So in case of 'net' net_handle_fd_param() (net.c)  ends up
> not invoking monitor_get_fd() but the else branch where strtol() is
> used to convert the fd. Now I won't call net_handle_fd_param() but
> could introduce tpm_handle_fd_param() also calling strtol(). Though
> that would not make me call a common function but duplicating the
> code there... I don't know of another function handling the parsing
> of fd's. Is there one ? If not, I'll also just fall back to using
> strtol().
> 
>   Stefan

We can create a common function and use that for net and tpm.
diff mbox

Patch

Index: qemu-git/hw/tpm_builtin.c
===================================================================
--- qemu-git.orig/hw/tpm_builtin.c
+++ qemu-git/hw/tpm_builtin.c
@@ -170,9 +170,16 @@  static struct enckey {
     AES_KEY tpm_dec_key;
 } enckey;
 
+static int tpm_initstatefd = -1;
+static bool tpm_initstate_bin;
+
 static int tpm_builtin_load_sized_data_from_bs(BlockDriverState *bs,
                                                enum BSEntryType be,
                                                TPMSizedBuffer *tsb);
+static TPM_RESULT tpm_builtin_get_initial_state(unsigned char **data,
+                                                uint32_t *length,
+                                                size_t tpm_number,
+                                                const char *name);
 
 
 
@@ -1269,7 +1276,7 @@  static TPM_RESULT tpm_builtin_nvram_load
         *length = permanent_state.size;
 
         if (*length == 0) {
-            rc = TPM_RETRY;
+            rc = tpm_builtin_get_initial_state(data, length, tpm_number, name);
         } else {
             /* keep a copy of the last permanent state */
             rc = TPM_Malloc(data, *length);
@@ -1452,6 +1459,94 @@  static TPM_RESULT tpm_builtin_io_getphys
 }
 
 
+static TPM_RESULT tpm_builtin_get_initial_state(unsigned char **data,
+                                                uint32_t *length,
+                                                size_t tpm_number,
+                                                const char *name)
+{
+    TPM_RESULT rc = TPM_RETRY;
+    uint32_t allocated = 0;
+    int len, flags;
+    unsigned char buf[1024];
+    unsigned char *result = NULL;
+    size_t result_len;
+
+    if (tpm_initstatefd >= 0) {
+        *data = NULL;
+        *length = 0;
+
+        flags = fcntl(tpm_initstatefd, F_GETFL);
+        if (flags < 0 ||
+            fcntl(tpm_initstatefd, F_SETFL, flags & ~O_NONBLOCK) < 0) {
+            return TPM_FAIL;
+        }
+
+        while (TRUE) {
+            len = read(tpm_initstatefd, buf, sizeof(buf));
+
+            if (len > 0) {
+                if (len > allocated - *length) {
+                    allocated = *length + len + 1024;
+                    if (TPM_Realloc(data, allocated) != TPM_SUCCESS) {
+                        goto err_exit;
+                    }
+                }
+                memcpy(&(*data)[*length], buf, len);
+                *length += len;
+                (*data)[*length] = 0;
+            } else if (len == 0) {
+                rc = TPM_SUCCESS;
+                break;
+            } else if (len < 0) {
+                if (errno == EINTR) {
+                    continue;
+                }
+                goto err_exit;
+            }
+        }
+
+        if (*data == NULL) {
+            /* nothing read */
+            rc = TPM_FAIL;
+            goto err_exit;
+        }
+
+        if (!tpm_initstate_bin) {
+            if (TPMLIB_DecodeBlob((char *)*data, TPMLIB_BLOB_TYPE_INITSTATE,
+                                  &result, &result_len) != TPM_SUCCESS) {
+                goto err_exit;
+            }
+            TPM_Free(*data);
+            *data = result;
+            *length = result_len;
+            result = NULL;
+        }
+        /* sanity check for the size of the blob */
+        if (*length > tpmlib_get_prop(TPMPROP_TPM_MAX_NV_SPACE)) {
+            goto err_exit;
+        }
+        /* have it written into the BlockStorage */
+        rc = tpm_builtin_nvram_storedata(*data, *length, tpm_number, name);
+        if (rc != TPM_SUCCESS) {
+            goto err_exit;
+        }
+    }
+
+norm_exit:
+    close(tpm_initstatefd);
+    tpm_initstatefd = -1;
+
+    return rc;
+
+err_exit:
+    TPM_Free(*data);
+    *data = NULL;
+    *length = 0;
+    TPM_Free(result);
+
+    goto norm_exit;
+}
+
 /*****************************************************************/
 
 
@@ -1748,6 +1843,7 @@  static TPMBackend *tpm_builtin_create(Qe
     const char *value;
     unsigned char keyvalue[256/8];
     int keysize = sizeof(keyvalue);
+    unsigned int offset;
 
     driver = g_malloc(sizeof(TPMBackend));
     if (!driver) {
@@ -1801,6 +1897,28 @@  static TPMBackend *tpm_builtin_create(Qe
         enckey.enctype = BS_DIR_ENCTYPE_NONE;
     }
 
+    value = qemu_opt_get(opts, "initstate");
+    if (value) {
+        offset = 0;
+
+        if (!strncmp(value, "bin:", 4)) {
+            tpm_initstate_bin = true;
+            offset = 4;
+        } else if (!strncmp(value, "base64:", 7)) {
+            tpm_initstate_bin = false;
+            offset = 7;
+        }
+
+        if (sscanf(&value[offset], "fd:%d", &tpm_initstatefd) != 1) {
+            tpm_initstatefd = open(&value[offset], O_RDONLY);
+            if (tpm_initstatefd < 0) {
+                fprintf(stderr, "tpm: could not open file '%s' for reading.\n",
+                        value);
+                goto err_exit;
+            }
+        }
+    }
+
     return driver;
 
 err_exit:
@@ -1816,6 +1934,9 @@  static void tpm_builtin_destroy(TPMBacke
     g_free(driver->id);
     g_free(driver->model);
     g_free(driver);
+
+    close(tpm_initstatefd);
+    tpm_initstatefd = -1;
 }
 
 
Index: qemu-git/qemu-config.c
===================================================================
--- qemu-git.orig/qemu-config.c
+++ qemu-git/qemu-config.c
@@ -527,6 +527,12 @@  static QemuOptsList qemu_tpmdev_opts = {
             .type = QEMU_OPT_STRING,
             .help = "Data encryption key",
         },
+        {
+            .name = "initstate",
+            .type = QEMU_OPT_STRING,
+            .help = "File or file descriptor for reading initial TPM state "
+                    "from",
+        },
         { /* end of list */ }
     },
 };
@@ -556,6 +562,12 @@  static QemuOptsList qemu_tpm_opts = {
             .type = QEMU_OPT_STRING,
             .help = "Data encryption key",
         },
+        {
+            .name = "initstate",
+            .type = QEMU_OPT_STRING,
+            .help = "File or file descriptor for reading initial TPM state "
+                    "from",
+        },
         { /* end of list */ }
     },
 };
Index: qemu-git/qemu-options.hx
===================================================================
--- qemu-git.orig/qemu-options.hx
+++ qemu-git/qemu-options.hx
@@ -1767,8 +1767,10 @@  DEFHEADING(TPM device options:)
 DEF("tpm", HAS_ARG, QEMU_OPTION_tpm, \
     "" \
     "-tpm builtin,path=<path>[,model=<model>][,key=<aes key>]\n" \
+    "     [,initstate=[bin:|base64:]fd:<fd>|<path>]\n" \
     "                enable a builtin TPM with state in file in path\n" \
     "                and encrypt the TPM's state with the given AES key\n" \
+    "                initstate= path to initial state of TPM; default is base64\n" \
     "-tpm null       enable a TPM null driver that responds with a fault\n" \
     "                message to every TPM request\n" \
     "-tpm model=?    to list available TPM device models\n" \
@@ -1800,7 +1802,7 @@  Use ? to print all available TPM backend
 qemu -tpmdev ?
 @end example
 
-@item -tpmdev builtin ,id=@var{id}, path=@var{path} [,key=@var{key}]
+@item -tpmdev builtin ,id=@var{id}, path=@var{path} [,key=@var{key}] [,initstate=@var{path}]
 
 Creates an instance of the built-in TPM.
 
@@ -1830,6 +1832,40 @@  using AES-CBC encryption scheme supply t
 -tpmdev builtin,id=tpm0,path=<path_to_qcow2>,key=aes-cbc:0x1234567890abcdef01234567890abcdef -device tpm-tis,tpmdev=tpm0
 @end example
 
+@option{initstate} specifies the path to a file containing the initial
+state of the TPM. It can be used to provide the TPM with an EK and certificates
+for the EK, TPM and Platform. Since the file contains binary data that
+have to conform to the TPM's layout of data, it must have been created using
+an approriate authoring tool.
+
+The initstate option allows to provide a binary state blob or one that is
+encode in base 64. The base64-encode state blob must have the format
+
+@example
+-----BEGIN INITSTATE-----
+<base 64 encoded state>
+-----END INITSTATE-----
+@end example
+
+The initstate option is only effective when Qemu is started with blank
+state.
+
+The initstate option supports several formats:
+
+@table @option
+ @item  [base64:]<path_to_blob>
+ Provide the path to the TPM's initial state blob in base64 format.
+ @item  bin:<path to blob>
+ Provide the path to the TPM's initial state blob in binary format.
+ @item  [base64:]fd:<fd>
+ Provide the base64 formatted initial state via a file descriptor to read from.
+ @item  bin:fd:<fd>
+ Provide the binary initial state via a file descriptor to read from.
+@end table
+
+@option{initstate} is optional.
+
+
 @item -tpmdev null
 
 Creates an instance of a TPM null driver that responds to every command
@@ -1840,7 +1876,7 @@  with a fault message.
 The short form of a TPM device option is:
 @table @option
 
-@item -tpm @var{backend-type}, path=@var{path} [,model=@var{model}] [,key=@var{key}]
+@item -tpm @var{backend-type}, path=@var{path} [,model=@var{model}] [,key=@var{key}] [,initstate=@var{path}]
 @findex -tpm
 
 @option{model} specifies the device model. The default device model is a