qemu: add `args_allowlist` to sandbox VM command line inputs

The QEMU driver allows arbitrary command line options, but many of
these options give access to host resources that operators may not
want to expose such as devices. Add an optional allowlist to the
plugin configuration so that operators can limit the resources for
QEMU.
This commit is contained in:
Tim Gross 2021-11-03 16:27:49 -04:00 committed by Luiz Aoqui
parent 373243e289
commit fc1d4814d9
No known key found for this signature in database
GPG Key ID: 29F459C0D9CBB573
4 changed files with 82 additions and 5 deletions

3
.changelog/11542.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:security
Allow limiting QEMU arguments to reduce access to host resources. [CVE-2021-43415](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-43415)
```

View File

@ -84,7 +84,8 @@ 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),
"image_paths": hclspec.NewAttr("image_paths", "list(string)", false),
"args_allowlist": hclspec.NewAttr("args_allowlist", "list(string)", false),
})
// taskConfigSpec is the hcl specification for the driver config section of
@ -136,6 +137,11 @@ type TaskState struct {
type Config struct {
// ImagePaths is an allow-list of paths qemu is allowed to load an image from
ImagePaths []string `codec:"image_paths"`
// ArgsAllowList is an allow-list of arguments the jobspec can
// include in arguments to qemu, so that cluster operators can can
// prevent access to devices
ArgsAllowList []string `codec:"args_allowlist"`
}
// Driver is a driver for running images via Qemu
@ -338,6 +344,26 @@ func isAllowedImagePath(allowedPaths []string, allocDir, imagePath string) bool
return false
}
// validateArgs ensures that all QEMU command line params are in the
// allowlist. This function must be called after all interpolation has
// taken place.
func validateArgs(pluginConfigAllowList, args []string) error {
if len(pluginConfigAllowList) > 0 {
allowed := map[string]struct{}{}
for _, arg := range pluginConfigAllowList {
allowed[arg] = struct{}{}
}
for _, arg := range args {
if strings.HasPrefix(strings.TrimSpace(arg), "-") {
if _, ok := allowed[arg]; !ok {
return fmt.Errorf("%q is not in args_allowlist", arg)
}
}
}
}
return nil
}
func (d *Driver) StartTask(cfg *drivers.TaskConfig) (*drivers.TaskHandle, *drivers.DriverNetwork, error) {
if _, ok := d.tasks.Get(cfg.ID); ok {
return nil, nil, fmt.Errorf("taskConfig with ID '%s' already started", cfg.ID)
@ -355,6 +381,10 @@ func (d *Driver) StartTask(cfg *drivers.TaskConfig) (*drivers.TaskHandle, *drive
handle := drivers.NewTaskHandle(taskHandleVersion)
handle.Config = cfg
if err := validateArgs(d.config.ArgsAllowList, driverConfig.Args); err != nil {
return nil, nil, err
}
// Get the image source
vmPath := driverConfig.ImagePath
if vmPath == "" {

View File

@ -453,3 +453,32 @@ func TestIsAllowedImagePath(t *testing.T) {
require.Falsef(t, isAllowedImagePath(allowedPaths, allocDir, p), "path should be not allowed: %v", p)
}
}
func TestArgsAllowList(t *testing.T) {
pluginConfigAllowList := []string{"-drive", "-net", "-snapshot"}
validArgs := [][]string{
{"-drive", "/path/to/wherever", "-snapshot"},
{"-net", "tap,vlan=0,ifname=tap0"},
}
invalidArgs := [][]string{
{"-usbdevice", "mouse"},
{"-singlestep"},
{"--singlestep"},
{" -singlestep"},
{"\t-singlestep"},
}
for _, args := range validArgs {
require.NoError(t, validateArgs(pluginConfigAllowList, args))
require.NoError(t, validateArgs([]string{}, args))
}
for _, args := range invalidArgs {
require.Error(t, validateArgs(pluginConfigAllowList, args))
require.NoError(t, validateArgs([]string{}, args))
}
}

View File

@ -54,7 +54,7 @@ The `qemu` driver supports the following configuration in the job spec:
forcefully terminated. (Note that
[prior to qemu 2.10.1](https://github.com/qemu/qemu/commit/ad9579aaa16d5b385922d49edac2c96c79bcfb6),
the monitor socket path is limited to 108 characters. Graceful shutdown will
be disabled if qemu is < 2.10.1 and the generated monitor path exceeds this
be disabled if QEMU is < 2.10.1 and the generated monitor path exceeds this
length. You may encounter this issue if you set long
[data_dir](/docs/configuration#data_dir)
or
@ -72,7 +72,7 @@ The `qemu` driver supports the following configuration in the job spec:
}
```
- `args` - (Optional) A list of strings that is passed to qemu as command line
- `args` - (Optional) A list of strings that is passed to QEMU as command line
options.
## Examples
@ -145,12 +145,19 @@ job "docs" {
plugin "qemu" {
config {
image_paths = ["/mnt/image/paths"]
args_allowlist = ["-drive", "-usbdevice"]
}
}
```
- `image_paths` (`[]string`: `[]`) - Specifies the host paths the QEMU driver is
allowed to load images from.
- `image_paths` (`[]string`: `[]`) - Specifies the host paths the QEMU
driver is allowed to load images from.
- `args_allowlist` (`[]string`: `[]`) - Specifies the command line
flags that the [`args`] option is permitted to pass to QEMU. If
unset, a job submitter can pass any command line flag into QEMU,
including flags that provide the VM with access to host devices such
as USB drives. Refer to the [QEMU documentation] for the available
flags.
## Resource Isolation
@ -162,3 +169,11 @@ Virtualization provides the highest level of isolation for workloads that
require additional security, and resource use is constrained by the QEMU
hypervisor rather than the host kernel. VM network traffic still flows through
the host's interface(s).
Note that the strong isolation provided by virtualization only applies
to the workload once the VM is started. Operators should use the
`args_allowlist` option to prevent job submitters from accessing
devices and resources they are not allowed to access.
[`args`]: /docs/drivers/qemu#args
[QEMU documentation]: https://www.qemu.org/docs/master/system/invocation.html