mirror of
https://github.com/kata-containers/kata-containers.git
synced 2025-04-30 12:44:39 +00:00
virtcontainers: Add support for Secure Execution
Secure Execution is a confidential computing technology on s390x (IBM Z & LinuxONE). Enable the correspondent virtualization technology in QEMU (where it is referred to as "Protected Virtualization"). - Introduce enableProtection and appendProtectionDevice functions for QEMU s390x. - Introduce CheckCmdline to check for "prot_virt=1" being present on the kernel command line. - Introduce CPUFacilities and avilableGuestProtection for hypervisor s390x to check for CPU support. Fixes: #1771 Signed-off-by: Jakob Naucke <jakob.naucke@ibm.com>
This commit is contained in:
parent
78f21710e3
commit
c0c05c73e1
@ -722,6 +722,51 @@ func getHostMemorySizeKb(memInfoPath string) (uint64, error) {
|
|||||||
return 0, fmt.Errorf("unable get MemTotal from %s", memInfoPath)
|
return 0, fmt.Errorf("unable get MemTotal from %s", memInfoPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CheckCmdline checks whether an option or parameter is present in the kernel command line.
|
||||||
|
// Search is case-insensitive.
|
||||||
|
// Takes path to file that contains the kernel command line, desired option, and permitted values
|
||||||
|
// (empty values to check for options).
|
||||||
|
func CheckCmdline(kernelCmdlinePath, searchParam string, searchValues []string) (bool, error) {
|
||||||
|
f, err := os.Open(kernelCmdlinePath)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
// Create check function -- either check for verbatim option
|
||||||
|
// or check for parameter and permitted values
|
||||||
|
var check func(string, string, []string) bool
|
||||||
|
if len(searchValues) == 0 {
|
||||||
|
check = func(option, searchParam string, _ []string) bool {
|
||||||
|
return strings.EqualFold(option, searchParam)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
check = func(param, searchParam string, searchValues []string) bool {
|
||||||
|
// split parameter and value
|
||||||
|
split := strings.SplitN(param, "=", 2)
|
||||||
|
if len(split) < 2 || split[0] != searchParam {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, value := range searchValues {
|
||||||
|
if strings.EqualFold(value, split[1]) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(f)
|
||||||
|
for scanner.Scan() {
|
||||||
|
for _, field := range strings.Fields(scanner.Text()) {
|
||||||
|
if check(field, searchParam, searchValues) {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
func CPUFlags(cpuInfoPath string) (map[string]bool, error) {
|
func CPUFlags(cpuInfoPath string) (map[string]bool, error) {
|
||||||
flagsField := "flags"
|
flagsField := "flags"
|
||||||
|
|
||||||
|
99
src/runtime/virtcontainers/hypervisor_s390x.go
Normal file
99
src/runtime/virtcontainers/hypervisor_s390x.go
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
// Copyright (c) IBM Corp. 2021
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
//
|
||||||
|
|
||||||
|
package virtcontainers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// This is valid in other architectures, but varcheck will complain
|
||||||
|
// when setting it in common code as it will be regarded unused
|
||||||
|
procKernelCmdline = "/proc/cmdline"
|
||||||
|
|
||||||
|
// Secure Execution
|
||||||
|
// https://www.kernel.org/doc/html/latest/virt/kvm/s390-pv.html
|
||||||
|
seCPUFacilityBit = 158
|
||||||
|
seCmdlineParam = "prot_virt"
|
||||||
|
)
|
||||||
|
|
||||||
|
var seCmdlineValues = []string{
|
||||||
|
"1", "on", "y", "yes",
|
||||||
|
}
|
||||||
|
|
||||||
|
// CPUFacilities retrieves active CPU facilities according to "Facility Indications", Principles of Operation.
|
||||||
|
// Takes cpuinfo path (such as /proc/cpuinfo), returns map of all active facility bits.
|
||||||
|
func CPUFacilities(cpuInfoPath string) (map[int]bool, error) {
|
||||||
|
facilitiesField := "facilities"
|
||||||
|
|
||||||
|
f, err := os.Open(cpuInfoPath)
|
||||||
|
if err != nil {
|
||||||
|
return map[int]bool{}, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
facilities := make(map[int]bool)
|
||||||
|
scanner := bufio.NewScanner(f)
|
||||||
|
for scanner.Scan() {
|
||||||
|
// Expected format: ["facilities", ":", ...] or ["facilities:", ...]
|
||||||
|
fields := strings.Fields(scanner.Text())
|
||||||
|
if len(fields) < 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.HasPrefix(fields[0], facilitiesField) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
start := 1
|
||||||
|
if fields[1] == ":" {
|
||||||
|
start = 2
|
||||||
|
}
|
||||||
|
for _, field := range fields[start:] {
|
||||||
|
bit, err := strconv.Atoi(field)
|
||||||
|
if err != nil {
|
||||||
|
return map[int]bool{}, err
|
||||||
|
}
|
||||||
|
facilities[bit] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return facilities, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
return map[int]bool{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return map[int]bool{}, fmt.Errorf("Couldn't find %q from %q output", facilitiesField, cpuInfoPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// availableGuestProtection returns seProtection (Secure Execution) if available.
|
||||||
|
// Checks that Secure Execution is available (CPU facilities) and enabled (kernel command line).
|
||||||
|
func availableGuestProtection() (guestProtection, error) {
|
||||||
|
facilities, err := CPUFacilities(procCPUInfo)
|
||||||
|
if err != nil {
|
||||||
|
return noneProtection, err
|
||||||
|
}
|
||||||
|
if !facilities[seCPUFacilityBit] {
|
||||||
|
return noneProtection, fmt.Errorf("This CPU does not support Secure Execution")
|
||||||
|
}
|
||||||
|
|
||||||
|
seCmdlinePresent, err := CheckCmdline(procKernelCmdline, seCmdlineParam, seCmdlineValues)
|
||||||
|
if err != nil {
|
||||||
|
return noneProtection, err
|
||||||
|
}
|
||||||
|
if !seCmdlinePresent {
|
||||||
|
return noneProtection, fmt.Errorf("Protected Virtualization is not enabled on kernel command line! " +
|
||||||
|
"Need %s=%s (or %s) to enable Secure Execution",
|
||||||
|
seCmdlineParam, seCmdlineValues[0], strings.Join(seCmdlineValues[1:], ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
return seProtection, nil
|
||||||
|
}
|
25
src/runtime/virtcontainers/hypervisor_s390x_test.go
Normal file
25
src/runtime/virtcontainers/hypervisor_s390x_test.go
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// Copyright (c) IBM Corp. 2021
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
//
|
||||||
|
|
||||||
|
package virtcontainers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCPUFacilities(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
facilities, err := CPUFacilities(procCPUInfo)
|
||||||
|
assert.NoError(err)
|
||||||
|
|
||||||
|
// z/Architecture facility should always be active (introduced in 2000)
|
||||||
|
assert.Equal(facilities[1], true)
|
||||||
|
// facility bits should not be as high as MaxInt
|
||||||
|
assert.Equal(facilities[math.MaxInt64], false)
|
||||||
|
}
|
@ -401,6 +401,21 @@ func TestGetHostMemorySizeKb(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCheckCmdline(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
cmdlineFp, err := ioutil.TempFile("", "")
|
||||||
|
assert.NoError(err)
|
||||||
|
_, err = cmdlineFp.WriteString("quiet root=/dev/sda2")
|
||||||
|
assert.NoError(err)
|
||||||
|
cmdlinePath := cmdlineFp.Name()
|
||||||
|
defer os.Remove(cmdlinePath)
|
||||||
|
|
||||||
|
assert.True(CheckCmdline(cmdlinePath, "quiet", []string{}))
|
||||||
|
assert.True(CheckCmdline(cmdlinePath, "root", []string{"/dev/sda1", "/dev/sda2"}))
|
||||||
|
assert.False(CheckCmdline(cmdlinePath, "ro", []string{}))
|
||||||
|
}
|
||||||
|
|
||||||
// nolint: unused, deadcode
|
// nolint: unused, deadcode
|
||||||
type testNestedVMMData struct {
|
type testNestedVMMData struct {
|
||||||
content []byte
|
content []byte
|
||||||
|
@ -299,6 +299,12 @@ func TestQemuAmd64AppendProtectionDevice(t *testing.T) {
|
|||||||
assert.Error(err)
|
assert.Error(err)
|
||||||
assert.Empty(bios)
|
assert.Empty(bios)
|
||||||
|
|
||||||
|
// Secure Execution protection
|
||||||
|
amd64.(*qemuAmd64).protection = seProtection
|
||||||
|
devices, bios, err = amd64.appendProtectionDevice(devices, firmware)
|
||||||
|
assert.Error(err)
|
||||||
|
assert.Empty(bios)
|
||||||
|
|
||||||
// sev protection
|
// sev protection
|
||||||
// TODO: update once it's supported
|
// TODO: update once it's supported
|
||||||
amd64.(*qemuAmd64).protection = sevProtection
|
amd64.(*qemuAmd64).protection = sevProtection
|
||||||
|
@ -168,6 +168,10 @@ const (
|
|||||||
// IBM POWER 9 Protected Execution Facility
|
// IBM POWER 9 Protected Execution Facility
|
||||||
// https://www.kernel.org/doc/html/latest/powerpc/ultravisor.html
|
// https://www.kernel.org/doc/html/latest/powerpc/ultravisor.html
|
||||||
pefProtection
|
pefProtection
|
||||||
|
|
||||||
|
// IBM Secure Execution (IBM Z & LinuxONE)
|
||||||
|
// https://www.kernel.org/doc/html/latest/virt/kvm/s390-pv.html
|
||||||
|
seProtection
|
||||||
)
|
)
|
||||||
|
|
||||||
type qemuArchBase struct {
|
type qemuArchBase struct {
|
||||||
|
@ -14,6 +14,7 @@ import (
|
|||||||
"github.com/kata-containers/kata-containers/src/runtime/virtcontainers/device/config"
|
"github.com/kata-containers/kata-containers/src/runtime/virtcontainers/device/config"
|
||||||
"github.com/kata-containers/kata-containers/src/runtime/virtcontainers/types"
|
"github.com/kata-containers/kata-containers/src/runtime/virtcontainers/types"
|
||||||
"github.com/kata-containers/kata-containers/src/runtime/virtcontainers/utils"
|
"github.com/kata-containers/kata-containers/src/runtime/virtcontainers/utils"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
type qemuS390x struct {
|
type qemuS390x struct {
|
||||||
@ -27,6 +28,11 @@ const (
|
|||||||
defaultQemuMachineOptions = "accel=kvm"
|
defaultQemuMachineOptions = "accel=kvm"
|
||||||
virtioSerialCCW = "virtio-serial-ccw"
|
virtioSerialCCW = "virtio-serial-ccw"
|
||||||
qmpMigrationWaitTimeout = 5 * time.Second
|
qmpMigrationWaitTimeout = 5 * time.Second
|
||||||
|
logSubsystem = "qemuS390x"
|
||||||
|
|
||||||
|
// Secure Execution, also known as Protected Virtualization
|
||||||
|
// https://qemu.readthedocs.io/en/latest/system/s390x/protvirt.html
|
||||||
|
secExecID = "pv0"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Verify needed parameters
|
// Verify needed parameters
|
||||||
@ -72,6 +78,12 @@ func newQemuArch(config HypervisorConfig) (qemuArch, error) {
|
|||||||
// Set first bridge type to CCW
|
// Set first bridge type to CCW
|
||||||
q.Bridges = append(q.Bridges, ccwbridge)
|
q.Bridges = append(q.Bridges, ccwbridge)
|
||||||
|
|
||||||
|
if config.ConfidentialGuest {
|
||||||
|
if err := q.enableProtection(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if config.ImagePath != "" {
|
if config.ImagePath != "" {
|
||||||
q.kernelParams = append(q.kernelParams, commonVirtioblkKernelRootParams...)
|
q.kernelParams = append(q.kernelParams, commonVirtioblkKernelRootParams...)
|
||||||
q.kernelParamsNonDebug = append(q.kernelParamsNonDebug, kernelParamsSystemdNonDebug...)
|
q.kernelParamsNonDebug = append(q.kernelParamsNonDebug, kernelParamsSystemdNonDebug...)
|
||||||
@ -301,3 +313,42 @@ func (q *qemuS390x) addDeviceToBridge(ctx context.Context, ID string, t types.Ty
|
|||||||
|
|
||||||
return fmt.Sprintf("%04x", addr), b, nil
|
return fmt.Sprintf("%04x", addr), b, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// enableProtection enables guest protection for QEMU's machine option.
|
||||||
|
func (q *qemuS390x) enableProtection() error {
|
||||||
|
protection, err := availableGuestProtection()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if protection != seProtection {
|
||||||
|
return fmt.Errorf("Got unexpected protection %v, only seProtection (Secure Execution) is supported", protection)
|
||||||
|
}
|
||||||
|
|
||||||
|
q.protection = protection
|
||||||
|
if q.qemuMachine.Options != "" {
|
||||||
|
q.qemuMachine.Options += ","
|
||||||
|
}
|
||||||
|
q.qemuMachine.Options += fmt.Sprintf("confidential-guest-support=%s", secExecID)
|
||||||
|
virtLog.WithFields(logrus.Fields{
|
||||||
|
"subsystem": logSubsystem,
|
||||||
|
"machine": q.qemuMachine}).
|
||||||
|
Info("Enabling guest protection with Secure Execution")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// appendProtectionDevice appends a QEMU object for Secure Execution.
|
||||||
|
// Takes devices and returns updated version. Takes BIOS and returns it (no modification on s390x).
|
||||||
|
func (q *qemuS390x) appendProtectionDevice(devices []govmmQemu.Device, firmware string) ([]govmmQemu.Device, string, error) {
|
||||||
|
switch q.protection {
|
||||||
|
case seProtection:
|
||||||
|
return append(devices,
|
||||||
|
govmmQemu.Object{
|
||||||
|
Type: govmmQemu.SecExecGuest,
|
||||||
|
ID: secExecID,
|
||||||
|
}), firmware, nil
|
||||||
|
case noneProtection:
|
||||||
|
return devices, firmware, nil
|
||||||
|
default:
|
||||||
|
return devices, firmware, fmt.Errorf("Unsupported guest protection technology: %v", q.protection)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -101,3 +101,50 @@ func TestQemuS390xAppendVhostUserDevice(t *testing.T) {
|
|||||||
assert.NoError(err)
|
assert.NoError(err)
|
||||||
assert.Equal(devices, expected)
|
assert.Equal(devices, expected)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestQemuS390xAppendProtectionDevice(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
s390x := newTestQemu(assert, QemuCCWVirtio)
|
||||||
|
|
||||||
|
var devices []govmmQemu.Device
|
||||||
|
var bios, firmware string
|
||||||
|
var err error
|
||||||
|
devices, bios, err = s390x.appendProtectionDevice(devices, firmware)
|
||||||
|
assert.NoError(err)
|
||||||
|
|
||||||
|
// no protection
|
||||||
|
assert.Empty(bios)
|
||||||
|
|
||||||
|
// PEF protection
|
||||||
|
s390x.(*qemuS390x).protection = pefProtection
|
||||||
|
devices, bios, err = s390x.appendProtectionDevice(devices, firmware)
|
||||||
|
assert.Error(err)
|
||||||
|
assert.Empty(bios)
|
||||||
|
|
||||||
|
// TDX protection
|
||||||
|
s390x.(*qemuS390x).protection = tdxProtection
|
||||||
|
devices, bios, err = s390x.appendProtectionDevice(devices, firmware)
|
||||||
|
assert.Error(err)
|
||||||
|
assert.Empty(bios)
|
||||||
|
|
||||||
|
// SEV protection
|
||||||
|
s390x.(*qemuS390x).protection = sevProtection
|
||||||
|
devices, bios, err = s390x.appendProtectionDevice(devices, firmware)
|
||||||
|
assert.Error(err)
|
||||||
|
assert.Empty(bios)
|
||||||
|
|
||||||
|
// Secure Execution protection
|
||||||
|
s390x.(*qemuS390x).protection = seProtection
|
||||||
|
|
||||||
|
devices, bios, err = s390x.appendProtectionDevice(devices, firmware)
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.Empty(bios)
|
||||||
|
|
||||||
|
expectedOut := []govmmQemu.Device{
|
||||||
|
govmmQemu.Object{
|
||||||
|
Type: govmmQemu.SecExecGuest,
|
||||||
|
ID: secExecID,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert.Equal(expectedOut, devices)
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user