From 82ff2db460fc1fbc646e1915cd24b12a2da32b1a Mon Sep 17 00:00:00 2001 From: Dan Mihai Date: Tue, 19 Sep 2023 17:19:41 +0000 Subject: [PATCH] runtime: support kernel params including spaces Support quoted kernel command line parameters that include space characters. Example: dm-mod.create="dm-verity,,,ro,0 736328 verity 1 /dev/vda1 /dev/vda2 4096 4096 92041 0 sha256 f211b9f1921ef726d57a72bf82be23a510076639fa8549ade10f85e214e0ddb4 065c13dfb5b4e0af034685aa5442bddda47b17c182ee44ba55a373835d18a038" Fixes: #8003 Signed-off-by: Dan Mihai --- src/runtime/pkg/katautils/config.go | 10 +- src/runtime/pkg/katautils/config_test.go | 2 +- src/runtime/virtcontainers/hypervisor.go | 36 ++++- src/runtime/virtcontainers/hypervisor_test.go | 135 ++++++++++++++++++ 4 files changed, 175 insertions(+), 8 deletions(-) diff --git a/src/runtime/pkg/katautils/config.go b/src/runtime/pkg/katautils/config.go index c950ed66aa..0e280de0e9 100644 --- a/src/runtime/pkg/katautils/config.go +++ b/src/runtime/pkg/katautils/config.go @@ -720,7 +720,7 @@ func newFirecrackerHypervisorConfig(h hypervisor) (vc.HypervisorConfig, error) { ImagePath: image, RootfsType: rootfsType, FirmwarePath: firmware, - KernelParams: vc.DeserializeParams(strings.Fields(kernelParams)), + KernelParams: vc.DeserializeParams(vc.KernelParamFields(kernelParams)), NumVCPUs: h.defaultVCPUs(), DefaultMaxVCPUs: h.defaultMaxVCPUs(), MemorySize: h.defaultMemSz(), @@ -853,7 +853,7 @@ func newQemuHypervisorConfig(h hypervisor) (vc.HypervisorConfig, error) { PFlash: pflashes, MachineAccelerators: machineAccelerators, CPUFeatures: cpuFeatures, - KernelParams: vc.DeserializeParams(strings.Fields(kernelParams)), + KernelParams: vc.DeserializeParams(vc.KernelParamFields(kernelParams)), HypervisorMachineType: machineType, NumVCPUs: h.defaultVCPUs(), DefaultMaxVCPUs: h.defaultMaxVCPUs(), @@ -965,7 +965,7 @@ func newAcrnHypervisorConfig(h hypervisor) (vc.HypervisorConfig, error) { HypervisorCtlPath: hypervisorctl, HypervisorCtlPathList: h.CtlPathList, FirmwarePath: firmware, - KernelParams: vc.DeserializeParams(strings.Fields(kernelParams)), + KernelParams: vc.DeserializeParams(vc.KernelParamFields(kernelParams)), NumVCPUs: h.defaultVCPUs(), DefaultMaxVCPUs: h.defaultMaxVCPUs(), MemorySize: h.defaultMemSz(), @@ -1055,7 +1055,7 @@ func newClhHypervisorConfig(h hypervisor) (vc.HypervisorConfig, error) { RootfsType: rootfsType, FirmwarePath: firmware, MachineAccelerators: machineAccelerators, - KernelParams: vc.DeserializeParams(strings.Fields(kernelParams)), + KernelParams: vc.DeserializeParams(vc.KernelParamFields(kernelParams)), HypervisorMachineType: machineType, NumVCPUs: h.defaultVCPUs(), DefaultMaxVCPUs: h.defaultMaxVCPUs(), @@ -1130,7 +1130,7 @@ func newDragonballHypervisorConfig(h hypervisor) (vc.HypervisorConfig, error) { KernelPath: kernel, ImagePath: image, RootfsType: rootfsType, - KernelParams: vc.DeserializeParams(strings.Fields(kernelParams)), + KernelParams: vc.DeserializeParams(vc.KernelParamFields(kernelParams)), NumVCPUs: h.defaultVCPUs(), DefaultMaxVCPUs: h.defaultMaxVCPUs(), MemorySize: h.defaultMemSz(), diff --git a/src/runtime/pkg/katautils/config_test.go b/src/runtime/pkg/katautils/config_test.go index 90f9f4d568..8b89234370 100644 --- a/src/runtime/pkg/katautils/config_test.go +++ b/src/runtime/pkg/katautils/config_test.go @@ -159,7 +159,7 @@ func createAllRuntimeConfigFiles(dir, hypervisor string) (testConfig testRuntime KernelPath: kernelPath, ImagePath: imagePath, RootfsType: rootfsType, - KernelParams: vc.DeserializeParams(strings.Fields(kernelParams)), + KernelParams: vc.DeserializeParams(vc.KernelParamFields(kernelParams)), HypervisorMachineType: machineType, NumVCPUs: defaultVCPUCount, DefaultMaxVCPUs: getCurrentCpuNum(), diff --git a/src/runtime/virtcontainers/hypervisor.go b/src/runtime/virtcontainers/hypervisor.go index 7d0575fb6f..bb0195351c 100644 --- a/src/runtime/virtcontainers/hypervisor.go +++ b/src/runtime/virtcontainers/hypervisor.go @@ -919,7 +919,7 @@ func CheckCmdline(kernelCmdlinePath, searchParam string, searchValues []string) scanner := bufio.NewScanner(f) for scanner.Scan() { - for _, field := range strings.Fields(scanner.Text()) { + for _, field := range KernelParamFields(scanner.Text()) { if check(field, searchParam, searchValues) { return true, nil } @@ -941,7 +941,7 @@ func CPUFlags(cpuInfoPath string) (map[string]bool, error) { scanner := bufio.NewScanner(f) for scanner.Scan() { // Expected format: ["flags", ":", ...] or ["flags:", ...] - fields := strings.Fields(scanner.Text()) + fields := KernelParamFields(scanner.Text()) if len(fields) < 2 { continue } @@ -1085,3 +1085,35 @@ type Hypervisor interface { // check if hypervisor supports built-in rate limiter. IsRateLimiterBuiltin() bool } + +// KernelParamFields is similar to strings.Fields(), but doesn't split +// based on space characters that are part of a quoted substring. Example +// of quoted kernel command line parameter value: +// dm-mod.create="dm-verity,,,ro,0 736328 verity 1 +// /dev/vda1 /dev/vda2 4096 4096 92041 0 sha256 +// f211b9f1921ef726d57a72bf82be23a510076639fa8549ade10f85e214e0ddb4 +// 065c13dfb5b4e0af034685aa5442bddda47b17c182ee44ba55a373835d18a038" +func KernelParamFields(s string) []string { + var params []string + + start := 0 + inQuote := false + for current, c := range s { + if c == '"' { + inQuote = !inQuote + } else if c == ' ' && !inQuote { + newParam := strings.TrimSpace(s[start:current]) + if newParam != "" { + params = append(params, newParam) + } + start = current + 1 + } + } + + newParam := strings.TrimSpace(s[start:]) + if newParam != "" { + params = append(params, newParam) + } + + return params +} diff --git a/src/runtime/virtcontainers/hypervisor_test.go b/src/runtime/virtcontainers/hypervisor_test.go index d67a386989..19bfdf4773 100644 --- a/src/runtime/virtcontainers/hypervisor_test.go +++ b/src/runtime/virtcontainers/hypervisor_test.go @@ -10,6 +10,7 @@ import ( "github.com/kata-containers/kata-containers/src/runtime/virtcontainers/types" "github.com/stretchr/testify/assert" "os" + "strings" "testing" ) @@ -490,3 +491,137 @@ func TestAssetPath(t *testing.T) { assert.Equal(expected, p, msg) } } + +func TestKernelParamFields(t *testing.T) { + assert := assert.New(t) + tests := []struct { + cmdLine string + expectedFieldsResult []string + expectedKernelParamFieldsResult []string + }{ + { + cmdLine: "a=b x=y", + expectedFieldsResult: []string{ + "a=b", + "x=y", + }, + expectedKernelParamFieldsResult: []string{ + "a=b", + "x=y", + }, + }, + { + cmdLine: "a=b x=y foo=bar", + expectedFieldsResult: []string{ + "a=b", + "x=y", + "foo=bar", + }, + expectedKernelParamFieldsResult: []string{ + "a=b", + "x=y", + "foo=bar", + }, + }, + { + cmdLine: "a x=y foo=bar", + expectedFieldsResult: []string{ + "a", + "x=y", + "foo=bar", + }, + expectedKernelParamFieldsResult: []string{ + "a", + "x=y", + "foo=bar", + }, + }, + { + cmdLine: "a=b x foo=bar", + expectedFieldsResult: []string{ + "a=b", + "x", + "foo=bar", + }, + expectedKernelParamFieldsResult: []string{ + "a=b", + "x", + "foo=bar", + }, + }, + { + cmdLine: "a=b x foo ", + expectedFieldsResult: []string{ + "a=b", + "x", + "foo", + }, + expectedKernelParamFieldsResult: []string{ + "a=b", + "x", + "foo", + }, + }, + { + cmdLine: "a=b x=\"y z\"", + expectedFieldsResult: []string{ + "a=b", + "x=\"y", + "z\"", + }, + expectedKernelParamFieldsResult: []string{ + "a=b", + "x=\"y z\"", + }, + }, + { + cmdLine: "foo=\"bar baz\"", + expectedFieldsResult: []string{ + "foo=\"bar", + "baz\"", + }, + expectedKernelParamFieldsResult: []string{ + "foo=\"bar baz\"", + }, + }, + { + cmdLine: "foo=\"bar baz\" abc=\"123\"", + expectedFieldsResult: []string{ + "foo=\"bar", + "baz\"", + "abc=\"123\"", + }, + expectedKernelParamFieldsResult: []string{ + "foo=\"bar baz\"", + "abc=\"123\"", + }, + }, + { + cmdLine: "\"a=b", + expectedFieldsResult: []string{ + "\"a=b", + }, + expectedKernelParamFieldsResult: []string{ + "\"a=b", + }, + }, + { + cmdLine: "\"a=b x=y", + expectedFieldsResult: []string{ + "\"a=b", + "x=y", + }, + expectedKernelParamFieldsResult: []string{ + "\"a=b x=y", + }, + }, + } + + for _, t := range tests { + params := strings.Fields(t.cmdLine) + assert.Equal(params, t.expectedFieldsResult, "Unexpected strings.Fields behavior") + + params = KernelParamFields(t.cmdLine) + assert.Equal(params, t.expectedKernelParamFieldsResult, "Unexpected KernelParamFields behavior") + } +}