Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/27182.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:improvement
qemu: adds an emulator allowlist to qemu plugin config
```
39 changes: 37 additions & 2 deletions drivers/qemu/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,10 @@ var (

// configSpec is the hcl specification returned by the ConfigSchema RPC
configSpec = hclspec.NewObject(map[string]*hclspec.Spec{
"image_paths": hclspec.NewAttr("image_paths", "list(string)", false),
"args_allowlist": hclspec.NewAttr("args_allowlist", "list(string)", false),
"image_paths": hclspec.NewAttr("image_paths", "list(string)", false),
"args_allowlist": hclspec.NewAttr("args_allowlist", "list(string)", false),
"emulator_allowlist": hclspec.NewAttr("emulator_allowlist", "list(string)", false),
"fingerprint_emulator": hclspec.NewAttr("fingerprint_emulator", "string", false),
})

// taskConfigSpec is the hcl specification for the driver config section of
Expand Down Expand Up @@ -150,6 +152,11 @@ type Config struct {
// prevent access to devices
ArgsAllowList []string `codec:"args_allowlist"`

// EmulatorAllowList is an allow-list of emulator binaries the
// jobspec and FingerprintEmulator can use, so that cluster
// operators can control which emulators job authors can use.
EmulatorAllowList []string `codec:"emulator_allowlist"`

// FingerprintEmulator specifies which QEMU binary is used
// for fingerprinting
FingerprintEmulator string `codec:"fingerprint_emulator"`
Expand Down Expand Up @@ -251,6 +258,13 @@ func (d *Driver) buildFingerprint() *drivers.Fingerprint {
if d.config.FingerprintEmulator != "" {
fpEmulator = d.config.FingerprintEmulator
}

if err := validateEmulator(fpEmulator, d.config.EmulatorAllowList); err != nil {
fingerprint.Health = drivers.HealthStateUndetected
fingerprint.HealthDescription = fmt.Sprintf("Fingerprint emulator is invalid: %v", err)
return fingerprint
}

outBytes, err := exec.Command(fpEmulator, "--version").Output()
if err != nil {
// return no error, as it isn't an error to not find qemu, it just means we
Expand Down Expand Up @@ -382,6 +396,23 @@ func isAllowedDriveInterface(driveInterface string) bool {
return false
}

func validateEmulator(emulator string, allowedEmulators []string) error {
if len(allowedEmulators) > 0 {
if !slices.Contains(allowedEmulators, emulator) {
return fmt.Errorf("emulator '%s' is not an allowed emulator", emulator)
}
} else {
match, err := regexp.MatchString("qemu-system-*", emulator)
if err != nil {
return err
}
if !match {
return fmt.Errorf("emulator '%s' is not valid", emulator)
}
}
return nil
}

// validateArgs ensures that all QEMU command line params are in the
// allowlist. This function must be called after all interpolation has
// taken place.
Expand Down Expand Up @@ -419,6 +450,10 @@ func (d *Driver) StartTask(cfg *drivers.TaskConfig) (*drivers.TaskHandle, *drive
handle := drivers.NewTaskHandle(taskHandleVersion)
handle.Config = cfg

if err := validateEmulator(driverConfig.Emulator, d.config.EmulatorAllowList); err != nil {
return nil, nil, err
}

if err := validateArgs(d.config.ArgsAllowList, driverConfig.Args); err != nil {
return nil, nil, err
}
Expand Down
41 changes: 41 additions & 0 deletions drivers/qemu/driver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package qemu

import (
"context"
"errors"
"io"
"os"
"path/filepath"
Expand Down Expand Up @@ -289,6 +290,46 @@ config {
must.Eq(t, expected, tc)
}

func TestValidateEmulator(t *testing.T) {
testcases := []struct {
name string
validEmulators []string
requestedEmulator string
exp error
}{
{
name: "empty valid emulators, valid request",
validEmulators: nil,
requestedEmulator: "qemu-system-x86_64",
exp: nil,
},
{
name: "empty valid emulators, invalid request",
validEmulators: nil,
requestedEmulator: "some-binary",
exp: errors.New("emulator 'some-binary' is not valid"),
},
{
name: "non-empty valid emulators, valid request",
validEmulators: []string{"qemu-system-x86_64"},
requestedEmulator: "qemu-system-x86_64",
exp: nil,
},
{
name: "non-empty valid emulators, invalid request",
validEmulators: []string{"qemu-system-x86_64"},
requestedEmulator: "qemu-system-aarch64",
exp: errors.New("emulator 'qemu-system-aarch64' is not an allowed emulator"),
},
}

for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
must.Eq(t, validateEmulator(tc.requestedEmulator, tc.validEmulators), tc.exp)
})
}
}

func TestIsAllowedDriveInterface(t *testing.T) {
validInterfaces := []string{"ide", "scsi", "sd", "mtd", "floppy", "pflash", "virtio", "none"}
invalidInterfaces := []string{"foo", "virtio-foo"}
Expand Down
Loading