176 lines
5.5 KiB
Go
176 lines
5.5 KiB
Go
//go:build linux || android
|
|
|
|
package proot
|
|
|
|
import (
|
|
"bytes"
|
|
"debug/elf"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
)
|
|
|
|
type execRewrite struct {
|
|
ExecPath string
|
|
Argv []string
|
|
}
|
|
|
|
func (pm *pathMapper) libraryPath() string {
|
|
return pm.libraryPathGuest()
|
|
}
|
|
|
|
func (pm *pathMapper) libraryPathGuest() string {
|
|
candidates := []string{}
|
|
switch runtime.GOARCH {
|
|
case "amd64":
|
|
candidates = append(candidates, "/lib/x86_64-linux-gnu", "/usr/lib/x86_64-linux-gnu", "/lib64")
|
|
case "arm64":
|
|
candidates = append(candidates, "/lib/aarch64-linux-gnu", "/usr/lib/aarch64-linux-gnu", "/system/lib64")
|
|
case "arm":
|
|
candidates = append(candidates, "/lib/arm-linux-gnueabihf", "/usr/lib/arm-linux-gnueabihf", "/system/lib")
|
|
case "386":
|
|
candidates = append(candidates, "/lib/i386-linux-gnu", "/usr/lib/i386-linux-gnu", "/lib32", "/usr/lib32")
|
|
case "loong64":
|
|
candidates = append(candidates, "/lib/loongarch64-linux-gnu", "/usr/lib/loongarch64-linux-gnu")
|
|
case "mips":
|
|
candidates = append(candidates, "/lib/mips-linux-gnu", "/usr/lib/mips-linux-gnu")
|
|
case "mips64":
|
|
candidates = append(candidates, "/lib/mips64-linux-gnuabi64", "/usr/lib/mips64-linux-gnuabi64")
|
|
case "mips64le":
|
|
candidates = append(candidates, "/lib/mips64el-linux-gnuabi64", "/usr/lib/mips64el-linux-gnuabi64")
|
|
case "mipsle":
|
|
candidates = append(candidates, "/lib/mipsel-linux-gnu", "/usr/lib/mipsel-linux-gnu")
|
|
case "ppc64":
|
|
candidates = append(candidates, "/lib/powerpc64-linux-gnu", "/usr/lib/powerpc64-linux-gnu")
|
|
case "ppc64le":
|
|
candidates = append(candidates, "/lib/powerpc64le-linux-gnu", "/usr/lib/powerpc64le-linux-gnu")
|
|
case "riscv64":
|
|
candidates = append(candidates, "/lib/riscv64-linux-gnu", "/usr/lib/riscv64-linux-gnu")
|
|
case "s390x":
|
|
candidates = append(candidates, "/lib/s390x-linux-gnu", "/usr/lib/s390x-linux-gnu")
|
|
}
|
|
candidates = append(candidates, "/lib", "/usr/lib", "/usr/local/lib", "/usr/lib64", "/system/lib64", "/system/lib")
|
|
seen := map[string]bool{}
|
|
out := make([]string, 0, len(candidates))
|
|
for _, guest := range candidates {
|
|
host := pm.GuestToHost(guest)
|
|
if host == "" || seen[guest] {
|
|
continue
|
|
}
|
|
if st, err := os.Stat(host); err == nil && st.IsDir() {
|
|
seen[guest] = true
|
|
out = append(out, guest)
|
|
}
|
|
}
|
|
return strings.Join(out, ":")
|
|
}
|
|
|
|
func (pm *pathMapper) resolveExec(hostPath, guestPath string, argv []string) (*execRewrite, bool) {
|
|
if rw, ok := pm.resolveScript(hostPath, guestPath, argv); ok {
|
|
return rw, true
|
|
}
|
|
if rw, ok := pm.resolveELF(hostPath, guestPath, argv); ok {
|
|
return rw, true
|
|
}
|
|
return &execRewrite{ExecPath: hostPath, Argv: argv}, false
|
|
}
|
|
|
|
func (pm *pathMapper) resolveScript(hostPath, guestPath string, argv []string) (*execRewrite, bool) {
|
|
data := make([]byte, 256)
|
|
f, err := os.Open(hostPath)
|
|
if err != nil {
|
|
return nil, false
|
|
}
|
|
defer f.Close()
|
|
n, _ := f.Read(data)
|
|
data = data[:n]
|
|
if !bytes.HasPrefix(data, []byte("#!")) {
|
|
return nil, false
|
|
}
|
|
line := string(data[2:])
|
|
if i := strings.IndexByte(line, '\n'); i >= 0 {
|
|
line = line[:i]
|
|
}
|
|
line = strings.TrimSpace(strings.TrimRight(line, "\r"))
|
|
if line == "" {
|
|
return nil, false
|
|
}
|
|
parts := strings.Fields(line)
|
|
interpGuest := parts[0]
|
|
interpHost := interpGuest
|
|
if strings.HasPrefix(interpGuest, "/") {
|
|
interpHost = pm.GuestToHost(interpGuest)
|
|
}
|
|
newArgv := []string{interpGuest}
|
|
if len(parts) > 1 {
|
|
newArgv = append(newArgv, parts[1:]...)
|
|
}
|
|
// The interpreter runs under ptrace, so argv paths must stay in the guest
|
|
// namespace. Passing the host path here makes the interpreter/loader try to
|
|
// open /rootfs/... and the tracer would translate it to /rootfs/rootfs/...
|
|
// again.
|
|
newArgv = append(newArgv, guestPath)
|
|
if len(argv) > 1 {
|
|
newArgv = append(newArgv, argv[1:]...)
|
|
}
|
|
|
|
// If the script interpreter is itself a dynamically-linked ELF (for example
|
|
// /usr/bin/perl), do not exec the interpreter host path directly. A direct
|
|
// exec would let the kernel resolve PT_INTERP in the host namespace, so the
|
|
// process may start with the host dynamic loader and guest libraries mixed
|
|
// together. Route the interpreter through the same ELF loader rewrite used
|
|
// for normal binaries, while keeping argv in the guest namespace.
|
|
if rw, ok := pm.resolveELF(interpHost, interpGuest, newArgv); ok {
|
|
return rw, true
|
|
}
|
|
|
|
return &execRewrite{ExecPath: interpHost, Argv: newArgv}, true
|
|
}
|
|
|
|
func (pm *pathMapper) resolveELF(hostPath, guestPath string, argv []string) (*execRewrite, bool) {
|
|
f, err := elf.Open(hostPath)
|
|
if err != nil {
|
|
return nil, false
|
|
}
|
|
defer f.Close()
|
|
var interp string
|
|
for _, p := range f.Progs {
|
|
if p.Type != elf.PT_INTERP {
|
|
continue
|
|
}
|
|
buf := make([]byte, p.Filesz)
|
|
if _, err := p.ReadAt(buf, 0); err == nil {
|
|
interp = strings.TrimRight(string(buf), "\x00")
|
|
}
|
|
break
|
|
}
|
|
if interp == "" {
|
|
return nil, false
|
|
}
|
|
interpGuest := interp
|
|
interpHost := interp
|
|
if filepath.IsAbs(interp) {
|
|
interpHost = pm.GuestToHost(interp)
|
|
}
|
|
if _, err := os.Stat(interpHost); err != nil {
|
|
// Minimal test rootfs trees often contain only /bin/<cmd>. Falling back
|
|
// to the host interpreter keeps those cases working when the host and
|
|
// guest ABI match, while still presenting guest paths to the traced loader.
|
|
if _, hostErr := os.Stat(interp); hostErr != nil {
|
|
return nil, false
|
|
}
|
|
interpHost = interp
|
|
}
|
|
libPath := pm.libraryPathGuest()
|
|
newArgv := []string{interpGuest}
|
|
if libPath != "" {
|
|
newArgv = append(newArgv, "--library-path", libPath)
|
|
}
|
|
newArgv = append(newArgv, guestPath)
|
|
if len(argv) > 1 {
|
|
newArgv = append(newArgv, argv[1:]...)
|
|
}
|
|
return &execRewrite{ExecPath: interpHost, Argv: newArgv}, true
|
|
}
|