From b8dbe46687c2a96efa9252b69d3fc1ce33bdc416 Mon Sep 17 00:00:00 2001 From: Aleksa Sarai Date: Thu, 18 Nov 2021 16:12:59 +1100 Subject: [PATCH] runc init: avoid netlink message length overflows When writing netlink messages, it is possible to have a byte array larger than UINT16_MAX which would result in the length field overflowing and allowing user-controlled data to be parsed as control characters (such as creating custom mount points, changing which set of namespaces to allow, and so on). Co-authored-by: Kir Kolyshkin Signed-off-by: Kir Kolyshkin Signed-off-by: Aleksa Sarai --- libcontainer/container_linux.go | 20 +++++++++++++++++++- libcontainer/message_linux.go | 9 +++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/libcontainer/container_linux.go b/libcontainer/container_linux.go index 6ce1854f68..1484703b0c 100644 --- a/libcontainer/container_linux.go +++ b/libcontainer/container_linux.go @@ -2028,16 +2028,34 @@ func encodeIDMapping(idMap []configs.IDMap) ([]byte, error) { return data.Bytes(), nil } +// netlinkError is an error wrapper type for use by custom netlink message +// types. Panics with errors are wrapped in netlinkError so that the recover +// in bootstrapData can distinguish intentional panics. +type netlinkError struct{ error } + // bootstrapData encodes the necessary data in netlink binary format // as a io.Reader. // Consumer can write the data to a bootstrap program // such as one that uses nsenter package to bootstrap the container's // init process correctly, i.e. with correct namespaces, uid/gid // mapping etc. -func (c *linuxContainer) bootstrapData(cloneFlags uintptr, nsMaps map[configs.NamespaceType]string) (io.Reader, error) { +func (c *linuxContainer) bootstrapData(cloneFlags uintptr, nsMaps map[configs.NamespaceType]string) (_ io.Reader, Err error) { // create the netlink message r := nl.NewNetlinkRequest(int(InitMsg), 0) + // Our custom messages cannot bubble up an error using returns, instead + // they will panic with the specific error type, netlinkError. In that + // case, recover from the panic and return that as an error. + defer func() { + if r := recover(); r != nil { + if e, ok := r.(netlinkError); ok { + Err = e.error + } else { + panic(r) + } + } + }() + // write cloneFlags r.AddData(&Int32msg{ Type: CloneFlagsAttr, diff --git a/libcontainer/message_linux.go b/libcontainer/message_linux.go index 1d4f5033aa..e4107ce39f 100644 --- a/libcontainer/message_linux.go +++ b/libcontainer/message_linux.go @@ -3,6 +3,9 @@ package libcontainer import ( + "fmt" + "math" + "github.com/vishvananda/netlink/nl" "golang.org/x/sys/unix" ) @@ -54,6 +57,12 @@ type Bytemsg struct { func (msg *Bytemsg) Serialize() []byte { l := msg.Len() + if l > math.MaxUint16 { + // We cannot return nil nor an error here, so we panic with + // a specific type instead, which is handled via recover in + // bootstrapData. + panic(netlinkError{fmt.Errorf("netlink: cannot serialize bytemsg of length %d (larger than UINT16_MAX)", l)}) + } buf := make([]byte, (l+unix.NLA_ALIGNTO-1) & ^(unix.NLA_ALIGNTO-1)) native := nl.NativeEndian() native.PutUint16(buf[0:2], uint16(l))