For Go 1.7 I started replacing the pointers in Go's run time type information with offsets from the beginning of the type section. (The motivation was significantly reduced binary size, as 8-bytes for the pointer + 32-bytes for the dynamic relocation is significantly more than a 4-byte offset multiplied through by several pointers in each type and thousands of types.)
This work is not yet complete, and in its partial state it leads to subtle bugs like #17709. When using a binary built with -buildmode=shared or -buildmode=plugin, there are multiple type information sections, one for each module. An offset into this region (represented by either a typeOff or a nameOff) needs to be resolved against the correct type region. But when you follow one of the old pointers in a type, it is entirely possible the dynamic relocation processed by ld.so will jump you to the types section of a different module.
This can lead to an offset from one module being resolved against the wrong type region, meaning garbage data is used for the type. This will manifest as subtle bugs: types incorrectly mapping (or not) onto interfaces, the reflect package returning wrong results, or bad garbage collection.
The likelihood of misplacing an offset decreases significantly if we remove the pointers, as we will not have to consider the interaction of two separate relocation systems. Let's do that.
The text was updated successfully, but these errors were encountered:
I have perhaps overstated the risk: this only applies to parts of the runtime that have access to non-canonical *_type values. Almost all of the runtime receives a *_type via module lookup, and moduledata.typemap ensures that the type is canonical.
Because the canonical *_type is the version of it from the earliest loaded moduledata, and because ld.so always resolves to the earliest loaded module, the pointers in a typical *_type point to the module that *_type originates from. It is definitely not an issue outside the runtime as all *_type values are canonical there, and even relatively high-level runtime code like iface.go only sees canonical *_type values.
In fact, runtime.typesEqual is one of the only functions that is exposed to non-canonical *_type values. Even so, I'd hate for someone else to have to debug something like this.
The runtime.typeEquals function is used during typelinksinit to
determine the canonical set of *_type values to use throughout the
runtime. As such, it is run against non-canonical *_type values, that
is, types from modules that are duplicates of a type from another
module that was loaded earlier in the program life.
These non-canonical *_type values sometimes contain pointers. These
pointers are pointing to position-independent data, and so they are set
by ld.so using dynamic relocations when the module is loaded. As such,
the pointer can point to the equivalent memory from a previous module.
This means if typesEqual follows a pointer inside a *_type, it can end
up at a piece of memory from another module. If it reads a typeOff or
nameOff from that memory and attempts to resolve it against the
non-canonical *_type from the later module, it will end up with a
reference to junk memory.
Instead, resolve against the pointer the offset was read from, so the
data is valid.
Should no longer matter after #17724 is resolved in a later Go.
Run-TryBot: David Crawshaw <firstname.lastname@example.org>
Reviewed-by: Keith Randall <email@example.com>