Summary
GetUnikernelConfig validates the mandatory config fields while they are
still base64-encoded, then decodes them. Go's encoding/base64 skips CR
and LF, so a field containing only a newline is non-empty at validation
time and decodes to the empty string. The call returns success with all
three mandatory fields empty.
Detail
getConfigFromJSON calls validate() then decode(). validate()
checks that UnikernelType, Hypervisor and UnikernelBinary are
non-empty, but at that point they hold the encoded text.
Only CR and LF bypass this. Space and tab return illegal base64 data,
so it is specifically the two characters the decoder is documented to
skip, not whitespace in general.
Impact
Map() drops empty values, so the three mandatory annotations never
reach the persisted state. pkg/unikontainers/unikontainers.go:123 then
returns ErrNotUnikernel for that container on every subsequent Get.
New succeeds and everything after it fails, which makes this present as
a runtime bug rather than a config rejection. Around fifteen call sites in
that file read those three annotations out of state without rechecking
them.
The input is controlled by whoever builds the image.
Reproduction
urunc.json with the three mandatory fields set to a single newline:
{
"<FILL: unikernelType tag>": "\n",
"<FILL: hypervisor tag>": "\n",
"<FILL: unikernelBinary tag>": "\n"
}
GetUnikernelConfig on the containing directory returns a nil error and
a config whose three mandatory fields are "".
Suggested fix
Decode before validating, so validate() inspects the values that are
actually used downstream. I have this working locally along with a
native fuzz target and a checked-in seed corpus entry for this input,
and can open a PR if that is welcome.
The annotation path in getConfigFromSpec has a related but distinct
problem (the TODO at config.go:84 means those values are never decoded
at all). I would rather raise that separately since fixing it changes
behaviour that looks deliberately parked.
Summary
GetUnikernelConfigvalidates the mandatory config fields while they arestill base64-encoded, then decodes them. Go's
encoding/base64skips CRand LF, so a field containing only a newline is non-empty at validation
time and decodes to the empty string. The call returns success with all
three mandatory fields empty.
Detail
getConfigFromJSONcallsvalidate()thendecode().validate()checks that
UnikernelType,HypervisorandUnikernelBinaryarenon-empty, but at that point they hold the encoded text.
Only CR and LF bypass this. Space and tab return
illegal base64 data,so it is specifically the two characters the decoder is documented to
skip, not whitespace in general.
Impact
Map()drops empty values, so the three mandatory annotations neverreach the persisted state.
pkg/unikontainers/unikontainers.go:123thenreturns
ErrNotUnikernelfor that container on every subsequentGet.Newsucceeds and everything after it fails, which makes this present asa runtime bug rather than a config rejection. Around fifteen call sites in
that file read those three annotations out of state without rechecking
them.
The input is controlled by whoever builds the image.
Reproduction
urunc.jsonwith the three mandatory fields set to a single newline:GetUnikernelConfigon the containing directory returns a nil error anda config whose three mandatory fields are
"".Suggested fix
Decode before validating, so
validate()inspects the values that areactually used downstream. I have this working locally along with a
native fuzz target and a checked-in seed corpus entry for this input,
and can open a PR if that is welcome.
The annotation path in
getConfigFromSpechas a related but distinctproblem (the TODO at
config.go:84means those values are never decodedat all). I would rather raise that separately since fixing it changes
behaviour that looks deliberately parked.