[v2,1/2] tpm2: add session handle context saving and restoring to the space code
diff mbox

Message ID 1485563558.3229.41.camel@HansenPartnership.com
State New
Headers show

Commit Message

James Bottomley Jan. 28, 2017, 12:32 a.m. UTC
sessions are different from transient objects in that their handles
may not be virtualized (because they're used for some hmac
calculations).  Additionally when a session is context saved, a
vestigial memory remains in the TPM and if it is also flushed, that
will be lost and the session context will refuse to load next time, so
the code is updated to flush only transient objects after a context
save.  Add a separate array (chip->session_tbl) to save and restore
sessions by handle.  Use the failure of a context save or load to
signal that the session has been flushed from the TPM and we can
remove its memory from chip->session_tbl.

Sessions are also isolated during each instance of a tpm space.  This
means that spaces shouldn't be able to see each other's sessions and
is enforced by ensuring that a space user may only refer to sessions
handles that are present in their own chip->session_tbl.  Finally when
a space is closed, all the sessions belonging to it should be flushed
so the handles may be re-used by other spaces.

Note that if we get a session save or load error, all sessions are
effectively flushed.  Even though we restore the session buffer, all
the old sessions will refuse to load after the flush and they'll be
purged from our session memory.  This means that while transient
context handling is still soft in the face of errors, session handling
is hard (any failure of the model means all sessions are lost).

Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com>

---

v2: - add kill space to remove sessions on close
    - update changelog
    - reduce session table to 3 entries
---
 drivers/char/tpm/tpm-chip.c   |   6 +++
 drivers/char/tpm/tpm.h        |   4 +-
 drivers/char/tpm/tpm2-space.c | 112 ++++++++++++++++++++++++++++++++++++++++--
 drivers/char/tpm/tpms-dev.c   |   2 +-
 4 files changed, 118 insertions(+), 6 deletions(-)

Comments

Jarkko Sakkinen Jan. 29, 2017, 9:39 p.m. UTC | #1
On Fri, Jan 27, 2017 at 04:32:38PM -0800, James Bottomley wrote:
> sessions are different from transient objects in that their handles
> may not be virtualized (because they're used for some hmac
> calculations).  Additionally when a session is context saved, a
> vestigial memory remains in the TPM and if it is also flushed, that
> will be lost and the session context will refuse to load next time, so
> the code is updated to flush only transient objects after a context
> save.  Add a separate array (chip->session_tbl) to save and restore
> sessions by handle.  Use the failure of a context save or load to
> signal that the session has been flushed from the TPM and we can
> remove its memory from chip->session_tbl.
> 
> Sessions are also isolated during each instance of a tpm space.  This
> means that spaces shouldn't be able to see each other's sessions and
> is enforced by ensuring that a space user may only refer to sessions
> handles that are present in their own chip->session_tbl.  Finally when
> a space is closed, all the sessions belonging to it should be flushed
> so the handles may be re-used by other spaces.
> 
> Note that if we get a session save or load error, all sessions are
> effectively flushed.  Even though we restore the session buffer, all
> the old sessions will refuse to load after the flush and they'll be
> purged from our session memory.  This means that while transient
> context handling is still soft in the face of errors, session handling
> is hard (any failure of the model means all sessions are lost).
> 
> Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com>
> 
> ---
> 
> v2: - add kill space to remove sessions on close
>     - update changelog
>     - reduce session table to 3 entries
> ---
>  drivers/char/tpm/tpm-chip.c   |   6 +++
>  drivers/char/tpm/tpm.h        |   4 +-
>  drivers/char/tpm/tpm2-space.c | 112 ++++++++++++++++++++++++++++++++++++++++--
>  drivers/char/tpm/tpms-dev.c   |   2 +-
>  4 files changed, 118 insertions(+), 6 deletions(-)
> 
> diff --git a/drivers/char/tpm/tpm-chip.c b/drivers/char/tpm/tpm-chip.c
> index ed4f887..6282ad0 100644
> --- a/drivers/char/tpm/tpm-chip.c
> +++ b/drivers/char/tpm/tpm-chip.c
> @@ -130,6 +130,7 @@ static void tpm_dev_release(struct device *dev)
>  
>  	kfree(chip->log.bios_event_log);
>  	kfree(chip->work_space.context_buf);
> +	kfree(chip->work_space.session_buf);
>  	kfree(chip);
>  }
>  
> @@ -223,6 +224,11 @@ struct tpm_chip *tpm_chip_alloc(struct device *pdev,
>  		rc = -ENOMEM;
>  		goto out;
>  	}
> +	chip->work_space.session_buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
> +	if (!chip->work_space.session_buf) {
> +		rc = -ENOMEM;
> +		goto out;
> +	}
>  
>  	return chip;
>  
> diff --git a/drivers/char/tpm/tpm.h b/drivers/char/tpm/tpm.h
> index c4b8ec9..10c57b9 100644
> --- a/drivers/char/tpm/tpm.h
> +++ b/drivers/char/tpm/tpm.h
> @@ -161,6 +161,8 @@ enum tpm2_cc_attrs {
>  struct tpm_space {
>  	u32 context_tbl[3];
>  	u8 *context_buf;
> +	u32 session_tbl[3];
> +	u8 *session_buf;
>  };
>  
>  enum tpm_chip_flags {
> @@ -583,7 +585,7 @@ unsigned long tpm2_calc_ordinal_duration(struct tpm_chip *chip, u32 ordinal);
>  int tpm2_probe(struct tpm_chip *chip);
>  int tpm2_find_cc(struct tpm_chip *chip, u32 cc);
>  int tpm2_init_space(struct tpm_space *space);
> -void tpm2_del_space(struct tpm_space *space);
> +void tpm2_del_space(struct tpm_chip *chip, struct tpm_space *space);
>  int tpm2_prepare_space(struct tpm_chip *chip, struct tpm_space *space, u32 cc,
>  		       u8 *cmd);
>  int tpm2_commit_space(struct tpm_chip *chip, struct tpm_space *space,
> diff --git a/drivers/char/tpm/tpm2-space.c b/drivers/char/tpm/tpm2-space.c
> index d241c2a..2b3d1ad 100644
> --- a/drivers/char/tpm/tpm2-space.c
> +++ b/drivers/char/tpm/tpm2-space.c
> @@ -32,18 +32,28 @@ struct tpm2_context {
>  	__be16 blob_size;
>  } __packed;
>  
> +static void tpm2_kill_sessions(struct tpm_chip *chip, struct tpm_space *space);
> +
>  int tpm2_init_space(struct tpm_space *space)
>  {
>  	space->context_buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
>  	if (!space->context_buf)
>  		return -ENOMEM;
>  
> +	space->session_buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
> +	if (space->session_buf == NULL) {
> +		kfree(space->context_buf);
> +		return -ENOMEM;
> +	}
> +
>  	return 0;
>  }
>  
> -void tpm2_del_space(struct tpm_space *space)
> +void tpm2_del_space(struct tpm_chip *chip, struct tpm_space *space)
>  {
> +	tpm2_kill_sessions(chip, space);
>  	kfree(space->context_buf);
> +	kfree(space->session_buf);
>  }
>  
>  static int tpm2_load_context(struct tpm_chip *chip, u8 *buf,
> @@ -69,6 +79,10 @@ static int tpm2_load_context(struct tpm_chip *chip, u8 *buf,
>  			 __func__, rc);
>  		tpm_buf_destroy(&tbuf);
>  		return -EFAULT;
> +	} else if (tpm2_rc_value(rc) == TPM2_RC_HANDLE ||
> +		   rc == TPM2_RC_REFERENCE_H0) {
> +		rc = -ENOENT;
> +		tpm_buf_destroy(&tbuf);

1. Why isn't the check in tpm2_save_context sufficient to know that
   session was flushed?
2. Can it really return both TPM_RC_HANDLE and TPM_RC_REFERENCE_H0? 

/Jarkko
--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
James Bottomley Jan. 29, 2017, 10:36 p.m. UTC | #2
On Sun, 2017-01-29 at 23:39 +0200, Jarkko Sakkinen wrote:
> On Fri, Jan 27, 2017 at 04:32:38PM -0800, James Bottomley wrote:
> > sessions are different from transient objects in that their handles
> > may not be virtualized (because they're used for some hmac
> > calculations).  Additionally when a session is context saved, a
> > vestigial memory remains in the TPM and if it is also flushed, that
> > will be lost and the session context will refuse to load next time,
> > so
> > the code is updated to flush only transient objects after a context
> > save.  Add a separate array (chip->session_tbl) to save and restore
> > sessions by handle.  Use the failure of a context save or load to
> > signal that the session has been flushed from the TPM and we can
> > remove its memory from chip->session_tbl.
> > 
> > Sessions are also isolated during each instance of a tpm space. 
> >  This
> > means that spaces shouldn't be able to see each other's sessions
> > and
> > is enforced by ensuring that a space user may only refer to
> > sessions
> > handles that are present in their own chip->session_tbl.  Finally
> > when
> > a space is closed, all the sessions belonging to it should be
> > flushed
> > so the handles may be re-used by other spaces.
> > 
> > Note that if we get a session save or load error, all sessions are
> > effectively flushed.  Even though we restore the session buffer,
> > all
> > the old sessions will refuse to load after the flush and they'll be
> > purged from our session memory.  This means that while transient
> > context handling is still soft in the face of errors, session
> > handling
> > is hard (any failure of the model means all sessions are lost).
> > 
> > Signed-off-by: James Bottomley <
> > James.Bottomley@HansenPartnership.com>
> > 
> > ---
> > 
> > v2: - add kill space to remove sessions on close
> >     - update changelog
> >     - reduce session table to 3 entries
> > ---
> >  drivers/char/tpm/tpm-chip.c   |   6 +++
> >  drivers/char/tpm/tpm.h        |   4 +-
> >  drivers/char/tpm/tpm2-space.c | 112
> > ++++++++++++++++++++++++++++++++++++++++--
> >  drivers/char/tpm/tpms-dev.c   |   2 +-
> >  4 files changed, 118 insertions(+), 6 deletions(-)
> > 
> > diff --git a/drivers/char/tpm/tpm-chip.c b/drivers/char/tpm/tpm
> > -chip.c
> > index ed4f887..6282ad0 100644
> > --- a/drivers/char/tpm/tpm-chip.c
> > +++ b/drivers/char/tpm/tpm-chip.c
> > @@ -130,6 +130,7 @@ static void tpm_dev_release(struct device *dev)
> >  
> >  	kfree(chip->log.bios_event_log);
> >  	kfree(chip->work_space.context_buf);
> > +	kfree(chip->work_space.session_buf);
> >  	kfree(chip);
> >  }
> >  
> > @@ -223,6 +224,11 @@ struct tpm_chip *tpm_chip_alloc(struct device
> > *pdev,
> >  		rc = -ENOMEM;
> >  		goto out;
> >  	}
> > +	chip->work_space.session_buf = kzalloc(PAGE_SIZE,
> > GFP_KERNEL);
> > +	if (!chip->work_space.session_buf) {
> > +		rc = -ENOMEM;
> > +		goto out;
> > +	}
> >  
> >  	return chip;
> >  
> > diff --git a/drivers/char/tpm/tpm.h b/drivers/char/tpm/tpm.h
> > index c4b8ec9..10c57b9 100644
> > --- a/drivers/char/tpm/tpm.h
> > +++ b/drivers/char/tpm/tpm.h
> > @@ -161,6 +161,8 @@ enum tpm2_cc_attrs {
> >  struct tpm_space {
> >  	u32 context_tbl[3];
> >  	u8 *context_buf;
> > +	u32 session_tbl[3];
> > +	u8 *session_buf;
> >  };
> >  
> >  enum tpm_chip_flags {
> > @@ -583,7 +585,7 @@ unsigned long tpm2_calc_ordinal_duration(struct
> > tpm_chip *chip, u32 ordinal);
> >  int tpm2_probe(struct tpm_chip *chip);
> >  int tpm2_find_cc(struct tpm_chip *chip, u32 cc);
> >  int tpm2_init_space(struct tpm_space *space);
> > -void tpm2_del_space(struct tpm_space *space);
> > +void tpm2_del_space(struct tpm_chip *chip, struct tpm_space
> > *space);
> >  int tpm2_prepare_space(struct tpm_chip *chip, struct tpm_space
> > *space, u32 cc,
> >  		       u8 *cmd);
> >  int tpm2_commit_space(struct tpm_chip *chip, struct tpm_space
> > *space,
> > diff --git a/drivers/char/tpm/tpm2-space.c b/drivers/char/tpm/tpm2
> > -space.c
> > index d241c2a..2b3d1ad 100644
> > --- a/drivers/char/tpm/tpm2-space.c
> > +++ b/drivers/char/tpm/tpm2-space.c
> > @@ -32,18 +32,28 @@ struct tpm2_context {
> >  	__be16 blob_size;
> >  } __packed;
> >  
> > +static void tpm2_kill_sessions(struct tpm_chip *chip, struct
> > tpm_space *space);
> > +
> >  int tpm2_init_space(struct tpm_space *space)
> >  {
> >  	space->context_buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
> >  	if (!space->context_buf)
> >  		return -ENOMEM;
> >  
> > +	space->session_buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
> > +	if (space->session_buf == NULL) {
> > +		kfree(space->context_buf);
> > +		return -ENOMEM;
> > +	}
> > +
> >  	return 0;
> >  }
> >  
> > -void tpm2_del_space(struct tpm_space *space)
> > +void tpm2_del_space(struct tpm_chip *chip, struct tpm_space
> > *space)
> >  {
> > +	tpm2_kill_sessions(chip, space);
> >  	kfree(space->context_buf);
> > +	kfree(space->session_buf);
> >  }
> >  
> >  static int tpm2_load_context(struct tpm_chip *chip, u8 *buf,
> > @@ -69,6 +79,10 @@ static int tpm2_load_context(struct tpm_chip
> > *chip, u8 *buf,
> >  			 __func__, rc);
> >  		tpm_buf_destroy(&tbuf);
> >  		return -EFAULT;
> > +	} else if (tpm2_rc_value(rc) == TPM2_RC_HANDLE ||
> > +		   rc == TPM2_RC_REFERENCE_H0) {
> > +		rc = -ENOENT;
> > +		tpm_buf_destroy(&tbuf);
> 
> 1. Why isn't the check in tpm2_save_context sufficient to know that
>    session was flushed?

Because sessions are global.  If something flushes the session not via
our space (like /dev/tpm0) then our only indication will be a load
failure.

> 2. Can it really return both TPM_RC_HANDLE and TPM_RC_REFERENCE_H0? 

Yes, it seems that a session that doesn't exist (because it's been
flushed) then it returns TPM_RC_REFERNCE_H0, but if the context has a
sequence mismatch (because it's been flushed and reloaded) then we get
TPM_RC_HANDLE.

James

> /Jarkko
> 

--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Ken Goldman Jan. 30, 2017, 12:35 a.m. UTC | #3
On 1/27/2017 7:32 PM, James Bottomley wrote:
>
> Sessions are also isolated during each instance of a tpm space.  This
> means that spaces shouldn't be able to see each other's sessions and
> is enforced by ensuring that a space user may only refer to sessions
> handles that are present in their own chip->session_tbl.  Finally when
> a space is closed, all the sessions belonging to it should be flushed
> so the handles may be re-used by other spaces.

This should be true for transient objects as well.



--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
James Bottomley Jan. 30, 2017, 12:55 a.m. UTC | #4
On Sun, 2017-01-29 at 19:35 -0500, Ken Goldman wrote:
> On 1/27/2017 7:32 PM, James Bottomley wrote:
> > 
> > Sessions are also isolated during each instance of a tpm space. 
> >  This means that spaces shouldn't be able to see each other's 
> > sessions and is enforced by ensuring that a space user may only 
> > refer to sessions handles that are present in their own chip
> > ->session_tbl.  Finally when a space is closed, all the sessions 
> > belonging to it should be flushed so the handles may be re-used by
> > other spaces.
> 
> This should be true for transient objects as well.

It is ... it's just this patch only covers sessions.

James


--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Jarkko Sakkinen Jan. 30, 2017, 9:45 p.m. UTC | #5
On Sun, Jan 29, 2017 at 02:36:58PM -0800, James Bottomley wrote:
> On Sun, 2017-01-29 at 23:39 +0200, Jarkko Sakkinen wrote:
> > On Fri, Jan 27, 2017 at 04:32:38PM -0800, James Bottomley wrote:
> > > sessions are different from transient objects in that their handles
> > > may not be virtualized (because they're used for some hmac
> > > calculations).  Additionally when a session is context saved, a
> > > vestigial memory remains in the TPM and if it is also flushed, that
> > > will be lost and the session context will refuse to load next time,
> > > so
> > > the code is updated to flush only transient objects after a context
> > > save.  Add a separate array (chip->session_tbl) to save and restore
> > > sessions by handle.  Use the failure of a context save or load to
> > > signal that the session has been flushed from the TPM and we can
> > > remove its memory from chip->session_tbl.
> > > 
> > > Sessions are also isolated during each instance of a tpm space. 
> > >  This
> > > means that spaces shouldn't be able to see each other's sessions
> > > and
> > > is enforced by ensuring that a space user may only refer to
> > > sessions
> > > handles that are present in their own chip->session_tbl.  Finally
> > > when
> > > a space is closed, all the sessions belonging to it should be
> > > flushed
> > > so the handles may be re-used by other spaces.
> > > 
> > > Note that if we get a session save or load error, all sessions are
> > > effectively flushed.  Even though we restore the session buffer,
> > > all
> > > the old sessions will refuse to load after the flush and they'll be
> > > purged from our session memory.  This means that while transient
> > > context handling is still soft in the face of errors, session
> > > handling
> > > is hard (any failure of the model means all sessions are lost).
> > > 
> > > Signed-off-by: James Bottomley <
> > > James.Bottomley@HansenPartnership.com>
> > > 
> > > ---
> > > 
> > > v2: - add kill space to remove sessions on close
> > >     - update changelog
> > >     - reduce session table to 3 entries
> > > ---
> > >  drivers/char/tpm/tpm-chip.c   |   6 +++
> > >  drivers/char/tpm/tpm.h        |   4 +-
> > >  drivers/char/tpm/tpm2-space.c | 112
> > > ++++++++++++++++++++++++++++++++++++++++--
> > >  drivers/char/tpm/tpms-dev.c   |   2 +-
> > >  4 files changed, 118 insertions(+), 6 deletions(-)
> > > 
> > > diff --git a/drivers/char/tpm/tpm-chip.c b/drivers/char/tpm/tpm
> > > -chip.c
> > > index ed4f887..6282ad0 100644
> > > --- a/drivers/char/tpm/tpm-chip.c
> > > +++ b/drivers/char/tpm/tpm-chip.c
> > > @@ -130,6 +130,7 @@ static void tpm_dev_release(struct device *dev)
> > >  
> > >  	kfree(chip->log.bios_event_log);
> > >  	kfree(chip->work_space.context_buf);
> > > +	kfree(chip->work_space.session_buf);
> > >  	kfree(chip);
> > >  }
> > >  
> > > @@ -223,6 +224,11 @@ struct tpm_chip *tpm_chip_alloc(struct device
> > > *pdev,
> > >  		rc = -ENOMEM;
> > >  		goto out;
> > >  	}
> > > +	chip->work_space.session_buf = kzalloc(PAGE_SIZE,
> > > GFP_KERNEL);
> > > +	if (!chip->work_space.session_buf) {
> > > +		rc = -ENOMEM;
> > > +		goto out;
> > > +	}
> > >  
> > >  	return chip;
> > >  
> > > diff --git a/drivers/char/tpm/tpm.h b/drivers/char/tpm/tpm.h
> > > index c4b8ec9..10c57b9 100644
> > > --- a/drivers/char/tpm/tpm.h
> > > +++ b/drivers/char/tpm/tpm.h
> > > @@ -161,6 +161,8 @@ enum tpm2_cc_attrs {
> > >  struct tpm_space {
> > >  	u32 context_tbl[3];
> > >  	u8 *context_buf;
> > > +	u32 session_tbl[3];
> > > +	u8 *session_buf;
> > >  };
> > >  
> > >  enum tpm_chip_flags {
> > > @@ -583,7 +585,7 @@ unsigned long tpm2_calc_ordinal_duration(struct
> > > tpm_chip *chip, u32 ordinal);
> > >  int tpm2_probe(struct tpm_chip *chip);
> > >  int tpm2_find_cc(struct tpm_chip *chip, u32 cc);
> > >  int tpm2_init_space(struct tpm_space *space);
> > > -void tpm2_del_space(struct tpm_space *space);
> > > +void tpm2_del_space(struct tpm_chip *chip, struct tpm_space
> > > *space);
> > >  int tpm2_prepare_space(struct tpm_chip *chip, struct tpm_space
> > > *space, u32 cc,
> > >  		       u8 *cmd);
> > >  int tpm2_commit_space(struct tpm_chip *chip, struct tpm_space
> > > *space,
> > > diff --git a/drivers/char/tpm/tpm2-space.c b/drivers/char/tpm/tpm2
> > > -space.c
> > > index d241c2a..2b3d1ad 100644
> > > --- a/drivers/char/tpm/tpm2-space.c
> > > +++ b/drivers/char/tpm/tpm2-space.c
> > > @@ -32,18 +32,28 @@ struct tpm2_context {
> > >  	__be16 blob_size;
> > >  } __packed;
> > >  
> > > +static void tpm2_kill_sessions(struct tpm_chip *chip, struct
> > > tpm_space *space);
> > > +
> > >  int tpm2_init_space(struct tpm_space *space)
> > >  {
> > >  	space->context_buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
> > >  	if (!space->context_buf)
> > >  		return -ENOMEM;
> > >  
> > > +	space->session_buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
> > > +	if (space->session_buf == NULL) {
> > > +		kfree(space->context_buf);
> > > +		return -ENOMEM;
> > > +	}
> > > +
> > >  	return 0;
> > >  }
> > >  
> > > -void tpm2_del_space(struct tpm_space *space)
> > > +void tpm2_del_space(struct tpm_chip *chip, struct tpm_space
> > > *space)
> > >  {
> > > +	tpm2_kill_sessions(chip, space);
> > >  	kfree(space->context_buf);
> > > +	kfree(space->session_buf);
> > >  }
> > >  
> > >  static int tpm2_load_context(struct tpm_chip *chip, u8 *buf,
> > > @@ -69,6 +79,10 @@ static int tpm2_load_context(struct tpm_chip
> > > *chip, u8 *buf,
> > >  			 __func__, rc);
> > >  		tpm_buf_destroy(&tbuf);
> > >  		return -EFAULT;
> > > +	} else if (tpm2_rc_value(rc) == TPM2_RC_HANDLE ||
> > > +		   rc == TPM2_RC_REFERENCE_H0) {
> > > +		rc = -ENOENT;
> > > +		tpm_buf_destroy(&tbuf);
> > 
> > 1. Why isn't the check in tpm2_save_context sufficient to know that
> >    session was flushed?
> 
> Because sessions are global.  If something flushes the session not via
> our space (like /dev/tpm0) then our only indication will be a load
> failure.

Right, got it. In this case you would get TPM_RC_REFERENCE_H0 when you
do TPM2_ContextLoad.

> > 2. Can it really return both TPM_RC_HANDLE and TPM_RC_REFERENCE_H0? 
> 
> Yes, it seems that a session that doesn't exist (because it's been
> flushed) then it returns TPM_RC_REFERNCE_H0, but if the context has a
> sequence mismatch (because it's been flushed and reloaded) then we get
> TPM_RC_HANDLE.
> 
> James

If it is flushed, wouldn't you just get TPM_RC_REFERENCE_H0 when you try
to TPM2_ContextLoad? The "and reloaded" does not make sense to me. Once
a session is flushed it cannot be reloaded.

Maybe you meant to say "beause it's been saved and reloaded"? That would
make more sense and fits better what I see in the Commands specification.

/Jarkko
--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Jarkko Sakkinen Jan. 30, 2017, 9:46 p.m. UTC | #6
On Sun, Jan 29, 2017 at 07:35:32PM -0500, Ken Goldman wrote:
> On 1/27/2017 7:32 PM, James Bottomley wrote:
> >
> > Sessions are also isolated during each instance of a tpm space.  This
> > means that spaces shouldn't be able to see each other's sessions and
> > is enforced by ensuring that a space user may only refer to sessions
> > handles that are present in their own chip->session_tbl.  Finally when
> > a space is closed, all the sessions belonging to it should be flushed
> > so the handles may be re-used by other spaces.
> 
> This should be true for transient objects as well.

Transient objects do not need anything special. All transient objects
are flushed after command-response so you implicitly get the isolation.

/Jarkko
--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
James Bottomley Jan. 30, 2017, 10:14 p.m. UTC | #7
On Mon, 2017-01-30 at 23:45 +0200, Jarkko Sakkinen wrote:
> On Sun, Jan 29, 2017 at 02:36:58PM -0800, James Bottomley wrote:
[...]
> > > 2. Can it really return both TPM_RC_HANDLE and
> > > TPM_RC_REFERENCE_H0? 
> > 
> > Yes, it seems that a session that doesn't exist (because it's been
> > flushed) then it returns TPM_RC_REFERNCE_H0, but if the context has 
> > a sequence mismatch (because it's been flushed and reloaded) then 
> > we get TPM_RC_HANDLE.
> > 
> > James
> 
> If it is flushed, wouldn't you just get TPM_RC_REFERENCE_H0 when you 
> try to TPM2_ContextLoad? The "and reloaded" does not make sense to 
> me. Once a session is flushed it cannot be reloaded.
> 
> Maybe you meant to say "beause it's been saved and reloaded"? That 
> would make more sense and fits better what I see in the Commands 
> specification.

I mean if you load a prior context instead of the current one for an
existing handle, effectively a replay, you get TPM_RC_HANDLE.

James

--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Jarkko Sakkinen Jan. 31, 2017, 1:15 p.m. UTC | #8
On Mon, Jan 30, 2017 at 02:14:37PM -0800, James Bottomley wrote:
> On Mon, 2017-01-30 at 23:45 +0200, Jarkko Sakkinen wrote:
> > On Sun, Jan 29, 2017 at 02:36:58PM -0800, James Bottomley wrote:
> [...]
> > > > 2. Can it really return both TPM_RC_HANDLE and
> > > > TPM_RC_REFERENCE_H0? 
> > > 
> > > Yes, it seems that a session that doesn't exist (because it's been
> > > flushed) then it returns TPM_RC_REFERNCE_H0, but if the context has 
> > > a sequence mismatch (because it's been flushed and reloaded) then 
> > > we get TPM_RC_HANDLE.
> > > 
> > > James
> > 
> > If it is flushed, wouldn't you just get TPM_RC_REFERENCE_H0 when you 
> > try to TPM2_ContextLoad? The "and reloaded" does not make sense to 
> > me. Once a session is flushed it cannot be reloaded.
> > 
> > Maybe you meant to say "beause it's been saved and reloaded"? That 
> > would make more sense and fits better what I see in the Commands 
> > specification.
> 
> I mean if you load a prior context instead of the current one for an
> existing handle, effectively a replay, you get TPM_RC_HANDLE.
> 
> James

Thanks for clarifying this.

/Jarkko
--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Jarkko Sakkinen Jan. 31, 2017, 4:21 p.m. UTC | #9
Now that I understand what is happening I'll give some code level
feedback. Overally I think this is in really good shape!

On Fri, Jan 27, 2017 at 04:32:38PM -0800, James Bottomley wrote:
> sessions are different from transient objects in that their handles
> may not be virtualized (because they're used for some hmac
> calculations).  Additionally when a session is context saved, a
> vestigial memory remains in the TPM and if it is also flushed, that
> will be lost and the session context will refuse to load next time, so
> the code is updated to flush only transient objects after a context
> save.  Add a separate array (chip->session_tbl) to save and restore
> sessions by handle.  Use the failure of a context save or load to
> signal that the session has been flushed from the TPM and we can
> remove its memory from chip->session_tbl.
> 
> Sessions are also isolated during each instance of a tpm space.  This
> means that spaces shouldn't be able to see each other's sessions and
> is enforced by ensuring that a space user may only refer to sessions
> handles that are present in their own chip->session_tbl.  Finally when
> a space is closed, all the sessions belonging to it should be flushed
> so the handles may be re-used by other spaces.
> 
> Note that if we get a session save or load error, all sessions are
> effectively flushed.  Even though we restore the session buffer, all
> the old sessions will refuse to load after the flush and they'll be
> purged from our session memory.  This means that while transient
> context handling is still soft in the face of errors, session handling
> is hard (any failure of the model means all sessions are lost).
> 
> Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com>
> 
> ---
> 
> v2: - add kill space to remove sessions on close
>     - update changelog
>     - reduce session table to 3 entries
> ---
>  drivers/char/tpm/tpm-chip.c   |   6 +++
>  drivers/char/tpm/tpm.h        |   4 +-
>  drivers/char/tpm/tpm2-space.c | 112 ++++++++++++++++++++++++++++++++++++++++--
>  drivers/char/tpm/tpms-dev.c   |   2 +-
>  4 files changed, 118 insertions(+), 6 deletions(-)
> 
> diff --git a/drivers/char/tpm/tpm-chip.c b/drivers/char/tpm/tpm-chip.c
> index ed4f887..6282ad0 100644
> --- a/drivers/char/tpm/tpm-chip.c
> +++ b/drivers/char/tpm/tpm-chip.c
> @@ -130,6 +130,7 @@ static void tpm_dev_release(struct device *dev)
>  
>  	kfree(chip->log.bios_event_log);
>  	kfree(chip->work_space.context_buf);
> +	kfree(chip->work_space.session_buf);
>  	kfree(chip);
>  }
>  
> @@ -223,6 +224,11 @@ struct tpm_chip *tpm_chip_alloc(struct device *pdev,
>  		rc = -ENOMEM;
>  		goto out;
>  	}
> +	chip->work_space.session_buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
> +	if (!chip->work_space.session_buf) {
> +		rc = -ENOMEM;
> +		goto out;
> +	}
>  
>  	return chip;
>  
> diff --git a/drivers/char/tpm/tpm.h b/drivers/char/tpm/tpm.h
> index c4b8ec9..10c57b9 100644
> --- a/drivers/char/tpm/tpm.h
> +++ b/drivers/char/tpm/tpm.h
> @@ -161,6 +161,8 @@ enum tpm2_cc_attrs {
>  struct tpm_space {
>  	u32 context_tbl[3];
>  	u8 *context_buf;
> +	u32 session_tbl[3];
> +	u8 *session_buf;
>  };
>  
>  enum tpm_chip_flags {
> @@ -583,7 +585,7 @@ unsigned long tpm2_calc_ordinal_duration(struct tpm_chip *chip, u32 ordinal);
>  int tpm2_probe(struct tpm_chip *chip);
>  int tpm2_find_cc(struct tpm_chip *chip, u32 cc);
>  int tpm2_init_space(struct tpm_space *space);
> -void tpm2_del_space(struct tpm_space *space);
> +void tpm2_del_space(struct tpm_chip *chip, struct tpm_space *space);
>  int tpm2_prepare_space(struct tpm_chip *chip, struct tpm_space *space, u32 cc,
>  		       u8 *cmd);
>  int tpm2_commit_space(struct tpm_chip *chip, struct tpm_space *space,
> diff --git a/drivers/char/tpm/tpm2-space.c b/drivers/char/tpm/tpm2-space.c
> index d241c2a..2b3d1ad 100644
> --- a/drivers/char/tpm/tpm2-space.c
> +++ b/drivers/char/tpm/tpm2-space.c
> @@ -32,18 +32,28 @@ struct tpm2_context {
>  	__be16 blob_size;
>  } __packed;
>  
> +static void tpm2_kill_sessions(struct tpm_chip *chip, struct tpm_space *space);

Move the declaration here so that you don't have to jump back and forth
in the code in order to understand what is happening.

> +
>  int tpm2_init_space(struct tpm_space *space)
>  {
>  	space->context_buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
>  	if (!space->context_buf)
>  		return -ENOMEM;
>  
> +	space->session_buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
> +	if (space->session_buf == NULL) {
> +		kfree(space->context_buf);
> +		return -ENOMEM;
> +	}
> +
>  	return 0;
>  }
>  
> -void tpm2_del_space(struct tpm_space *space)
> +void tpm2_del_space(struct tpm_chip *chip, struct tpm_space *space)
>  {
> +	tpm2_kill_sessions(chip, space);
>  	kfree(space->context_buf);
> +	kfree(space->session_buf);
>  }
>  
>  static int tpm2_load_context(struct tpm_chip *chip, u8 *buf,
> @@ -69,6 +79,10 @@ static int tpm2_load_context(struct tpm_chip *chip, u8 *buf,
>  			 __func__, rc);
>  		tpm_buf_destroy(&tbuf);
>  		return -EFAULT;
> +	} else if (tpm2_rc_value(rc) == TPM2_RC_HANDLE ||
> +		   rc == TPM2_RC_REFERENCE_H0) {
> +		rc = -ENOENT;
> +		tpm_buf_destroy(&tbuf);

Maybe a comment here would make sense (for maitenance purposes) as it
isn't inherently obvious why and when these error codes are used.

>  	} else if (rc > 0) {
>  		dev_warn(&chip->dev, "%s: failed with a TPM error 0x%04X\n",
>  			 __func__, rc);
> @@ -121,12 +135,30 @@ static int tpm2_save_context(struct tpm_chip *chip, u32 handle, u8 *buf,
>  	}
>  
>  	memcpy(&buf[*offset], &tbuf.data[TPM_HEADER_SIZE], body_size);
> -	tpm2_flush_context_cmd(chip, handle, TPM_TRANSMIT_UNLOCKED);
>  	*offset += body_size;
>  	tpm_buf_destroy(&tbuf);
>  	return 0;
>  }
>  
> +static int tpm2_session_add(struct tpm_chip *chip, u32 handle)
> +{
> +	struct tpm_space *space = &chip->work_space;
> +	int i;
> +
> +	for (i = 0; i < ARRAY_SIZE(space->session_tbl); i++)
> +		if (space->session_tbl[i] == 0)
> +			break;
> +	if (i == ARRAY_SIZE(space->session_tbl)) {
> +		dev_err(&chip->dev, "out of session slots\n");

I think using dev_err is not correct here as this is a legit situation.
I would lower this down to dev_dbg.

> +		tpm2_flush_context_cmd(chip, handle, TPM_TRANSMIT_UNLOCKED);
> +		return -ENOMEM;
> +	}
> +
> +	space->session_tbl[i] = handle;
> +
> +	return 0;
> +}
> +
>  static void tpm2_flush_space(struct tpm_chip *chip)
>  {
>  	struct tpm_space *space = &chip->work_space;
> @@ -136,6 +168,25 @@ static void tpm2_flush_space(struct tpm_chip *chip)
>  		if (space->context_tbl[i] && ~space->context_tbl[i])
>  			tpm2_flush_context_cmd(chip, space->context_tbl[i],
>  					       TPM_TRANSMIT_UNLOCKED);
> +
> +	for (i = 0; i < ARRAY_SIZE(space->session_tbl); i++) {
> +		if (space->session_tbl[i])
> +			tpm2_flush_context_cmd(chip, space->session_tbl[i],
> +					       TPM_TRANSMIT_UNLOCKED);
> +	}
> +}
> +
> +static void tpm2_kill_sessions(struct tpm_chip *chip, struct tpm_space *space)
> +{
> +	int i;
> +
> +	mutex_lock(&chip->tpm_mutex);
> +	for (i = 0; i < ARRAY_SIZE(space->session_tbl); i++) {
> +		if (space->session_tbl[i])
> +			tpm2_flush_context_cmd(chip, space->session_tbl[i],
> +					       TPM_TRANSMIT_UNLOCKED);
> +	}
> +	mutex_unlock(&chip->tpm_mutex);
>  }

Please use this in tpm2_flush_space() and take the mutex in those call
sites where you need it.

>  
>  static int tpm2_load_space(struct tpm_chip *chip)
> @@ -161,6 +212,28 @@ static int tpm2_load_space(struct tpm_chip *chip)
>  			return rc;
>  	}
>  
> +	for (i = 0, offset = 0; i < ARRAY_SIZE(space->session_tbl); i++) {
> +		u32 handle;
> +
> +		if (!space->session_tbl[i])
> +			continue;
> +
> +		rc = tpm2_load_context(chip, space->session_buf,
> +				       &offset, &handle);
> +		if (rc == -ENOENT) {
> +			/* load failed, just forget session */
> +			space->session_tbl[i] = 0;
> +		} else if (rc) {
> +			tpm2_flush_space(chip);
> +			return rc;
> +		}
> +		if (handle != space->session_tbl[i]) {
> +			dev_warn(&chip->dev, "session restored to wrong handle\n");
> +			tpm2_flush_space(chip);
> +			return -EFAULT;

I forgot to ask about this corner case. When can it happen?

> +		}
> +	}
> +
>  	return 0;
>  }
>  
> @@ -220,7 +293,10 @@ int tpm2_prepare_space(struct tpm_chip *chip, struct tpm_space *space, u32 cc,
>  
>  	memcpy(&chip->work_space.context_tbl, &space->context_tbl,
>  	       sizeof(space->context_tbl));
> +	memcpy(&chip->work_space.session_tbl, &space->session_tbl,
> +	       sizeof(space->session_tbl));
>  	memcpy(chip->work_space.context_buf, space->context_buf, PAGE_SIZE);
> +	memcpy(chip->work_space.session_buf, space->session_buf, PAGE_SIZE);
>  
>  	rc = tpm2_load_space(chip);
>  	if (rc) {
> @@ -240,7 +316,7 @@ int tpm2_prepare_space(struct tpm_chip *chip, struct tpm_space *space, u32 cc,
>  static int tpm2_map_response(struct tpm_chip *chip, u32 cc, u8 *rsp, size_t len)
>  {
>  	struct tpm_space *space = &chip->work_space;
> -	u32 phandle;
> +	u32 phandle, phandle_type;

Extremely cosmetic comment but I prefer one line per declaration.

>  	u32 vhandle;
>  	u32 attrs;
>  	u32 return_code = get_unaligned_be32((__be32 *)&rsp[6]);
> @@ -259,9 +335,15 @@ static int tpm2_map_response(struct tpm_chip *chip, u32 cc, u8 *rsp, size_t len)
>  		return 0;
>  
>  	phandle = be32_to_cpup((__be32 *)&rsp[TPM_HEADER_SIZE]);
> -	if ((phandle & 0xFF000000) != TPM2_HT_TRANSIENT)
> +	phandle_type = (phandle & 0xFF000000);
> +	if (phandle_type != TPM2_HT_TRANSIENT &&
> +	    phandle_type != TPM2_HT_HMAC_SESSION &&
> +	    phandle_type != TPM2_HT_POLICY_SESSION)
>  		return 0;
>  
> +	if (phandle_type != TPM2_HT_TRANSIENT)
> +		return tpm2_session_add(chip, phandle);
> +
>  	/* Garbage collect a dead context. */
>  	for (i = 0; i < ARRAY_SIZE(space->context_tbl); i++) {
>  		if (space->context_tbl[i] == phandle) {
> @@ -307,9 +389,28 @@ static int tpm2_save_space(struct tpm_chip *chip)
>  		} else if (rc)
>  			return rc;
>  
> +		tpm2_flush_context_cmd(chip, space->context_tbl[i],
> +				       TPM_TRANSMIT_UNLOCKED);
>  		space->context_tbl[i] = ~0;
>  	}
>  
> +	for (i = 0, offset = 0; i < ARRAY_SIZE(space->session_tbl); i++) {
> +		if (!space->session_tbl[i])
> +			continue;
> +
> +		rc = tpm2_save_context(chip, space->session_tbl[i],
> +				       space->session_buf, PAGE_SIZE,
> +				       &offset);
> +
> +		if (rc == -ENOENT) {
> +			/* handle error saving session, just forget it */
> +			space->session_tbl[i] = 0;
> +		} else if (rc < 0) {
> +			tpm2_flush_space(chip);
> +			return rc;
> +		}
> +	}
> +
>  	return 0;
>  }
>  
> @@ -335,7 +436,10 @@ int tpm2_commit_space(struct tpm_chip *chip, struct tpm_space *space,
>  
>  	memcpy(&space->context_tbl, &chip->work_space.context_tbl,
>  	       sizeof(space->context_tbl));
> +	memcpy(&space->session_tbl, &chip->work_space.session_tbl,
> +	       sizeof(space->session_tbl));
>  	memcpy(space->context_buf, chip->work_space.context_buf, PAGE_SIZE);
> +	memcpy(space->session_buf, chip->work_space.session_buf, PAGE_SIZE);
>  
>  	return 0;
>  }
> diff --git a/drivers/char/tpm/tpms-dev.c b/drivers/char/tpm/tpms-dev.c
> index 5720885..4c98db8 100644
> --- a/drivers/char/tpm/tpms-dev.c
> +++ b/drivers/char/tpm/tpms-dev.c
> @@ -39,7 +39,7 @@ static int tpms_release(struct inode *inode, struct file *file)
>  	struct tpms_priv *priv = container_of(fpriv, struct tpms_priv, priv);
>  
>  	tpm_common_release(file, fpriv);
> -	tpm2_del_space(&priv->space);
> +	tpm2_del_space(fpriv->chip, &priv->space);
>  	kfree(priv);
>  
>  	return 0;
> -- 
> 2.6.6
> 

/Jarkko
--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Jarkko Sakkinen Jan. 31, 2017, 4:27 p.m. UTC | #10
On Tue, Jan 31, 2017 at 06:21:15PM +0200, Jarkko Sakkinen wrote:
> Now that I understand what is happening I'll give some code level
> feedback. Overally I think this is in really good shape!
> 
> On Fri, Jan 27, 2017 at 04:32:38PM -0800, James Bottomley wrote:
> > sessions are different from transient objects in that their handles
> > may not be virtualized (because they're used for some hmac
> > calculations).  Additionally when a session is context saved, a
> > vestigial memory remains in the TPM and if it is also flushed, that
> > will be lost and the session context will refuse to load next time, so
> > the code is updated to flush only transient objects after a context
> > save.  Add a separate array (chip->session_tbl) to save and restore
> > sessions by handle.  Use the failure of a context save or load to
> > signal that the session has been flushed from the TPM and we can
> > remove its memory from chip->session_tbl.
> > 
> > Sessions are also isolated during each instance of a tpm space.  This
> > means that spaces shouldn't be able to see each other's sessions and
> > is enforced by ensuring that a space user may only refer to sessions
> > handles that are present in their own chip->session_tbl.  Finally when
> > a space is closed, all the sessions belonging to it should be flushed
> > so the handles may be re-used by other spaces.
> > 
> > Note that if we get a session save or load error, all sessions are
> > effectively flushed.  Even though we restore the session buffer, all
> > the old sessions will refuse to load after the flush and they'll be
> > purged from our session memory.  This means that while transient
> > context handling is still soft in the face of errors, session handling
> > is hard (any failure of the model means all sessions are lost).
> > 
> > Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com>
> > 
> > ---
> > 
> > v2: - add kill space to remove sessions on close
> >     - update changelog
> >     - reduce session table to 3 entries
> > ---
> >  drivers/char/tpm/tpm-chip.c   |   6 +++
> >  drivers/char/tpm/tpm.h        |   4 +-
> >  drivers/char/tpm/tpm2-space.c | 112 ++++++++++++++++++++++++++++++++++++++++--
> >  drivers/char/tpm/tpms-dev.c   |   2 +-
> >  4 files changed, 118 insertions(+), 6 deletions(-)
> > 
> > diff --git a/drivers/char/tpm/tpm-chip.c b/drivers/char/tpm/tpm-chip.c
> > index ed4f887..6282ad0 100644
> > --- a/drivers/char/tpm/tpm-chip.c
> > +++ b/drivers/char/tpm/tpm-chip.c
> > @@ -130,6 +130,7 @@ static void tpm_dev_release(struct device *dev)
> >  
> >  	kfree(chip->log.bios_event_log);
> >  	kfree(chip->work_space.context_buf);
> > +	kfree(chip->work_space.session_buf);
> >  	kfree(chip);
> >  }
> >  
> > @@ -223,6 +224,11 @@ struct tpm_chip *tpm_chip_alloc(struct device *pdev,
> >  		rc = -ENOMEM;
> >  		goto out;
> >  	}
> > +	chip->work_space.session_buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
> > +	if (!chip->work_space.session_buf) {
> > +		rc = -ENOMEM;
> > +		goto out;
> > +	}
> >  
> >  	return chip;
> >  
> > diff --git a/drivers/char/tpm/tpm.h b/drivers/char/tpm/tpm.h
> > index c4b8ec9..10c57b9 100644
> > --- a/drivers/char/tpm/tpm.h
> > +++ b/drivers/char/tpm/tpm.h
> > @@ -161,6 +161,8 @@ enum tpm2_cc_attrs {
> >  struct tpm_space {
> >  	u32 context_tbl[3];
> >  	u8 *context_buf;
> > +	u32 session_tbl[3];
> > +	u8 *session_buf;
> >  };
> >  
> >  enum tpm_chip_flags {
> > @@ -583,7 +585,7 @@ unsigned long tpm2_calc_ordinal_duration(struct tpm_chip *chip, u32 ordinal);
> >  int tpm2_probe(struct tpm_chip *chip);
> >  int tpm2_find_cc(struct tpm_chip *chip, u32 cc);
> >  int tpm2_init_space(struct tpm_space *space);
> > -void tpm2_del_space(struct tpm_space *space);
> > +void tpm2_del_space(struct tpm_chip *chip, struct tpm_space *space);
> >  int tpm2_prepare_space(struct tpm_chip *chip, struct tpm_space *space, u32 cc,
> >  		       u8 *cmd);
> >  int tpm2_commit_space(struct tpm_chip *chip, struct tpm_space *space,
> > diff --git a/drivers/char/tpm/tpm2-space.c b/drivers/char/tpm/tpm2-space.c
> > index d241c2a..2b3d1ad 100644
> > --- a/drivers/char/tpm/tpm2-space.c
> > +++ b/drivers/char/tpm/tpm2-space.c
> > @@ -32,18 +32,28 @@ struct tpm2_context {
> >  	__be16 blob_size;
> >  } __packed;
> >  
> > +static void tpm2_kill_sessions(struct tpm_chip *chip, struct tpm_space *space);
> 
> Move the declaration here so that you don't have to jump back and forth
> in the code in order to understand what is happening.
> 
> > +
> >  int tpm2_init_space(struct tpm_space *space)
> >  {
> >  	space->context_buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
> >  	if (!space->context_buf)
> >  		return -ENOMEM;
> >  
> > +	space->session_buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
> > +	if (space->session_buf == NULL) {
> > +		kfree(space->context_buf);
> > +		return -ENOMEM;
> > +	}
> > +
> >  	return 0;
> >  }
> >  
> > -void tpm2_del_space(struct tpm_space *space)
> > +void tpm2_del_space(struct tpm_chip *chip, struct tpm_space *space)
> >  {
> > +	tpm2_kill_sessions(chip, space);
> >  	kfree(space->context_buf);
> > +	kfree(space->session_buf);
> >  }
> >  
> >  static int tpm2_load_context(struct tpm_chip *chip, u8 *buf,
> > @@ -69,6 +79,10 @@ static int tpm2_load_context(struct tpm_chip *chip, u8 *buf,
> >  			 __func__, rc);
> >  		tpm_buf_destroy(&tbuf);
> >  		return -EFAULT;
> > +	} else if (tpm2_rc_value(rc) == TPM2_RC_HANDLE ||
> > +		   rc == TPM2_RC_REFERENCE_H0) {
> > +		rc = -ENOENT;
> > +		tpm_buf_destroy(&tbuf);
> 
> Maybe a comment here would make sense (for maitenance purposes) as it
> isn't inherently obvious why and when these error codes are used.
> 
> >  	} else if (rc > 0) {
> >  		dev_warn(&chip->dev, "%s: failed with a TPM error 0x%04X\n",
> >  			 __func__, rc);
> > @@ -121,12 +135,30 @@ static int tpm2_save_context(struct tpm_chip *chip, u32 handle, u8 *buf,
> >  	}
> >  
> >  	memcpy(&buf[*offset], &tbuf.data[TPM_HEADER_SIZE], body_size);
> > -	tpm2_flush_context_cmd(chip, handle, TPM_TRANSMIT_UNLOCKED);
> >  	*offset += body_size;
> >  	tpm_buf_destroy(&tbuf);
> >  	return 0;
> >  }
> >  
> > +static int tpm2_session_add(struct tpm_chip *chip, u32 handle)
> > +{
> > +	struct tpm_space *space = &chip->work_space;
> > +	int i;
> > +
> > +	for (i = 0; i < ARRAY_SIZE(space->session_tbl); i++)
> > +		if (space->session_tbl[i] == 0)
> > +			break;
> > +	if (i == ARRAY_SIZE(space->session_tbl)) {
> > +		dev_err(&chip->dev, "out of session slots\n");
> 
> I think using dev_err is not correct here as this is a legit situation.
> I would lower this down to dev_dbg.
> 
> > +		tpm2_flush_context_cmd(chip, handle, TPM_TRANSMIT_UNLOCKED);
> > +		return -ENOMEM;
> > +	}
> > +
> > +	space->session_tbl[i] = handle;
> > +
> > +	return 0;
> > +}
> > +
> >  static void tpm2_flush_space(struct tpm_chip *chip)
> >  {
> >  	struct tpm_space *space = &chip->work_space;
> > @@ -136,6 +168,25 @@ static void tpm2_flush_space(struct tpm_chip *chip)
> >  		if (space->context_tbl[i] && ~space->context_tbl[i])
> >  			tpm2_flush_context_cmd(chip, space->context_tbl[i],
> >  					       TPM_TRANSMIT_UNLOCKED);
> > +
> > +	for (i = 0; i < ARRAY_SIZE(space->session_tbl); i++) {
> > +		if (space->session_tbl[i])
> > +			tpm2_flush_context_cmd(chip, space->session_tbl[i],
> > +					       TPM_TRANSMIT_UNLOCKED);
> > +	}
> > +}
> > +
> > +static void tpm2_kill_sessions(struct tpm_chip *chip, struct tpm_space *space)
> > +{
> > +	int i;
> > +
> > +	mutex_lock(&chip->tpm_mutex);
> > +	for (i = 0; i < ARRAY_SIZE(space->session_tbl); i++) {
> > +		if (space->session_tbl[i])
> > +			tpm2_flush_context_cmd(chip, space->session_tbl[i],
> > +					       TPM_TRANSMIT_UNLOCKED);
> > +	}
> > +	mutex_unlock(&chip->tpm_mutex);
> >  }
> 
> Please use this in tpm2_flush_space() and take the mutex in those call
> sites where you need it.

... and maybe for consistency this should be tpm2_flush_sessions?

/Jarkko
--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
James Bottomley Jan. 31, 2017, 10:55 p.m. UTC | #11
On Tue, 2017-01-31 at 18:21 +0200, Jarkko Sakkinen wrote:
[...]
> Now that I understand what is happening I'll give some code level
> feedback. Overally I think this is in really good shape!

Thanks!

[...]
> On Fri, Jan 27, 2017 at 04:32:38PM -0800, James Bottomley wrote:
> > --- a/drivers/char/tpm/tpm2-space.c
> > +++ b/drivers/char/tpm/tpm2-space.c
> > @@ -32,18 +32,28 @@ struct tpm2_context {
> >  	__be16 blob_size;
> >  } __packed;
> >  
> > +static void tpm2_kill_sessions(struct tpm_chip *chip, struct
> > tpm_space *space);
> 
> Move the declaration here so that you don't have to jump back and 
> forth in the code in order to understand what is happening.

OK, can do that.

> > +
> >  int tpm2_init_space(struct tpm_space *space)
> >  {
> >  	space->context_buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
> >  	if (!space->context_buf)
> >  		return -ENOMEM;
> >  
> > +	space->session_buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
> > +	if (space->session_buf == NULL) {
> > +		kfree(space->context_buf);
> > +		return -ENOMEM;
> > +	}
> > +
> >  	return 0;
> >  }
> >  
> > -void tpm2_del_space(struct tpm_space *space)
> > +void tpm2_del_space(struct tpm_chip *chip, struct tpm_space
> > *space)
> >  {
> > +	tpm2_kill_sessions(chip, space);
> >  	kfree(space->context_buf);
> > +	kfree(space->session_buf);
> >  }
> >  
> >  static int tpm2_load_context(struct tpm_chip *chip, u8 *buf,
> > @@ -69,6 +79,10 @@ static int tpm2_load_context(struct tpm_chip
> > *chip, u8 *buf,
> >  			 __func__, rc);
> >  		tpm_buf_destroy(&tbuf);
> >  		return -EFAULT;
> > +	} else if (tpm2_rc_value(rc) == TPM2_RC_HANDLE ||
> > +		   rc == TPM2_RC_REFERENCE_H0) {
> > +		rc = -ENOENT;
> > +		tpm_buf_destroy(&tbuf);
> 
> Maybe a comment here would make sense (for maitenance purposes) as it
> isn't inherently obvious why and when these error codes are used.

Sure, I can put that in.

> 
> >  	} else if (rc > 0) {
> >  		dev_warn(&chip->dev, "%s: failed with a TPM error
> > 0x%04X\n",
> >  			 __func__, rc);
> > @@ -121,12 +135,30 @@ static int tpm2_save_context(struct tpm_chip
> > *chip, u32 handle, u8 *buf,
> >  	}
> >  
> >  	memcpy(&buf[*offset], &tbuf.data[TPM_HEADER_SIZE],
> > body_size);
> > -	tpm2_flush_context_cmd(chip, handle,
> > TPM_TRANSMIT_UNLOCKED);
> >  	*offset += body_size;
> >  	tpm_buf_destroy(&tbuf);
> >  	return 0;
> >  }
> >  
> > +static int tpm2_session_add(struct tpm_chip *chip, u32 handle)
> > +{
> > +	struct tpm_space *space = &chip->work_space;
> > +	int i;
> > +
> > +	for (i = 0; i < ARRAY_SIZE(space->session_tbl); i++)
> > +		if (space->session_tbl[i] == 0)
> > +			break;
> > +	if (i == ARRAY_SIZE(space->session_tbl)) {
> > +		dev_err(&chip->dev, "out of session slots\n");
> 
> I think using dev_err is not correct here as this is a legit 
> situation. I would lower this down to dev_dbg.

I can do that, but I think this should be higher than debug.  If this
trips, something an application was doing will fail with a non TPM
error and someone may wish to investigate why.  Having a kernel message
would help with that (but they won't see it if it's debug).

I'm also leaning towards the idea that we should actually have one more
_tbl slot than we know the TPM does, so that if someone goes over it's
the TPM that gives them a real TPM out of memory error rather than the
space code returning -ENOMEM.

If you agree, I think it should be four for both sessions_tbl and
context_tbl.

> > +		tpm2_flush_context_cmd(chip, handle,
> > TPM_TRANSMIT_UNLOCKED);
> > +		return -ENOMEM;
> > +	}
> > +
> > +	space->session_tbl[i] = handle;
> > +
> > +	return 0;
> > +}
> > +
> >  static void tpm2_flush_space(struct tpm_chip *chip)
> >  {
> >  	struct tpm_space *space = &chip->work_space;
> > @@ -136,6 +168,25 @@ static void tpm2_flush_space(struct tpm_chip
> > *chip)
> >  		if (space->context_tbl[i] && ~space
> > ->context_tbl[i])
> >  			tpm2_flush_context_cmd(chip, space
> > ->context_tbl[i],
> >  					      
> >  TPM_TRANSMIT_UNLOCKED);
> > +
> > +	for (i = 0; i < ARRAY_SIZE(space->session_tbl); i++) {
> > +		if (space->session_tbl[i])
> > +			tpm2_flush_context_cmd(chip, space
> > ->session_tbl[i],
> > +					      
> >  TPM_TRANSMIT_UNLOCKED);
> > +	}
> > +}
> > +
> > +static void tpm2_kill_sessions(struct tpm_chip *chip, struct
> > tpm_space *space)
> > +{
> > +	int i;
> > +
> > +	mutex_lock(&chip->tpm_mutex);
> > +	for (i = 0; i < ARRAY_SIZE(space->session_tbl); i++) {
> > +		if (space->session_tbl[i])
> > +			tpm2_flush_context_cmd(chip, space
> > ->session_tbl[i],
> > +					      
> >  TPM_TRANSMIT_UNLOCKED);
> > +	}
> > +	mutex_unlock(&chip->tpm_mutex);
> >  }
> 
> Please use this in tpm2_flush_space()

OK.

>  and take the mutex in those call sites where you need it.

I think it's slightly better practice to hide critical sections in call
functions, that way callers don't have so much locking stuff to get
wrong (which is one of our biggest causes of programming faults). 
 However, since this has now become an internal function to tpm2
-space.c, i think moving the locking in this case is fine, so I'll
change it.

> >  static int tpm2_load_space(struct tpm_chip *chip)
> > @@ -161,6 +212,28 @@ static int tpm2_load_space(struct tpm_chip
> > *chip)
> >  			return rc;
> >  	}
> >  
> > +	for (i = 0, offset = 0; i < ARRAY_SIZE(space
> > ->session_tbl); i++) {
> > +		u32 handle;
> > +
> > +		if (!space->session_tbl[i])
> > +			continue;
> > +
> > +		rc = tpm2_load_context(chip, space->session_buf,
> > +				       &offset, &handle);
> > +		if (rc == -ENOENT) {
> > +			/* load failed, just forget session */
> > +			space->session_tbl[i] = 0;
> > +		} else if (rc) {
> > +			tpm2_flush_space(chip);
> > +			return rc;
> > +		}
> > +		if (handle != space->session_tbl[i]) {
> > +			dev_warn(&chip->dev, "session restored to
> > wrong handle\n");
> > +			tpm2_flush_space(chip);
> > +			return -EFAULT;
> 
> I forgot to ask about this corner case. When can it happen?

In theory never.  In practice, if the _tbl and the _buf ever got out of
sync (which hopefully should never happen), so it looks like a useful
assertion check (in the old days I'd have made it a BUG_ON).

> > +		}
> > +	}
> > +
> >  	return 0;
> >  }
> >  
> > @@ -220,7 +293,10 @@ int tpm2_prepare_space(struct tpm_chip *chip,
> > struct tpm_space *space, u32 cc,
> >  
> >  	memcpy(&chip->work_space.context_tbl, &space->context_tbl,
> >  	       sizeof(space->context_tbl));
> > +	memcpy(&chip->work_space.session_tbl, &space->session_tbl,
> > +	       sizeof(space->session_tbl));
> >  	memcpy(chip->work_space.context_buf, space->context_buf,
> > PAGE_SIZE);
> > +	memcpy(chip->work_space.session_buf, space->session_buf,
> > PAGE_SIZE);
> >  
> >  	rc = tpm2_load_space(chip);
> >  	if (rc) {
> > @@ -240,7 +316,7 @@ int tpm2_prepare_space(struct tpm_chip *chip,
> > struct tpm_space *space, u32 cc,
> >  static int tpm2_map_response(struct tpm_chip *chip, u32 cc, u8
> > *rsp, size_t len)
> >  {
> >  	struct tpm_space *space = &chip->work_space;
> > -	u32 phandle;
> > +	u32 phandle, phandle_type;
> 
> Extremely cosmetic comment but I prefer one line per declaration.

Well, OK.

James

--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Patch
diff mbox

diff --git a/drivers/char/tpm/tpm-chip.c b/drivers/char/tpm/tpm-chip.c
index ed4f887..6282ad0 100644
--- a/drivers/char/tpm/tpm-chip.c
+++ b/drivers/char/tpm/tpm-chip.c
@@ -130,6 +130,7 @@  static void tpm_dev_release(struct device *dev)
 
 	kfree(chip->log.bios_event_log);
 	kfree(chip->work_space.context_buf);
+	kfree(chip->work_space.session_buf);
 	kfree(chip);
 }
 
@@ -223,6 +224,11 @@  struct tpm_chip *tpm_chip_alloc(struct device *pdev,
 		rc = -ENOMEM;
 		goto out;
 	}
+	chip->work_space.session_buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
+	if (!chip->work_space.session_buf) {
+		rc = -ENOMEM;
+		goto out;
+	}
 
 	return chip;
 
diff --git a/drivers/char/tpm/tpm.h b/drivers/char/tpm/tpm.h
index c4b8ec9..10c57b9 100644
--- a/drivers/char/tpm/tpm.h
+++ b/drivers/char/tpm/tpm.h
@@ -161,6 +161,8 @@  enum tpm2_cc_attrs {
 struct tpm_space {
 	u32 context_tbl[3];
 	u8 *context_buf;
+	u32 session_tbl[3];
+	u8 *session_buf;
 };
 
 enum tpm_chip_flags {
@@ -583,7 +585,7 @@  unsigned long tpm2_calc_ordinal_duration(struct tpm_chip *chip, u32 ordinal);
 int tpm2_probe(struct tpm_chip *chip);
 int tpm2_find_cc(struct tpm_chip *chip, u32 cc);
 int tpm2_init_space(struct tpm_space *space);
-void tpm2_del_space(struct tpm_space *space);
+void tpm2_del_space(struct tpm_chip *chip, struct tpm_space *space);
 int tpm2_prepare_space(struct tpm_chip *chip, struct tpm_space *space, u32 cc,
 		       u8 *cmd);
 int tpm2_commit_space(struct tpm_chip *chip, struct tpm_space *space,
diff --git a/drivers/char/tpm/tpm2-space.c b/drivers/char/tpm/tpm2-space.c
index d241c2a..2b3d1ad 100644
--- a/drivers/char/tpm/tpm2-space.c
+++ b/drivers/char/tpm/tpm2-space.c
@@ -32,18 +32,28 @@  struct tpm2_context {
 	__be16 blob_size;
 } __packed;
 
+static void tpm2_kill_sessions(struct tpm_chip *chip, struct tpm_space *space);
+
 int tpm2_init_space(struct tpm_space *space)
 {
 	space->context_buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
 	if (!space->context_buf)
 		return -ENOMEM;
 
+	space->session_buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
+	if (space->session_buf == NULL) {
+		kfree(space->context_buf);
+		return -ENOMEM;
+	}
+
 	return 0;
 }
 
-void tpm2_del_space(struct tpm_space *space)
+void tpm2_del_space(struct tpm_chip *chip, struct tpm_space *space)
 {
+	tpm2_kill_sessions(chip, space);
 	kfree(space->context_buf);
+	kfree(space->session_buf);
 }
 
 static int tpm2_load_context(struct tpm_chip *chip, u8 *buf,
@@ -69,6 +79,10 @@  static int tpm2_load_context(struct tpm_chip *chip, u8 *buf,
 			 __func__, rc);
 		tpm_buf_destroy(&tbuf);
 		return -EFAULT;
+	} else if (tpm2_rc_value(rc) == TPM2_RC_HANDLE ||
+		   rc == TPM2_RC_REFERENCE_H0) {
+		rc = -ENOENT;
+		tpm_buf_destroy(&tbuf);
 	} else if (rc > 0) {
 		dev_warn(&chip->dev, "%s: failed with a TPM error 0x%04X\n",
 			 __func__, rc);
@@ -121,12 +135,30 @@  static int tpm2_save_context(struct tpm_chip *chip, u32 handle, u8 *buf,
 	}
 
 	memcpy(&buf[*offset], &tbuf.data[TPM_HEADER_SIZE], body_size);
-	tpm2_flush_context_cmd(chip, handle, TPM_TRANSMIT_UNLOCKED);
 	*offset += body_size;
 	tpm_buf_destroy(&tbuf);
 	return 0;
 }
 
+static int tpm2_session_add(struct tpm_chip *chip, u32 handle)
+{
+	struct tpm_space *space = &chip->work_space;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(space->session_tbl); i++)
+		if (space->session_tbl[i] == 0)
+			break;
+	if (i == ARRAY_SIZE(space->session_tbl)) {
+		dev_err(&chip->dev, "out of session slots\n");
+		tpm2_flush_context_cmd(chip, handle, TPM_TRANSMIT_UNLOCKED);
+		return -ENOMEM;
+	}
+
+	space->session_tbl[i] = handle;
+
+	return 0;
+}
+
 static void tpm2_flush_space(struct tpm_chip *chip)
 {
 	struct tpm_space *space = &chip->work_space;
@@ -136,6 +168,25 @@  static void tpm2_flush_space(struct tpm_chip *chip)
 		if (space->context_tbl[i] && ~space->context_tbl[i])
 			tpm2_flush_context_cmd(chip, space->context_tbl[i],
 					       TPM_TRANSMIT_UNLOCKED);
+
+	for (i = 0; i < ARRAY_SIZE(space->session_tbl); i++) {
+		if (space->session_tbl[i])
+			tpm2_flush_context_cmd(chip, space->session_tbl[i],
+					       TPM_TRANSMIT_UNLOCKED);
+	}
+}
+
+static void tpm2_kill_sessions(struct tpm_chip *chip, struct tpm_space *space)
+{
+	int i;
+
+	mutex_lock(&chip->tpm_mutex);
+	for (i = 0; i < ARRAY_SIZE(space->session_tbl); i++) {
+		if (space->session_tbl[i])
+			tpm2_flush_context_cmd(chip, space->session_tbl[i],
+					       TPM_TRANSMIT_UNLOCKED);
+	}
+	mutex_unlock(&chip->tpm_mutex);
 }
 
 static int tpm2_load_space(struct tpm_chip *chip)
@@ -161,6 +212,28 @@  static int tpm2_load_space(struct tpm_chip *chip)
 			return rc;
 	}
 
+	for (i = 0, offset = 0; i < ARRAY_SIZE(space->session_tbl); i++) {
+		u32 handle;
+
+		if (!space->session_tbl[i])
+			continue;
+
+		rc = tpm2_load_context(chip, space->session_buf,
+				       &offset, &handle);
+		if (rc == -ENOENT) {
+			/* load failed, just forget session */
+			space->session_tbl[i] = 0;
+		} else if (rc) {
+			tpm2_flush_space(chip);
+			return rc;
+		}
+		if (handle != space->session_tbl[i]) {
+			dev_warn(&chip->dev, "session restored to wrong handle\n");
+			tpm2_flush_space(chip);
+			return -EFAULT;
+		}
+	}
+
 	return 0;
 }
 
@@ -220,7 +293,10 @@  int tpm2_prepare_space(struct tpm_chip *chip, struct tpm_space *space, u32 cc,
 
 	memcpy(&chip->work_space.context_tbl, &space->context_tbl,
 	       sizeof(space->context_tbl));
+	memcpy(&chip->work_space.session_tbl, &space->session_tbl,
+	       sizeof(space->session_tbl));
 	memcpy(chip->work_space.context_buf, space->context_buf, PAGE_SIZE);
+	memcpy(chip->work_space.session_buf, space->session_buf, PAGE_SIZE);
 
 	rc = tpm2_load_space(chip);
 	if (rc) {
@@ -240,7 +316,7 @@  int tpm2_prepare_space(struct tpm_chip *chip, struct tpm_space *space, u32 cc,
 static int tpm2_map_response(struct tpm_chip *chip, u32 cc, u8 *rsp, size_t len)
 {
 	struct tpm_space *space = &chip->work_space;
-	u32 phandle;
+	u32 phandle, phandle_type;
 	u32 vhandle;
 	u32 attrs;
 	u32 return_code = get_unaligned_be32((__be32 *)&rsp[6]);
@@ -259,9 +335,15 @@  static int tpm2_map_response(struct tpm_chip *chip, u32 cc, u8 *rsp, size_t len)
 		return 0;
 
 	phandle = be32_to_cpup((__be32 *)&rsp[TPM_HEADER_SIZE]);
-	if ((phandle & 0xFF000000) != TPM2_HT_TRANSIENT)
+	phandle_type = (phandle & 0xFF000000);
+	if (phandle_type != TPM2_HT_TRANSIENT &&
+	    phandle_type != TPM2_HT_HMAC_SESSION &&
+	    phandle_type != TPM2_HT_POLICY_SESSION)
 		return 0;
 
+	if (phandle_type != TPM2_HT_TRANSIENT)
+		return tpm2_session_add(chip, phandle);
+
 	/* Garbage collect a dead context. */
 	for (i = 0; i < ARRAY_SIZE(space->context_tbl); i++) {
 		if (space->context_tbl[i] == phandle) {
@@ -307,9 +389,28 @@  static int tpm2_save_space(struct tpm_chip *chip)
 		} else if (rc)
 			return rc;
 
+		tpm2_flush_context_cmd(chip, space->context_tbl[i],
+				       TPM_TRANSMIT_UNLOCKED);
 		space->context_tbl[i] = ~0;
 	}
 
+	for (i = 0, offset = 0; i < ARRAY_SIZE(space->session_tbl); i++) {
+		if (!space->session_tbl[i])
+			continue;
+
+		rc = tpm2_save_context(chip, space->session_tbl[i],
+				       space->session_buf, PAGE_SIZE,
+				       &offset);
+
+		if (rc == -ENOENT) {
+			/* handle error saving session, just forget it */
+			space->session_tbl[i] = 0;
+		} else if (rc < 0) {
+			tpm2_flush_space(chip);
+			return rc;
+		}
+	}
+
 	return 0;
 }
 
@@ -335,7 +436,10 @@  int tpm2_commit_space(struct tpm_chip *chip, struct tpm_space *space,
 
 	memcpy(&space->context_tbl, &chip->work_space.context_tbl,
 	       sizeof(space->context_tbl));
+	memcpy(&space->session_tbl, &chip->work_space.session_tbl,
+	       sizeof(space->session_tbl));
 	memcpy(space->context_buf, chip->work_space.context_buf, PAGE_SIZE);
+	memcpy(space->session_buf, chip->work_space.session_buf, PAGE_SIZE);
 
 	return 0;
 }
diff --git a/drivers/char/tpm/tpms-dev.c b/drivers/char/tpm/tpms-dev.c
index 5720885..4c98db8 100644
--- a/drivers/char/tpm/tpms-dev.c
+++ b/drivers/char/tpm/tpms-dev.c
@@ -39,7 +39,7 @@  static int tpms_release(struct inode *inode, struct file *file)
 	struct tpms_priv *priv = container_of(fpriv, struct tpms_priv, priv);
 
 	tpm_common_release(file, fpriv);
-	tpm2_del_space(&priv->space);
+	tpm2_del_space(fpriv->chip, &priv->space);
 	kfree(priv);
 
 	return 0;