Files
visor/tools/embeddedbinary/embeddedbinary_template.go
T
2025-10-22 15:02:53 -07:00

177 lines
6.0 KiB
Go

// Copyright 2023 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package embeddedbinary embeds an external binary and provides a function to
// exec it.
package embeddedbinary
import (
"bytes"
"compress/flate"
_ "embed"
"fmt"
"io"
"os"
"path"
"runtime"
"syscall"
"golang.org/x/sys/unix"
)
// BinaryName is the name of the embedded binary.
const BinaryName = "embedded.bin.name"
//go:embed embedded.bin.flate
var compressedBinary []byte
// Options is the set of options to execute the embedded binary.
type Options struct {
// Argv is the set of arguments to exec with.
// `Argv[0]` is the name of the binary as invoked.
// If Argv is empty, it will default to a single-element slice, with
// `Argv[0]` being the binary name.
Argv []string
// Envv is the set of environment variables to pass to the executed process.
Envv []string
// Files is the set of file descriptors to pass to forked processes.
// Only used when forking, not pure exec'ing.
Files []uintptr
// SysProcAttr provides OS-specific options to the executed process.
// Only used when forking, not pure exec'ing.
SysProcAttr *unix.SysProcAttr
}
// Bogus import to satisfy the compiler that we are using the flate import,
// even when compression is disabled.
const _ = flate.NoCompression
// run decompresses and run the embedded binary with the given arguments.
// If fork is true, the binary runs in a separate process, and its PID is
// returned.
// Otherwise, the binary is exec'd, so the current process stops executing.
func run(options *Options, fork bool) (int, error) {
if len(options.Argv) == 0 {
options.Argv = []string{BinaryName}
}
// The "flate.NewReader" below may be replaced by "io.Reader" when
// compression is off.
binaryReader := flate.NewReader(bytes.NewReader(compressedBinary))
runtime.LockOSThread()
defer runtime.UnlockOSThread()
oldMask := unix.Umask(0077)
defer unix.Umask(oldMask)
tmpFD := -1
// /tmp is sometimes mounted noexec for "security" reasons. Handle this by
// falling back to executing from a memfd.
parentDir := os.TempDir()
var parentDirStatfs unix.Statfs_t
if err := unix.Statfs(parentDir, &parentDirStatfs); err == nil && parentDirStatfs.Flags&unix.ST_NOEXEC == 0 {
tmpDir, err := os.MkdirTemp(parentDir, "gvisor.*.tmp")
if err != nil {
return 0, fmt.Errorf("cannot create temp directory: %w", err)
}
tmpDirHandle, err := os.Open(tmpDir)
if err != nil {
return 0, fmt.Errorf("cannot open temp directory: %w", err)
}
defer tmpDirHandle.Close()
binPath := path.Join(tmpDir, BinaryName)
tmpFile, err := os.OpenFile(binPath, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0700)
if err != nil {
return 0, fmt.Errorf("cannot open temp file: %w", err)
}
if err := os.RemoveAll(tmpDir); err != nil {
return 0, fmt.Errorf("cannot remove temp directory: %w", err)
}
unix.Umask(oldMask)
if _, err := io.Copy(tmpFile, binaryReader); err != nil {
tmpFile.Close()
return 0, fmt.Errorf("cannot decompress embedded binary or write it to temporary file: %w", err)
}
// Reopen the file for reading.
tmpFileReadOnly, err := os.OpenFile(fmt.Sprintf("/proc/self/fd/%d", tmpFile.Fd()), os.O_RDONLY, 0700)
if err != nil {
tmpFile.Close()
return 0, fmt.Errorf("cannot re-open temp file for reading: %w", err)
}
if err := tmpFile.Close(); err != nil {
return 0, fmt.Errorf("cannot close temp file: %w", err)
}
defer tmpFileReadOnly.Close()
tmpFD = int(tmpFileReadOnly.Fd())
} else {
var err error
tmpFD, err = unix.MemfdCreate(BinaryName, unix.MFD_ALLOW_SEALING|unix.MFD_EXEC)
if err == unix.EINVAL {
// Assume that the kernel precedes 105ff5339f498 ("mm/memfd: add
// MFD_NOEXEC_SEAL and MFD_EXEC"), Linux 6.3+.
tmpFD, err = unix.MemfdCreate(BinaryName, unix.MFD_ALLOW_SEALING)
}
if err != nil {
return 0, fmt.Errorf("cannot create memfd: %w", err)
}
tmpFile := os.NewFile(uintptr(tmpFD), BinaryName)
defer tmpFile.Close()
unix.Umask(oldMask)
if _, err := io.Copy(tmpFile, binaryReader); err != nil {
return 0, fmt.Errorf("cannot decompress embedded binary or write it to temporary memfd: %w", err)
}
// Prevent future writes to the memfd.
if _, err := unix.FcntlInt(uintptr(tmpFD), unix.F_ADD_SEALS, unix.F_SEAL_SEAL|unix.F_SEAL_SHRINK|unix.F_SEAL_GROW|unix.F_SEAL_WRITE); err != nil {
return 0, fmt.Errorf("cannot seal memfd: %w", err)
}
}
if _, err := unix.Seek(tmpFD, 0, unix.SEEK_SET); err != nil {
return 0, fmt.Errorf("cannot seek temp file back to 0: %w", err)
}
if fork {
// Go's syscall/exec_linux.go:forkAndExecInChild1() can clobber FDs
// outside of syscall.ProcAttr.Files, including tmpFD, so the FD that
// the child execs must be in syscall.ProcAttr.Files to ensure that
// it's valid at time of execve().
childTmpFD := len(options.Files)
files := append(options.Files, uintptr(tmpFD))
fdPath := fmt.Sprintf("/proc/self/fd/%d", childTmpFD)
return syscall.ForkExec(fdPath, options.Argv, &syscall.ProcAttr{
Env: options.Envv,
Files: files,
Sys: options.SysProcAttr,
})
}
fdPath := fmt.Sprintf("/proc/self/fd/%d", tmpFD)
if err := unix.Exec(fdPath, options.Argv, options.Envv); err != nil {
return 0, fmt.Errorf("cannot exec embedded binary: %w", err)
}
panic("unreachable")
}
// Exec execs the embedded binary. The current process is replaced.
// This function only returns if unsuccessful.
func Exec(options Options) error {
_, err := run(&options, false)
return err
}
// ForkExec runs the embedded binary in a separate process.
// Returns the PID of the child process.
func ForkExec(options Options) (int, error) {
return run(&options, true)
}