In the Plug It In: Designing for Extensibility there was something untold:
But what if your driver requires some third-party tools to be available on the system? That’s where things get a bit more complicated but still solvable.
Indeed, some of the power drivers supported by MAAS rely on third-party tools that need to be installed on the system. But what if there is no way to run snap install
or apt install
?
Have you ever wanted to ship a standalone Go executable that includes and runs other binaries without touching the file system? Or maybe you have to do it because off-disk execution is hardened or you have a containerized environments. It is absolutely doable with Go and Linux!
That said, let’s bundle everything into a single binary. Here’s what we need:
-
Being aware of the //go:embed directive
-
Knowledge of the following system calls:
- memfd_create creates an anonymous file and returns a file descriptor.
- write writes to a file descriptor.
- execveat executes the program referred to by a file descriptor
-
Because the syscall package doesn’t provide wrappers for all the syscalls, we need to use
syscall.Syscall
and look up the required syscall numbersNR
.
You can get NR
from Linux source, or use a handy table by guitmz
NR | syscall |
---|---|
319 | memfd_create |
322 | execveat |
// memfd creates an anonymous file and returns a file
// descriptor that refers to it. The file behaves like a regular
// file, and so can be modified, truncated, memory-mapped, and so on.
// However, unlike a regular file, it lives in RAM and has a volatile
// backing storage.
func memfd(path string) (uintptr, error) {
s, err := syscall.BytePtrFromString(path)
if err != nil {
return 0, err
}
r1, _, err := syscall.Syscall(319, uintptr(unsafe.Pointer(s)), 0, 0)
if int(r1) == -1 {
return r1, err
}
return r1, nil
}
// writefd writes bytes to a file descriptor
func writefd(fd uintptr, buf []byte) (err error) {
_, err = syscall.Write(int(fd), buf)
if err != nil {
return err
}
return nil
}
// execfd executes the program referred by file descriptor
func execfd(fd uintptr) (err error) {
s, err := syscall.BytePtrFromString("")
if err != nil {
return err
}
ret, _, errno := syscall.Syscall6(322, fd, uintptr(unsafe.Pointer(s)), 0, 0, 0x1000, 0)
if int(ret) == -1 {
print(errno)
return errno
}
return nil
}
- With wrappers for all the necessary syscalls in place, all we need is to embed our binary. For testing purposes, we can just copy
id
cp $(which id) .
- I’ve omitted error handling, but here’s what our
main.go
looks like:
package main
import (
_ "embed"
"syscall"
"unsafe"
)
//go:embed id
var id []byte
func main() {
memfd, _ := memfd("id")
_ = writefd(memfd, id)
print(execfd(memfd))
}
- Result
❯ go run main.go
uid=1000(troyanov) gid=1000(troyanov) groups=1000(troyanov),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),122(lpadmin),134(lxd),135(sambashare),999(dock
er)
❯ strace -e memfd_create,execveat,write ./main
memfd_create("id", 0) = 3
write(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\3002\0\0\0\0\0\0"..., 39424) = 39424
execveat(3, "", NULL, NULL, AT_EMPTY_PATH) = 0
write(1, "uid=1000(troyanov) gid=1000(troy"..., 156uid=1000(troyanov) gid=1000(troyanov) groups=1000(troyanov),4(adm),24(cdrom),27(sudo),30(dip),46(plug
dev),122(lpadmin),134(lxd),135(sambashare),999(docker)
) = 156
+++ exited with 0 +++
Thats how you can create sort of Matryoshka doll if you want.