2024-09-25 11:33:00 +02:00
|
|
|
package relocator
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"debug/elf"
|
|
|
|
|
"fmt"
|
|
|
|
|
"io"
|
|
|
|
|
"os"
|
|
|
|
|
"os/exec"
|
|
|
|
|
"path/filepath"
|
|
|
|
|
"strings"
|
|
|
|
|
"syscall"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// isDynamicELF checks if the given file is a dynamically linked ELF binary.
|
|
|
|
|
func isDynamicELF(filePath string) (bool, error) {
|
|
|
|
|
file, err := os.Open(filePath)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return false, fmt.Errorf("failed to open file: %v", err)
|
|
|
|
|
}
|
|
|
|
|
defer file.Close()
|
|
|
|
|
|
|
|
|
|
// Parse the file as an ELF binary using the debug/elf package
|
|
|
|
|
elfFile, err := elf.NewFile(file)
|
|
|
|
|
if err != nil {
|
|
|
|
|
// If it's not an ELF file, return false
|
|
|
|
|
return false, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if the ELF binary has a PT_DYNAMIC segment (indicating dynamic linking)
|
|
|
|
|
for _, prog := range elfFile.Progs {
|
|
|
|
|
if prog.Type == elf.PT_DYNAMIC {
|
|
|
|
|
return true, nil // Dynamically linked ELF
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false, nil // Not dynamically linked
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// copyRegularFile copies a file from src to dest, preserving ownership and permissions.
|
|
|
|
|
func copyRegularFile(dest, src string) error {
|
|
|
|
|
fmt.Printf("Copying file from %s to %s\n", src, dest)
|
|
|
|
|
|
|
|
|
|
// Open source file
|
|
|
|
|
srcFile, err := os.Open(src)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
defer srcFile.Close()
|
|
|
|
|
|
|
|
|
|
// Create destination file
|
|
|
|
|
destFile, err := os.Create(dest)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
defer destFile.Close()
|
|
|
|
|
|
|
|
|
|
// Copy file contents
|
|
|
|
|
if _, err := io.Copy(destFile, srcFile); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get file info to preserve permissions and ownership
|
|
|
|
|
fileInfo, err := os.Lstat(src)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Preserve file permissions
|
|
|
|
|
if err := os.Chmod(dest, fileInfo.Mode()); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Preserve file ownership if possible
|
|
|
|
|
if stat, ok := fileInfo.Sys().(*syscall.Stat_t); ok {
|
|
|
|
|
_ = os.Lchown(dest, int(stat.Uid), int(stat.Gid)) // Ignore ownership error for non-root users
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// relocateELFBinary copies the ELF binary and updates its shared library paths.
|
|
|
|
|
func relocateELFBinary(prefix, dest, src string) error {
|
|
|
|
|
// Copy the ELF binary to the new destination
|
|
|
|
|
if err := copyRegularFile(dest, src); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get shared libraries using "ldd" command
|
|
|
|
|
out, err := exec.Command("ldd", src).Output()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("failed to get linked libraries for %s: %v", src, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Store unique parent directories of linked libraries
|
|
|
|
|
dirSet := make(map[string]struct{})
|
|
|
|
|
|
|
|
|
|
// Parse the output from ldd and relocate each library
|
|
|
|
|
for _, line := range strings.Split(string(out), "\n") {
|
|
|
|
|
parts := strings.Fields(line)
|
|
|
|
|
if len(parts) >= 3 && parts[1] == "=>" {
|
|
|
|
|
libraryPath := parts[2] // Linked library path
|
|
|
|
|
|
|
|
|
|
// Get the directory of the library and add to the set
|
|
|
|
|
libraryDir := filepath.Dir(libraryPath)
|
|
|
|
|
dirSet[libraryDir] = struct{}{}
|
|
|
|
|
|
|
|
|
|
// Recursively relocate the library
|
|
|
|
|
if err := RelocateFile(prefix, libraryPath); err != nil {
|
|
|
|
|
return fmt.Errorf("failed to relocate library %s: %v", libraryPath, err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-11 14:10:37 +02:00
|
|
|
var interpreter string
|
|
|
|
|
// Get the interpreter using "patchelf --print-interpreter"
|
|
|
|
|
interpOut, err := exec.Command("patchelf", "--print-interpreter", src).Output()
|
|
|
|
|
if err == nil {
|
|
|
|
|
interpreter = strings.TrimSpace(string(interpOut)) // Trim any whitespace or newlines
|
|
|
|
|
|
|
|
|
|
RelocateFile(prefix, interpreter)
|
|
|
|
|
} else {
|
|
|
|
|
interpreter = ""
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-25 11:33:00 +02:00
|
|
|
// Aggregate the directories into a colon-separated rpath
|
|
|
|
|
var dirList []string
|
|
|
|
|
for dir := range dirSet {
|
|
|
|
|
dirList = append(dirList, filepath.Join(prefix, dir))
|
|
|
|
|
}
|
2024-10-11 14:10:37 +02:00
|
|
|
|
|
|
|
|
// If an interpreter was found, relocate it
|
|
|
|
|
if interpreter != "" {
|
|
|
|
|
newInterpPath := filepath.Join(prefix, interpreter)
|
|
|
|
|
if _, err := exec.Command("patchelf", "--set-interpreter", newInterpPath, dest).Output(); err != nil {
|
|
|
|
|
return fmt.Errorf("failed to update interpreter for %s: %v", dest, err)
|
|
|
|
|
}
|
|
|
|
|
fmt.Printf("Relocated interpreter from %s to %s\n", interpreter, newInterpPath)
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-25 11:33:00 +02:00
|
|
|
rpath := strings.Join(dirList, ":")
|
|
|
|
|
|
2024-10-11 14:10:37 +02:00
|
|
|
if rpath != "" {
|
|
|
|
|
// Update the ELF binary's rpath using patchelf
|
|
|
|
|
if _, err := exec.Command("patchelf", "--set-rpath", rpath, dest).Output(); err != nil {
|
|
|
|
|
return fmt.Errorf("failed to update rpath for %s: %v", dest, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fmt.Printf("Relocated binary %s to %s with rpath: %s\n", src, dest, rpath)
|
2024-09-25 11:33:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// RelocateFile relocates a file (regular file, directory, or symlink) to the target prefix.
|
|
|
|
|
func RelocateFile(prefix, src string) error {
|
|
|
|
|
if src != "/" {
|
|
|
|
|
parent := filepath.Dir(src)
|
|
|
|
|
// Recursively relocate parent directories
|
|
|
|
|
if err := RelocateFile(prefix, parent); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Determine the destination path under the prefix
|
|
|
|
|
dst := filepath.Join(prefix, src)
|
|
|
|
|
|
|
|
|
|
// Check if the destination already exists
|
|
|
|
|
if _, err := os.Lstat(dst); os.IsNotExist(err) {
|
|
|
|
|
// Get source file information
|
|
|
|
|
sinfo, err := os.Lstat(src)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("error processing %s: %v", src, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Handle symbolic links
|
|
|
|
|
if (sinfo.Mode() & os.ModeSymlink) != 0 {
|
|
|
|
|
linkTarget, err := os.Readlink(src)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Handle absolute and relative symlinks
|
|
|
|
|
if filepath.IsAbs(linkTarget) {
|
|
|
|
|
relocatedTarget := filepath.Join(prefix, linkTarget)
|
|
|
|
|
fmt.Printf("Creating absolute symlink %s as %s\n", relocatedTarget, dst)
|
|
|
|
|
if err := os.Symlink(relocatedTarget, dst); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
return RelocateFile(prefix, linkTarget)
|
|
|
|
|
} else {
|
|
|
|
|
parent := filepath.Dir(src)
|
|
|
|
|
target := filepath.Join(parent, linkTarget)
|
|
|
|
|
fmt.Printf("Creating relative symlink %s as %s\n", linkTarget, dst)
|
|
|
|
|
if err := os.Symlink(linkTarget, dst); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
return RelocateFile(prefix, target)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Handle directories
|
|
|
|
|
if sinfo.IsDir() {
|
|
|
|
|
fmt.Printf("Creating directory %s\n", dst)
|
|
|
|
|
return os.MkdirAll(dst, sinfo.Mode())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Ignore non-regular files such as sockets, devices, etc.
|
|
|
|
|
if !sinfo.Mode().IsRegular() {
|
|
|
|
|
fmt.Printf("Skipping non-regular file: %s\n", src)
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Handle regular files (ELF or otherwise)
|
|
|
|
|
isElf, err := isDynamicELF(src)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if isElf {
|
|
|
|
|
return relocateELFBinary(prefix, dst, src)
|
|
|
|
|
} else {
|
|
|
|
|
return copyRegularFile(dst, src)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Follow symlinks if the destination is a symlink
|
|
|
|
|
if dinfo, err := os.Lstat(dst); err == nil && (dinfo.Mode()&os.ModeSymlink) != 0 {
|
|
|
|
|
linkTarget, err := os.Readlink(src)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if filepath.IsAbs(linkTarget) {
|
|
|
|
|
return RelocateFile(prefix, linkTarget)
|
|
|
|
|
}
|
|
|
|
|
parent := filepath.Dir(src)
|
|
|
|
|
return RelocateFile(prefix, filepath.Join(parent, linkTarget))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|