Skip to content

Commit

Permalink
Add patch of first module's itabs to relocate unreachable methods to …
Browse files Browse the repository at this point in the history
…the new module's code address
  • Loading branch information
Anonymous committed Nov 4, 2022
1 parent 00691ea commit bff7bb1
Show file tree
Hide file tree
Showing 5 changed files with 237 additions and 5 deletions.
169 changes: 169 additions & 0 deletions itab.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
package goloader

import (
"fmt"
"github.com/pkujhd/goloader/mprotect"
"unsafe"
)

// Similar to runtime.(*itab).init() but replaces method text pointers to start the offset from the specified base address
func (m *itab) adjustMethods(codeBase uintptr, methodIndices []int) string {
inter := m.inter
typ := m._type
x := typ.uncommon()

ni := len(inter.mhdr)
nt := int(x.mcount)
xmhdr := (*[1 << 16]method)(add(unsafe.Pointer(x), uintptr(x.moff)))[:nt:nt]
methods := (*[1 << 16]unsafe.Pointer)(unsafe.Pointer(&m.fun[0]))[:ni:ni]
imethods:
for k := 0; k < ni; k++ {
i := &inter.mhdr[k]
itype := inter.typ.typeOff(i.ityp)
name := inter.typ.nameOff(i.name)
iname := name.name()
ipkg := name.pkgPath()
if ipkg == "" {
ipkg = inter.pkgpath.name()
}
for _, j := range methodIndices {
t := &xmhdr[j]
if t.ifn < 0 {
panic("shouldn't be possible")
}
tname := typ.nameOff(t.name)
if typ.typeOff(t.mtyp) == itype && tname.name() == iname {
pkgPath := tname.pkgPath()
if pkgPath == "" {
pkgPath = typ.nameOff(x.pkgpath).name()
}
if tname.isExported() || pkgPath == ipkg {
if m != nil {
ifn := unsafe.Pointer(codeBase + uintptr(t.ifn))
methods[k] = ifn
}
continue imethods
}
}
}
}
return ""
}

func patchTypeMethods(t *_type, u, prevU *uncommonType, patchedTypeMethodsIfn, patchedTypeMethodsTfn map[*_type][]int) (err error) {
// It's possible that a baked in type in the main module does not have all its methods reachable
// (i.e. some method offsets will be set to -1 via the linker's reachability analysis) whereas the
// new type will have them them all.

// In this case, to avoid fatal "unreachable method called. linker bug?" errors, we need to
// manipulate the method offsets to make them not -1, and manually partially adjust the
// firstmodule itabs to rewrite the method addresses to point at the new module text (and remember to clean up afterwards)

if u != nil && prevU != nil {
methods := u.methods()
prevMethods := prevU.methods()
if len(methods) == len(prevMethods) {
for i := range methods {
if methods[i].tfn == -1 || methods[i].ifn == -1 {

if prevMethods[i].ifn != -1 {
page := mprotect.GetPage(uintptr(unsafe.Pointer(&methods[i].ifn)))
err = mprotect.MprotectMakeWritable(page)
if err != nil {
return fmt.Errorf("failed to make page writeable while patching type %s: %w", _name(t.nameOff(t.str)), err)
}
methods[i].ifn = prevMethods[i].ifn
err = mprotect.MprotectMakeReadOnly(page)
if err != nil {
return fmt.Errorf("failed to make page read only while patching type %s: %w", _name(t.nameOff(t.str)), err)
}
// Store for later cleanup on Unload()
patchedTypeMethodsIfn[t] = append(patchedTypeMethodsIfn[t], i)
}

if prevMethods[i].tfn != -1 {
page := mprotect.GetPage(uintptr(unsafe.Pointer(&methods[i].tfn)))
err = mprotect.MprotectMakeWritable(page)
if err != nil {
return fmt.Errorf("failed to make page writeable while patching type %s: %w", _name(t.nameOff(t.str)), err)
}
methods[i].tfn = prevMethods[i].tfn
err = mprotect.MprotectMakeReadOnly(page)
if err != nil {
return fmt.Errorf("failed to make page read only while patching type %s: %w", _name(t.nameOff(t.str)), err)
}
// Store for later cleanup on Unload()
patchedTypeMethodsTfn[t] = append(patchedTypeMethodsTfn[t], i)
}
}
}
}
}
return nil
}

func (cm *CodeModule) patchTypeMethods() (err error) {
// Adjust the main module's itabs so that any missing methods now point to new module's text instead of "unreachable code".

firstModule := activeModules()[0]

for _, itab := range firstModule.itablinks {
methodIndicesIfn, ifnPatched := cm.patchedTypeMethodsIfn[itab._type]
methodIndicesTfn, tfnPatched := cm.patchedTypeMethodsTfn[itab._type]
if ifnPatched || tfnPatched {
page := mprotect.GetPage(uintptr(unsafe.Pointer(&itab.fun[0])))
err = mprotect.MprotectMakeWritable(page)
if err != nil {
return fmt.Errorf("failed to make page writeable while re-initing itab for type %s: %w", _name(itab._type.nameOff(itab._type.str)), err)
}
if ifnPatched {
itab.adjustMethods(uintptr(cm.codeBase), methodIndicesIfn)
}
if tfnPatched {
itab.adjustMethods(uintptr(cm.codeBase), methodIndicesTfn)
}

err = mprotect.MprotectMakeReadOnly(page)
if err != nil {
return fmt.Errorf("failed to make page read only while re-initing itab for type %s: %w", _name(itab._type.nameOff(itab._type.str)), err)
}
}
}
return nil
}

func (cm *CodeModule) revertPatchedTypeMethods() error {
for t, indices := range cm.patchedTypeMethodsIfn {
u := t.uncommon()
methods := u.methods()
for _, i := range indices {
page := mprotect.GetPage(uintptr(unsafe.Pointer(&methods[i].ifn)))
err := mprotect.MprotectMakeWritable(page)
if err != nil {
return fmt.Errorf("failed to make page writeable while patching type %s: %w", _name(t.nameOff(t.str)), err)
}
methods[i].ifn = -1
err = mprotect.MprotectMakeReadOnly(page)
if err != nil {
return fmt.Errorf("failed to make page read only while patching type %s: %w", _name(t.nameOff(t.str)), err)
}
}
}
for t, indices := range cm.patchedTypeMethodsTfn {
u := t.uncommon()
methods := u.methods()
for _, i := range indices {
page := mprotect.GetPage(uintptr(unsafe.Pointer(&methods[i].tfn)))
err := mprotect.MprotectMakeWritable(page)
if err != nil {
return fmt.Errorf("failed to make page writeable while patching type %s: %w", _name(t.nameOff(t.str)), err)
}
methods[i].tfn = -1
err = mprotect.MprotectMakeReadOnly(page)
if err != nil {
return fmt.Errorf("failed to make page read only while patching type %s: %w", _name(t.nameOff(t.str)), err)
}
}
}
return nil
}
34 changes: 29 additions & 5 deletions ld.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,12 @@ type Linker struct {

type CodeModule struct {
segment
Syms map[string]uintptr
module *moduledata
gcdata []byte
gcbss []byte
Syms map[string]uintptr
module *moduledata
gcdata []byte
gcbss []byte
patchedTypeMethodsIfn map[*_type][]int
patchedTypeMethodsTfn map[*_type][]int
}

var (
Expand Down Expand Up @@ -510,6 +512,8 @@ collect:
typehash[t.hash] = append(tlist, t)
}

patchedTypeMethodsIfn := make(map[*_type][]int)
patchedTypeMethodsTfn := make(map[*_type][]int)
segment := &codeModule.segment
byteorder := linker.Arch.ByteOrder
for _, symbol := range linker.symMap {
Expand All @@ -529,6 +533,7 @@ collect:
// if this is pointing to a type descriptor at an offset inside this binary, we should deduplicate it against
// already known types from other modules to allow fast type assertion using *_type pointer equality
t := (*_type)(unsafe.Pointer(addr))
prevT := (*_type)(unsafe.Pointer(addr))
for _, candidate := range typehash[t.hash] {
seen := map[_typePair]struct{}{}
if typesEqual(t, candidate, seen) {
Expand All @@ -538,7 +543,14 @@ collect:
}

// Only relocate code if the type is a duplicate
if uintptr(unsafe.Pointer(t)) != addr {
if t != prevT {
u := t.uncommon()
prevU := prevT.uncommon()
err := patchTypeMethods(t, u, prevU, patchedTypeMethodsIfn, patchedTypeMethodsTfn)
if err != nil {
return err
}

addr = uintptr(unsafe.Pointer(t))
switch loc.Type {
case reloctype.R_PCREL:
Expand Down Expand Up @@ -577,6 +589,14 @@ collect:
}
}
}
codeModule.patchedTypeMethodsIfn = patchedTypeMethodsIfn
codeModule.patchedTypeMethodsTfn = patchedTypeMethodsTfn

if err != nil {
return err
}
err = codeModule.patchTypeMethods()

return err
}

Expand Down Expand Up @@ -655,6 +675,10 @@ func Load(linker *Linker, symPtr map[string]uintptr) (codeModule *CodeModule, er
}

func (cm *CodeModule) Unload() error {
err := cm.revertPatchedTypeMethods()
if err != nil {
return err
}
removeitabs(cm.module)
runtime.GC()
modulesLock.Lock()
Expand Down
25 changes: 25 additions & 0 deletions mprotect/mprotect_unix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//go:build darwin || dragonfly || freebsd || linux || openbsd || solaris || netbsd
// +build darwin dragonfly freebsd linux openbsd solaris netbsd

package mprotect

import (
"syscall"
"unsafe"
)

func GetPage(p uintptr) []byte {
return (*(*[0xFFFFFF]byte)(unsafe.Pointer(p & ^uintptr(syscall.Getpagesize()-1))))[:syscall.Getpagesize()]
}

func RawMemoryAccess(b uintptr) []byte {
return (*(*[0xFF]byte)(unsafe.Pointer(b)))[:]
}

func MprotectMakeWritable(page []byte) error {
return syscall.Mprotect(page, syscall.PROT_READ|syscall.PROT_WRITE|syscall.PROT_EXEC)
}

func MprotectMakeReadOnly(page []byte) error {
return syscall.Mprotect(page, syscall.PROT_READ|syscall.PROT_EXEC)
}
6 changes: 6 additions & 0 deletions mprotect/mprotect_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
//go:build windows
// +build windows

package mprotect

// TODO - see if VirtualProtect works? https://learn.microsoft.com/en-gb/windows/win32/api/memoryapi/nf-memoryapi-virtualprotect
8 changes: 8 additions & 0 deletions type.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@ func _typeOff(t *_type, off typeOff) *_type
//go:linkname _name runtime.name.name
func _name(n name) string

//go:linkname _pkgPath runtime.name.pkgPath
func _pkgPath(n name) string

//go:linkname _isExported runtime.name.isExported
func _isExported(n name) bool

//go:linkname _methods reflect.(*uncommonType).methods
func _methods(t *uncommonType) []method

Expand All @@ -79,6 +85,8 @@ func (t *_type) uncommon() *uncommonType { return _uncommon(t) }
func (t *_type) nameOff(off nameOff) name { return _nameOff(t, off) }
func (t *_type) typeOff(off typeOff) *_type { return _typeOff(t, off) }
func (n name) name() string { return _name(n) }
func (n name) pkgPath() string { return _pkgPath(n) }
func (n name) isExported() bool { return _isExported(n) }
func (t *uncommonType) methods() []method { return _methods(t) }
func (t *_type) Kind() reflect.Kind { return _Kind(t) }
func (t *_type) Elem() *_type { return _Elem(t) }
Expand Down

0 comments on commit bff7bb1

Please sign in to comment.