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/ref/BUILD.bazel b/metropolis/pkg/bootparam/ref/BUILD.bazel
new file mode 100644
index 0000000..d22540a
--- /dev/null
+++ b/metropolis/pkg/bootparam/ref/BUILD.bazel
@@ -0,0 +1,11 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_library")
+
+go_library(
+ name = "ref",
+ srcs = ["ref.go"],
+ cgo = True,
+ gc_goopts = ["-d=libfuzzer"],
+ importpath = "source.monogon.dev/metropolis/pkg/bootparam/ref",
+ visibility = ["//visibility:public"],
+ deps = ["//metropolis/pkg/bootparam"],
+)
diff --git a/metropolis/pkg/bootparam/ref/ref.go b/metropolis/pkg/bootparam/ref/ref.go
new file mode 100644
index 0000000..9842ecd
--- /dev/null
+++ b/metropolis/pkg/bootparam/ref/ref.go
@@ -0,0 +1,140 @@
+// Package ref provides the reference implementation for kernel command line
+// parsing as present in the Linux kernel. This is a separate package and
+// not part of the bootparam tests because Go does not let you use cgo in
+// tests.
+package ref
+
+// Reference implementation from the kernel
+
+/*
+#include <stdlib.h>
+#include <ctype.h>
+#include <stddef.h>
+
+#define _U 0x01
+#define _L 0x02
+#define _D 0x04
+#define _C 0x08
+#define _P 0x10
+#define _S 0x20
+#define _X 0x40
+#define _SP 0x80
+
+#define __ismask(x) (_ctype[(int)(unsigned char)(x)])
+#define kisspace(c) ((__ismask(c)&(_S)) != 0)
+
+const unsigned char _ctype[] = {
+_C,_C,_C,_C,_C,_C,_C,_C,
+_C,_C|_S,_C|_S,_C|_S,_C|_S,_C|_S,_C,_C,
+_C,_C,_C,_C,_C,_C,_C,_C,
+_C,_C,_C,_C,_C,_C,_C,_C,
+_S|_SP,_P,_P,_P,_P,_P,_P,_P,
+_P,_P,_P,_P,_P,_P,_P,_P,
+_D,_D,_D,_D,_D,_D,_D,_D,
+_D,_D,_P,_P,_P,_P,_P,_P,
+_P,_U|_X,_U|_X,_U|_X,_U|_X,_U|_X,_U|_X,_U,
+_U,_U,_U,_U,_U,_U,_U,_U,
+_U,_U,_U,_U,_U,_U,_U,_U,
+_U,_U,_U,_P,_P,_P,_P,_P,
+_P,_L|_X,_L|_X,_L|_X,_L|_X,_L|_X,_L|_X,_L,
+_L,_L,_L,_L,_L,_L,_L,_L,
+_L,_L,_L,_L,_L,_L,_L,_L,
+_L,_L,_L,_P,_P,_P,_P,_C,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+_S|_SP,_P,_P,_P,_P,_P,_P,_P,_P,_P,_P,_P,_P,_P,_P,_P,
+_P,_P,_P,_P,_P,_P,_P,_P,_P,_P,_P,_P,_P,_P,_P,_P,
+_U,_U,_U,_U,_U,_U,_U,_U,_U,_U,_U,_U,_U,_U,_U,_U,
+_U,_U,_U,_U,_U,_U,_U,_P,_U,_U,_U,_U,_U,_U,_U,_L,
+_L,_L,_L,_L,_L,_L,_L,_L,_L,_L,_L,_L,_L,_L,_L,_L,
+_L,_L,_L,_L,_L,_L,_L,_P,_L,_L,_L,_L,_L,_L,_L,_L};
+
+
+
+char *skip_spaces(const char *str)
+{
+ while (kisspace(*str))
+ ++str;
+ return (char *)str;
+}
+
+
+// * Parse a string to get a param value pair.
+// * You can use " around spaces, but can't escape ".
+// * Hyphens and underscores equivalent in parameter names.
+ char *next_arg(char *args, char **param, char **val)
+ {
+ unsigned int i, equals = 0;
+ int in_quote = 0, quoted = 0;
+
+ if (*args == '"') {
+ args++;
+ in_quote = 1;
+ quoted = 1;
+ }
+
+ for (i = 0; args[i]; i++) {
+ if (kisspace(args[i]) && !in_quote)
+ break;
+ if (equals == 0) {
+ if (args[i] == '=')
+ equals = i;
+ }
+ if (args[i] == '"')
+ in_quote = !in_quote;
+ }
+
+ *param = args;
+ if (!equals)
+ *val = NULL;
+ else {
+ args[equals] = '\0';
+ *val = args + equals + 1;
+
+ // Don't include quotes in value.
+ if (**val == '"') {
+ (*val)++;
+ if (args[i-1] == '"')
+ args[i-1] = '\0';
+ }
+ }
+ if (quoted && i > 0 && args[i-1] == '"')
+ args[i-1] = '\0';
+
+ if (args[i]) {
+ args[i] = '\0';
+ args += i + 1;
+ } else
+ args += i;
+
+ // Chew up trailing spaces.
+ return skip_spaces(args);
+ }
+*/
+import "C"
+import (
+ "unsafe"
+
+ "source.monogon.dev/metropolis/pkg/bootparam"
+)
+
+func Parse(str string) (params bootparam.Params, rest string) {
+ cs := C.CString(bootparam.TrimLeftSpace(str))
+ csAllocPtr := cs
+ var param, val *C.char
+ for *cs != 0 {
+ var p bootparam.Param
+ cs = C.next_arg(cs, ¶m, &val)
+ p.Param = C.GoString(param)
+ if val != nil {
+ p.Value = C.GoString(val)
+ }
+ if p.Param == "--" {
+ rest = C.GoString(cs)
+ return
+ }
+ params = append(params, p)
+ }
+ C.free(unsafe.Pointer(csAllocPtr))
+ return
+}