pkg/bootparam: add bootparam pkg
This adds the bootparam package which can marshal and unmarshal the Linux
kernel command line into boot parameters and a rest section passed to
init.
This is a very quirky format, thus there is a fuzz testing harness
against the reference implementation from the kernel included to verify
correctness.
A set of weird edge cases is rejected by Unmarshal instead of parsing
to nonsensical data as the reference implementation does to save on
complexity in the parser.
Change-Id: I6debfa67e69ae8db4e0356f34ecb127ea27d18de
Reviewed-on: https://review.monogon.dev/c/monogon/+/1125
Tested-by: Jenkins CI
Reviewed-by: Serge Bazanski <serge@monogon.tech>
diff --git a/metropolis/pkg/bootparam/bootparam_test.go b/metropolis/pkg/bootparam/bootparam_test.go
new file mode 100644
index 0000000..a0032a4
--- /dev/null
+++ b/metropolis/pkg/bootparam/bootparam_test.go
@@ -0,0 +1,60 @@
+// If this is bootparam we have an import cycle
+package bootparam_test
+
+import (
+ "strings"
+ "testing"
+
+ "github.com/google/go-cmp/cmp"
+
+ "source.monogon.dev/metropolis/pkg/bootparam"
+ "source.monogon.dev/metropolis/pkg/bootparam/ref"
+)
+
+// Fuzzers can be run with
+// bazel test //metropolis/pkg/bootparam:bootparam_test
+// --test_arg=-test.fuzz=FuzzMarshal
+// --test_arg=-test.fuzzcachedir=/tmp/fuzz
+// --test_arg=-test.fuzztime=60s
+
+func FuzzUnmarshal(f *testing.F) {
+ f.Add(`initrd="\test\some=value" root=yolo "definitely quoted" ro rootflags=`)
+ f.Fuzz(func(t *testing.T, a string) {
+ refOut, refRest := ref.Parse(a)
+ out, rest, err := bootparam.Unmarshal(a)
+ if err != nil {
+ return
+ }
+ if diff := cmp.Diff(refOut, out); diff != "" {
+ t.Errorf("Parse(%q): params mismatch (-want +got):\n%s", a, diff)
+ }
+ if refRest != rest {
+ t.Errorf("Parse(%q): expected rest to be %q, got %q", a, refRest, rest)
+ }
+ })
+}
+
+func FuzzMarshal(f *testing.F) {
+ // Choose delimiters which mean nothing to the parser
+ f.Add("a:b;assd:9dsf;1234", "some fancy rest")
+ f.Fuzz(func(t *testing.T, paramsRaw string, rest string) {
+ paramsSeparated := strings.Split(paramsRaw, ";")
+ var params bootparam.Params
+ for _, p := range paramsSeparated {
+ a, b, _ := strings.Cut(p, ":")
+ params = append(params, bootparam.Param{Param: a, Value: b})
+ }
+ rest = bootparam.TrimLeftSpace(rest)
+ encoded, err := bootparam.Marshal(params, rest)
+ if err != nil {
+ return // Invalid input
+ }
+ refOut, refRest := ref.Parse(encoded)
+ if diff := cmp.Diff(refOut, params); diff != "" {
+ t.Errorf("Marshal(%q): params mismatch (-want +got):\n%s", paramsRaw, diff)
+ }
+ if refRest != rest {
+ t.Errorf("Parse(%q, %q): expected rest to be %q, got %q", paramsRaw, rest, refRest, rest)
+ }
+ })
+}