execve

package module
v0.0.0-...-e5c3a7f Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 6, 2023 License: ISC Imports: 3 Imported by: 1

README

execve

Go Reference

A Go package for fexecve(3) and execveat(2).

EXAMPLES

  • fdexe: embed an executable in a Go binary and run from memory

  • dfdexe: embed a directory of executables in a Go binary and run from memory

  • ioexe: read an executable from stdin and run from memory

Run an executable using a file descriptor

package main

import (
	"fmt"
	"os"
	"os/exec"

	"codeberg.org/msantos/execve"
)

func main() {
	if len(os.Args) < 2 {
		fmt.Fprintf(os.Stderr, "usage: <cmd> <args>\n")
		os.Exit(2)
	}

	arg0, err := exec.LookPath(os.Args[1])
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(127)
	}

	fd, err := os.Open(arg0)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	if err := execve.Fexecve(fd.Fd(), os.Args[1:], os.Environ()); err != nil {
		fmt.Fprintln(os.Stderr, err)
	}

	os.Exit(126)
}

Execute a script using a file descriptor

package main

import (
	"fmt"
	"os"
	"os/exec"
	"syscall"

	"codeberg.org/msantos/execve"
)

func main() {
	if len(os.Args) < 2 {
		fmt.Fprintf(os.Stderr, "usage: <cmd> <args>\n")
		os.Exit(2)
	}

	arg0, err := exec.LookPath(os.Args[1])
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(127)
	}

	fd, err := syscall.Open(arg0, syscall.O_RDONLY, 0)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	os.Args[1] = fmt.Sprintf("/dev/fd/%d", fd)

	if err := execve.Fexecve(uintptr(fd), os.Args[1:], os.Environ()); err != nil {
		fmt.Fprintln(os.Stderr, err)
	}

	os.Exit(126)
}

Documentation

Overview

Package execve is a wrapper around the system execve(2) and fexecve(3) system calls.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Execveat

func Execveat(fd uintptr, pathname string, argv []string, envv []string, flags int) error

Execveat executes at program at a path using a file descriptor. The go runtime process image is replaced by the executable described by the directory file descriptor and pathname.

Example
package main

import (
	"fmt"
	"os"

	"codeberg.org/msantos/execve"
	"golang.org/x/sys/unix"
)

func main() {
	if err := os.Chdir("/bin"); err != nil {
		fmt.Fprintln(os.Stderr, err)
		return
	}

	n := int32(unix.AT_FDCWD)
	AT_FDCWD := uintptr(uint32(n))

	if err := execve.Execveat(
		AT_FDCWD,
		"sh", []string{"sh", "-c", "echo test"},
		[]string{},
		0,
	); err != nil {
		fmt.Fprintln(os.Stderr, err)
		return
	}
}
Output:

func Fexecve

func Fexecve(fd uintptr, argv []string, envv []string) error

Fexecve executes the program referred to by a file descriptor. The go runtime process image is replaced with the executable referred to by the file descriptor. The file descriptor should be opened with the O_CLOEXEC flag set (the default when using os.Open) to prevent the fd from leaking to the new process image.

The exception to this rule is running scripts: the file descriptor must be opened without O_CLOEXEC.

BUG(fexecve): If fd refers to a script (i.e., it is an executable text file that names a script interpreter with a first line that begins with the characters #!) and the close-on-exec flag has been set for fd, then fexecve() fails with the error ENOENT. This error occurs because, by the time the script interpreter is executed, fd has already been closed because of the close-on-exec flag. Thus, the close-on-exec flag can't be set on fd if it refers to a script, leading to the problems described in NOTES (fexecve(3)).

Example
package main

import (
	"fmt"
	"os"

	"codeberg.org/msantos/execve"
)

func main() {
	fd, err := os.Open("/bin/sh")
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		return
	}

	if err := execve.Fexecve(
		fd.Fd(),
		[]string{"sh", "-c", "echo test"},
		[]string{},
	); err != nil {
		fmt.Fprintln(os.Stderr, err)
		return
	}
}
Output:

Example (Memfd)

ExampleFexecve_memfd is an example of running an executable from memory.

package main

import (
	"fmt"
	"io"
	"os"
	"os/exec"

	"codeberg.org/msantos/execve"
	"golang.org/x/sys/unix"
)

func main() {
	path, err := exec.LookPath("echo")
	if err != nil {
		fmt.Fprintf(os.Stderr, "LookPath: %v", err)
		return
	}

	exe, err := os.Open(path)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Open: %v", err)
		return
	}

	fd, err := unix.MemfdCreate("ExampleFexecve_memfd", unix.MFD_CLOEXEC)
	if err != nil {
		fmt.Fprintf(os.Stderr, "MemfdCreate: %v", err)
		return
	}

	f := os.NewFile(uintptr(fd), "ExampleFexecve_memfd")

	if _, err := io.Copy(f, exe); err != nil {
		fmt.Fprintf(os.Stderr, "Copy: %v", err)
		return
	}

	if err := execve.Fexecve(f.Fd(), []string{"-n", "test"}, os.Environ()); err != nil {
		fmt.Fprintf(os.Stderr, "Fexecve: %v", err)
		return
	}
}
Output:

Example (Script)
package main

import (
	"fmt"
	"os"
	"syscall"

	"codeberg.org/msantos/execve"
)

func main() {
	// Make a shell script
	file, err := os.CreateTemp("", "script")
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		return
	}

	name := file.Name()

	if _, err := file.Write([]byte("#!/bin/sh\necho $@")); err != nil {
		fmt.Fprintln(os.Stderr, err)
		return
	}

	if err := file.Chmod(0o755); err != nil {
		fmt.Fprintln(os.Stderr, err)
		return
	}

	if err := file.Close(); err != nil {
		fmt.Fprintln(os.Stderr, err)
		return
	}

	// Get a file descriptor with O_CLOEXEC unset
	fd, err := syscall.Open(name, syscall.O_RDONLY, 0)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		return
	}

	sh := fmt.Sprintf("/dev/fd/%d", fd)

	if err := execve.Fexecve(
		uintptr(fd),
		[]string{sh, "test"},
		[]string{},
	); err != nil {
		fmt.Fprintln(os.Stderr, err)
		return
	}
}
Output:

Types

This section is empty.

Notes

Bugs

  • If fd refers to a script (i.e., it is an executable text file that names a script interpreter with a first line that begins with the characters #!) and the close-on-exec flag has been set for fd, then fexecve() fails with the error ENOENT. This error occurs because, by the time the script interpreter is executed, fd has already been closed because of the close-on-exec flag. Thus, the close-on-exec flag can't be set on fd if it refers to a script, leading to the problems described in NOTES (fexecve(3)).

Directories

Path Synopsis
examples
dfdexe
Dfdexe embeds and runs a directory of executables from memory.
Dfdexe embeds and runs a directory of executables from memory.
fdexe
Fdexe embeds and runs an executable from memory.
Fdexe embeds and runs an executable from memory.
ioexe
Ioexe reads and runs an executable from stdin.
Ioexe reads and runs an executable from stdin.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL