Skip to content

Commit

Permalink
Add vTPM support for Linux
Browse files Browse the repository at this point in the history
This patch adds vTPM support for Linux to libcontainer.

The functionality is based on a recently added vtpm_proxy driver, which
is becoming available in Linux 4.8. The driver provides /dev/vtpmx, on
which an ioctl is called that spawns a TPM device in the host's /dev
directory and returns an anonymous file-descriptor on which a TPM emulator
can listen for TPM commands. If we for example created /dev/tpm12 on the
host we make this device available as /dev/tpm0 inside the container. We
also add its major and minor numbers to the device cgroup.

We implement a VTPM class that allows us to create the device and starts
a TPM emulator 'swtpm', to which it passes the anonymous file descriptor.
Besides that, the user can choose to have the vTPM create certificates in
a step that simulates TPM manufacturing. We do this by calling the external
swtpm_setup program, which is part of the swtpm project.

VTPM support is added inside the JSON configuration as follows:

[...]
	"linux": {
		"resources": {
			"devices": [
				{
					"allow": false,
					"access": "rwm"
				}
			] ,
			"vtpms": [
				{
					"statePath": "/tmp/tpm-1",
					"createCerts": true
				},
			]
		},
[...]

This JSON markup makes a single TPM available inside the created container.

o The statPath parameter indicates the directory where the TPM emulator
  'swtpm' writes the state of the TPM device to.
o The createCerts parameter indicates that certificates for the
  TPM are to be created.

The current implementation does not support checkpointing, so checkpointing
of a container with an attached vTPM is prevented.

The swtpm project is available here  : https://github.com/stefanberger/swtpm
The libtpms project is available here: https://github.com/stefanberger/libtpms

Signed-off-by: Stefan Berger <[email protected]>
Signed-off-by: Stefan Berger <[email protected]>
Signed-off-by: Stefan Berger <[email protected]>
  • Loading branch information
stefanberger committed Dec 31, 2019
1 parent 8633da8 commit 64e9c4c
Show file tree
Hide file tree
Showing 9 changed files with 736 additions and 3 deletions.
4 changes: 4 additions & 0 deletions libcontainer/configs/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"os/exec"
"time"

"github.com/opencontainers/runc/libcontainer/vtpm"
"github.com/opencontainers/runtime-spec/specs-go"

"github.com/sirupsen/logrus"
Expand Down Expand Up @@ -200,6 +201,9 @@ type Config struct {
// RootlessCgroups is set when unlikely to have the full access to cgroups.
// When RootlessCgroups is set, cgroups errors are ignored.
RootlessCgroups bool `json:"rootless_cgroups,omitempty"`

// VTPM configuration
VTPMs []*vtpm.VTPM `json:"vtpms"`
}

type Hooks struct {
Expand Down
4 changes: 4 additions & 0 deletions libcontainer/configs/device.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ type Device struct {
// Path to the device.
Path string `json:"path"`

// the name of the device inside the container (optional)
// allows a host device to appear under different name inside container
Devpath string `json:"devpath"`

// Major is the device's major number.
Major int64 `json:"major"`

Expand Down
4 changes: 4 additions & 0 deletions libcontainer/container_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -932,6 +932,10 @@ func (c *linuxContainer) Checkpoint(criuOpts *CriuOpts) error {
// support for doing unprivileged dumps, but the setup of
// rootless containers might make this complicated.

if len(c.config.VTPMs) > 0 {
return fmt.Errorf("Checkpointing with attached vTPM is not supported")
}

// criu 1.5.2 => 10502
if err := c.checkCriuVersion(10502); err != nil {
return err
Expand Down
7 changes: 6 additions & 1 deletion libcontainer/rootfs_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -612,7 +612,12 @@ func bindMountDeviceNode(dest string, node *configs.Device) error {

// Creates the device node in the rootfs of the container.
func createDeviceNode(rootfs string, node *configs.Device, bind bool) error {
dest := filepath.Join(rootfs, node.Path)
var dest string
if node.Devpath != "" {
dest = filepath.Join(rootfs, node.Devpath)
} else {
dest = filepath.Join(rootfs, node.Path)
}
if err := os.MkdirAll(filepath.Dir(dest), 0755); err != nil {
return err
}
Expand Down
4 changes: 4 additions & 0 deletions libcontainer/specconv/spec_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/opencontainers/runc/libcontainer/configs"
"github.com/opencontainers/runc/libcontainer/seccomp"
libcontainerUtils "github.com/opencontainers/runc/libcontainer/utils"
"github.com/opencontainers/runc/libcontainer/vtpm"
"github.com/opencontainers/runtime-spec/specs-go"

"golang.org/x/sys/unix"
Expand Down Expand Up @@ -152,6 +153,7 @@ type CreateOpts struct {
Spec *specs.Spec
RootlessEUID bool
RootlessCgroups bool
VTPMs []*vtpm.VTPM
}

// CreateLibcontainerConfig creates a new libcontainer configuration from a
Expand Down Expand Up @@ -187,6 +189,7 @@ func CreateLibcontainerConfig(opts *CreateOpts) (*configs.Config, error) {
NoNewKeyring: opts.NoNewKeyring,
RootlessEUID: opts.RootlessEUID,
RootlessCgroups: opts.RootlessCgroups,
VTPMs: opts.VTPMs,
}

exists := false
Expand Down Expand Up @@ -608,6 +611,7 @@ func createDevices(spec *specs.Spec, config *configs.Config) error {
device := &configs.Device{
Type: dt,
Path: d.Path,
Devpath: d.Devpath,
Major: d.Major,
Minor: d.Minor,
FileMode: filemode,
Expand Down
2 changes: 2 additions & 0 deletions libcontainer/state_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"path/filepath"

"github.com/opencontainers/runc/libcontainer/configs"
"github.com/opencontainers/runc/libcontainer/vtpm/vtpm-helper"

"github.com/sirupsen/logrus"
"golang.org/x/sys/unix"
Expand Down Expand Up @@ -38,6 +39,7 @@ type containerState interface {
}

func destroy(c *linuxContainer) error {
vtpmhelper.DestroyVTPMs(c.config.VTPMs)
if !c.config.Namespaces.Contains(configs.NEWPID) {
if err := signalAllProcesses(c.cgroupManager, unix.SIGKILL); err != nil {
logrus.Warn(err)
Expand Down
115 changes: 115 additions & 0 deletions libcontainer/vtpm/vtpm-helper/vtpm_helper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// + build linux

package vtpmhelper

import (
"fmt"
"os"
"syscall"

"github.com/opencontainers/runc/libcontainer/configs"
"github.com/opencontainers/runc/libcontainer/vtpm"

"github.com/opencontainers/runtime-spec/specs-go"
"golang.org/x/sys/unix"
)

func addVTPMDevice(spec *specs.Spec, hostpath, devpath string, major, minor uint32) {
var filemode os.FileMode = 0600

device := specs.LinuxDevice{
Path: hostpath,
Devpath: devpath,
Type: "c",
Major: int64(major),
Minor: int64(minor),
FileMode: &filemode,
}
spec.Linux.Devices = append(spec.Linux.Devices, device)

major_p := new(int64)
*major_p = int64(major)
minor_p := new(int64)
*minor_p = int64(minor)

ld := &specs.LinuxDeviceCgroup{
Allow: true,
Type: "c",
Major: major_p,
Minor: minor_p,
Access: "rwm",
}
spec.Linux.Resources.Devices = append(spec.Linux.Resources.Devices, *ld)
}

// Create a VTPM
func CreateVTPM(spec *specs.Spec, vtpmdev *specs.VTPM, devnum int) (*vtpm.VTPM, error) {

vtpm, err := vtpm.NewVTPM(vtpmdev.Statepath, vtpmdev.StatepathIsManaged, vtpmdev.TPMVersion, vtpmdev.CreateCertificates, vtpmdev.Runas, vtpmdev.PcrBanks)
if err != nil {
return nil, err
}

// Start the vTPM process; once stopped, the device pair will
// also disappear
err, vtpm.CreatedStatepath = vtpm.Start()
if err != nil {
return nil, err
}

hostdev := vtpm.GetTPMDevname()
major, minor := vtpm.GetMajorMinor()

devpath := fmt.Sprintf("/dev/tpm%d", devnum)
addVTPMDevice(spec, hostdev, devpath, major, minor)

// check if /dev/vtpmrm%d is available
host_tpmrm := fmt.Sprintf("/dev/tpmrm%d", vtpm.GetTPMDevNum())
if fileInfo, err := os.Lstat(host_tpmrm); err == nil {
if stat_t, ok := fileInfo.Sys().(*syscall.Stat_t); ok {
devNumber := stat_t.Rdev
devpath = fmt.Sprintf("/dev/tpmrm%d", devnum)
addVTPMDevice(spec, host_tpmrm, devpath, unix.Major(devNumber), unix.Minor(devNumber))
}
}

return vtpm, nil
}

func setVTPMHostDevOwner(vtpm *vtpm.VTPM, uid, gid int) error {
hostdev := vtpm.GetTPMDevname()
// adapt ownership of the device since only root can access it
if err := os.Chown(hostdev, uid, gid); err != nil {
return err
}

host_tpmrm := fmt.Sprintf("/dev/tpmrm%d", vtpm.GetTPMDevNum())
if _, err := os.Lstat(host_tpmrm); err == nil {
// adapt ownership of the device since only root can access it
if err := os.Chown(host_tpmrm, uid, gid); err != nil {
return err
}
}

return nil
}

// SetVTPMHostDevsOwner sets the owner of the host devices to the
// container root's mapped user id; if root inside the container is
// uid 1000 on the host, the devices will be owned by uid 1000.
func SetVTPMHostDevsOwner(config *configs.Config, uid, gid int) error {
if uid != 0 {
for _, vtpm := range config.VTPMs {
if err := setVTPMHostDevOwner(vtpm, uid, gid); err != nil {
return err
}
}
}
return nil
}

func DestroyVTPMs(vtpms []*vtpm.VTPM) {
for _, vtpm := range vtpms {
vtpm.Stop(vtpm.CreatedStatepath)
}
}
Loading

0 comments on commit 64e9c4c

Please sign in to comment.