Skip to content

Commit 15b88d8

Browse files
committed
Add vTPM support for Linux
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]>
1 parent 6afc37c commit 15b88d8

File tree

9 files changed

+813
-3
lines changed

9 files changed

+813
-3
lines changed

libcontainer/configs/config.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"os/exec"
88
"time"
99

10+
"github.com/opencontainers/runc/libcontainer/vtpm"
1011
"github.com/opencontainers/runtime-spec/specs-go"
1112
"github.com/pkg/errors"
1213
"github.com/sirupsen/logrus"
@@ -201,6 +202,9 @@ type Config struct {
201202
// RootlessCgroups is set when unlikely to have the full access to cgroups.
202203
// When RootlessCgroups is set, cgroups errors are ignored.
203204
RootlessCgroups bool `json:"rootless_cgroups,omitempty"`
205+
206+
// VTPM configuration
207+
VTPMs []*vtpm.VTPM `json:"vtpms"`
204208
}
205209

206210
type HookName string

libcontainer/configs/device.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ type Device struct {
2121
// Path to the device.
2222
Path string `json:"path"`
2323

24+
// the name of the device inside the container (optional)
25+
// allows a host device to appear under different name inside container
26+
Devpath string `json:"devpath"`
27+
2428
// FileMode permission bits for the device.
2529
FileMode os.FileMode `json:"file_mode"`
2630

libcontainer/container_linux.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -843,6 +843,9 @@ func (c *linuxContainer) Checkpoint(criuOpts *CriuOpts) error {
843843
// support for doing unprivileged dumps, but the setup of
844844
// rootless containers might make this complicated.
845845

846+
if len(c.config.VTPMs) > 0 {
847+
return fmt.Errorf("Checkpointing with attached vTPM is not supported")
848+
}
846849
// We are relying on the CRIU version RPC which was introduced with CRIU 3.0.0
847850
if err := c.checkCriuVersion(30000); err != nil {
848851
return err

libcontainer/rootfs_linux.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -617,7 +617,12 @@ func createDeviceNode(rootfs string, node *configs.Device, bind bool) error {
617617
// The node only exists for cgroup reasons, ignore it here.
618618
return nil
619619
}
620-
dest := filepath.Join(rootfs, node.Path)
620+
var dest string
621+
if node.Devpath != "" {
622+
dest = filepath.Join(rootfs, node.Devpath)
623+
} else {
624+
dest = filepath.Join(rootfs, node.Path)
625+
}
621626
if err := os.MkdirAll(filepath.Dir(dest), 0755); err != nil {
622627
return err
623628
}

libcontainer/specconv/spec_linux.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"github.com/opencontainers/runc/libcontainer/configs"
2020
"github.com/opencontainers/runc/libcontainer/seccomp"
2121
libcontainerUtils "github.com/opencontainers/runc/libcontainer/utils"
22+
"github.com/opencontainers/runc/libcontainer/vtpm"
2223
"github.com/opencontainers/runtime-spec/specs-go"
2324

2425
"golang.org/x/sys/unix"
@@ -200,6 +201,7 @@ type CreateOpts struct {
200201
Spec *specs.Spec
201202
RootlessEUID bool
202203
RootlessCgroups bool
204+
VTPMs []*vtpm.VTPM
203205
}
204206

205207
// CreateLibcontainerConfig creates a new libcontainer configuration from a
@@ -235,6 +237,7 @@ func CreateLibcontainerConfig(opts *CreateOpts) (*configs.Config, error) {
235237
NoNewKeyring: opts.NoNewKeyring,
236238
RootlessEUID: opts.RootlessEUID,
237239
RootlessCgroups: opts.RootlessCgroups,
240+
VTPMs: opts.VTPMs,
238241
}
239242

240243
exists := false
@@ -683,6 +686,7 @@ func createDevices(spec *specs.Spec, config *configs.Config) error {
683686
Minor: d.Minor,
684687
},
685688
Path: d.Path,
689+
Devpath: d.Devpath,
686690
FileMode: filemode,
687691
Uid: uid,
688692
Gid: gid,

libcontainer/state_linux.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"path/filepath"
99

1010
"github.com/opencontainers/runc/libcontainer/configs"
11+
"github.com/opencontainers/runc/libcontainer/vtpm/vtpm-helper"
1112

1213
"github.com/sirupsen/logrus"
1314
"golang.org/x/sys/unix"
@@ -38,6 +39,7 @@ type containerState interface {
3839
}
3940

4041
func destroy(c *linuxContainer) error {
42+
vtpmhelper.DestroyVTPMs(c.config.VTPMs)
4143
if !c.config.Namespaces.Contains(configs.NEWPID) {
4244
if err := signalAllProcesses(c.cgroupManager, unix.SIGKILL); err != nil {
4345
logrus.Warn(err)
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
// + build linux
2+
3+
package vtpmhelper
4+
5+
import (
6+
"fmt"
7+
"os"
8+
"syscall"
9+
10+
"github.com/opencontainers/runc/libcontainer/configs"
11+
"github.com/opencontainers/runc/libcontainer/vtpm"
12+
13+
"github.com/opencontainers/runtime-spec/specs-go"
14+
"golang.org/x/sys/unix"
15+
)
16+
17+
// addVTPMDevice adds a device and cgroup entry to the spec
18+
func addVTPMDevice(spec *specs.Spec, hostpath, devpath string, major, minor uint32) {
19+
var filemode os.FileMode = 0600
20+
21+
device := specs.LinuxDevice{
22+
Path: hostpath,
23+
Devpath: devpath,
24+
Type: "c",
25+
Major: int64(major),
26+
Minor: int64(minor),
27+
FileMode: &filemode,
28+
}
29+
spec.Linux.Devices = append(spec.Linux.Devices, device)
30+
31+
major_p := new(int64)
32+
*major_p = int64(major)
33+
minor_p := new(int64)
34+
*minor_p = int64(minor)
35+
36+
ld := &specs.LinuxDeviceCgroup{
37+
Allow: true,
38+
Type: "c",
39+
Major: major_p,
40+
Minor: minor_p,
41+
Access: "rwm",
42+
}
43+
spec.Linux.Resources.Devices = append(spec.Linux.Resources.Devices, *ld)
44+
}
45+
46+
// CreateVTPM create a VTPM proxy device and starts the TPM emulator with it
47+
func CreateVTPM(spec *specs.Spec, vtpmdev *specs.VTPM, devnum int) (*vtpm.VTPM, error) {
48+
49+
vtpm, err := vtpm.NewVTPM(vtpmdev.Statepath, vtpmdev.StatepathIsManaged, vtpmdev.TPMVersion, vtpmdev.CreateCertificates, vtpmdev.Runas, vtpmdev.PcrBanks)
50+
if err != nil {
51+
return nil, err
52+
}
53+
54+
// Start the vTPM process; once stopped, the device pair will also disappear
55+
vtpm.CreatedStatepath, err = vtpm.Start()
56+
if err != nil {
57+
return nil, err
58+
}
59+
60+
hostdev := vtpm.GetTPMDevname()
61+
major, minor := vtpm.GetMajorMinor()
62+
63+
devpath := fmt.Sprintf("/dev/tpm%d", devnum)
64+
addVTPMDevice(spec, hostdev, devpath, major, minor)
65+
66+
// for TPM 2: check if /dev/vtpmrm%d is available
67+
host_tpmrm := fmt.Sprintf("/dev/tpmrm%d", vtpm.GetTPMDevNum())
68+
if fileInfo, err := os.Lstat(host_tpmrm); err == nil {
69+
if stat_t, ok := fileInfo.Sys().(*syscall.Stat_t); ok {
70+
devNumber := stat_t.Rdev
71+
devpath = fmt.Sprintf("/dev/tpmrm%d", devnum)
72+
addVTPMDevice(spec, host_tpmrm, devpath, unix.Major(devNumber), unix.Minor(devNumber))
73+
}
74+
}
75+
76+
return vtpm, nil
77+
}
78+
79+
func setVTPMHostDevOwner(vtpm *vtpm.VTPM, uid, gid int) error {
80+
hostdev := vtpm.GetTPMDevname()
81+
// adapt ownership of the device since only root can access it
82+
if err := os.Chown(hostdev, uid, gid); err != nil {
83+
return err
84+
}
85+
86+
host_tpmrm := fmt.Sprintf("/dev/tpmrm%d", vtpm.GetTPMDevNum())
87+
if _, err := os.Lstat(host_tpmrm); err == nil {
88+
// adapt ownership of the device since only root can access it
89+
if err := os.Chown(host_tpmrm, uid, gid); err != nil {
90+
return err
91+
}
92+
}
93+
94+
return nil
95+
}
96+
97+
// SetVTPMHostDevsOwner sets the owner of the host devices to the
98+
// container root's mapped user id; if root inside the container is
99+
// uid 1000 on the host, the devices will be owned by uid 1000.
100+
func SetVTPMHostDevsOwner(config *configs.Config, uid, gid int) error {
101+
if uid != 0 {
102+
for _, vtpm := range config.VTPMs {
103+
if err := setVTPMHostDevOwner(vtpm, uid, gid); err != nil {
104+
return err
105+
}
106+
}
107+
}
108+
return nil
109+
}
110+
111+
// DestroyVTPMs stops all VTPMs and cleans up the state directory if necessary
112+
func DestroyVTPMs(vtpms []*vtpm.VTPM) {
113+
for _, vtpm := range vtpms {
114+
vtpm.Stop(vtpm.CreatedStatepath)
115+
}
116+
}

0 commit comments

Comments
 (0)