mv contrib/cmd tests/cmd (except memfd-bind)

The following commands are moved from `contrib/cmd` to `tests/cmd`:
- fs-idmap
- pidfd-kill
- recvtty
- remap-rootfs
- sd-helper
- seccompagent

Signed-off-by: Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
This commit is contained in:
Akihiro Suda
2024-08-16 00:55:48 +09:00
parent 0377d50458
commit f76489f0af
14 changed files with 32 additions and 28 deletions
-61
View File
@@ -1,61 +0,0 @@
package main
import (
"fmt"
"os"
"os/exec"
"syscall"
"golang.org/x/sys/unix"
)
func main() {
if len(os.Args) != 2 {
fmt.Fprintln(os.Stderr, "usage:", os.Args[0], "path_to_mount_set_attr")
os.Exit(1)
}
src := os.Args[1]
if err := supportsIDMap(src); err != nil {
fmt.Fprintln(os.Stderr, "fatal error:", err)
os.Exit(1)
}
}
func supportsIDMap(src string) error {
treeFD, err := unix.OpenTree(unix.AT_FDCWD, src, uint(unix.OPEN_TREE_CLONE|unix.OPEN_TREE_CLOEXEC|unix.AT_EMPTY_PATH))
if err != nil {
return fmt.Errorf("error calling open_tree %q: %w", src, err)
}
defer unix.Close(treeFD)
cmd := exec.Command("sleep", "5")
cmd.SysProcAttr = &syscall.SysProcAttr{
Cloneflags: syscall.CLONE_NEWUSER,
UidMappings: []syscall.SysProcIDMap{{ContainerID: 0, HostID: 65536, Size: 65536}},
GidMappings: []syscall.SysProcIDMap{{ContainerID: 0, HostID: 65536, Size: 65536}},
}
if err := cmd.Start(); err != nil {
return fmt.Errorf("failed to run the helper binary: %w", err)
}
defer func() {
_ = cmd.Process.Kill()
_ = cmd.Wait()
}()
path := fmt.Sprintf("/proc/%d/ns/user", cmd.Process.Pid)
var userNsFile *os.File
if userNsFile, err = os.Open(path); err != nil {
return fmt.Errorf("unable to get user ns file descriptor: %w", err)
}
defer userNsFile.Close()
attr := unix.MountAttr{
Attr_set: unix.MOUNT_ATTR_IDMAP,
Userns_fd: uint64(userNsFile.Fd()),
}
if err := unix.MountSetattr(treeFD, "", unix.AT_EMPTY_PATH, &attr); err != nil {
return fmt.Errorf("error calling mount_setattr: %w", err)
}
return nil
}
-114
View File
@@ -1,114 +0,0 @@
package main
import (
"errors"
"fmt"
"net"
"os"
"os/signal"
"github.com/urfave/cli"
"golang.org/x/sys/unix"
"github.com/opencontainers/runc/libcontainer/utils"
)
const (
usage = `Open Container Initiative contrib/cmd/pidfd-kill
pidfd-kill is an implementation of a consumer of runC's --pidfd-socket API.
After received SIGTERM, pidfd-kill sends the given signal to init process by
pidfd received from --pidfd-socket.
To use pidfd-kill, just specify a socket path at which you want to receive
pidfd:
$ pidfd-kill [--signal KILL] socket.sock
`
)
func main() {
app := cli.NewApp()
app.Name = "pidfd-kill"
app.Usage = usage
app.Flags = []cli.Flag{
cli.StringFlag{
Name: "signal",
Value: "SIGKILL",
Usage: "Signal to send to the init process",
},
cli.StringFlag{
Name: "pid-file",
Value: "",
Usage: "Path to write the pidfd-kill process ID to",
},
}
app.Action = func(ctx *cli.Context) error {
args := ctx.Args()
if len(args) != 1 {
return errors.New("required a single socket path")
}
socketFile := ctx.Args()[0]
pidFile := ctx.String("pid-file")
if pidFile != "" {
pid := fmt.Sprintf("%d\n", os.Getpid())
if err := os.WriteFile(pidFile, []byte(pid), 0o644); err != nil {
return err
}
defer os.Remove(pidFile)
}
sigStr := ctx.String("signal")
if sigStr == "" {
sigStr = "SIGKILL"
}
sig := unix.SignalNum(sigStr)
pidfdFile, err := recvPidfd(socketFile)
if err != nil {
return err
}
defer pidfdFile.Close()
signalCh := make(chan os.Signal, 16)
signal.Notify(signalCh, unix.SIGTERM)
<-signalCh
return unix.PidfdSendSignal(int(pidfdFile.Fd()), sig, nil, 0)
}
if err := app.Run(os.Args); err != nil {
fmt.Fprintln(os.Stderr, "fatal error:", err)
os.Exit(1)
}
}
func recvPidfd(socketFile string) (*os.File, error) {
ln, err := net.Listen("unix", socketFile)
if err != nil {
return nil, err
}
defer ln.Close()
conn, err := ln.Accept()
if err != nil {
return nil, err
}
defer conn.Close()
unixconn, ok := conn.(*net.UnixConn)
if !ok {
return nil, errors.New("failed to cast to unixconn")
}
socket, err := unixconn.File()
if err != nil {
return nil, err
}
defer socket.Close()
return utils.RecvFile(socket)
}
-250
View File
@@ -1,250 +0,0 @@
/*
* Copyright 2016 SUSE LLC
*
* 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 main
import (
"errors"
"fmt"
"io"
"net"
"os"
"strings"
"sync"
"github.com/containerd/console"
"github.com/opencontainers/runc/libcontainer/utils"
"github.com/urfave/cli"
)
// version will be populated by the Makefile, read from
// VERSION file of the source code.
var version = ""
// gitCommit will be the hash that the binary was built from
// and will be populated by the Makefile
var gitCommit = ""
const (
usage = `Open Container Initiative contrib/cmd/recvtty
recvtty is a reference implementation of a consumer of runC's --console-socket
API. It has two main modes of operation:
* single: Only permit one terminal to be sent to the socket, which is
then hooked up to the stdio of the recvtty process. This is useful
for rudimentary shell management of a container.
* null: Permit as many terminals to be sent to the socket, but they
are read to /dev/null. This is used for testing, and imitates the
old runC API's --console=/dev/pts/ptmx hack which would allow for a
similar trick. This is probably not what you want to use, unless
you're doing something like our bats integration tests.
To use recvtty, just specify a socket path at which you want to receive
terminals:
$ recvtty [--mode <single|null>] socket.sock
`
)
func bail(err error) {
fmt.Fprintf(os.Stderr, "[recvtty] fatal error: %v\n", err)
os.Exit(1)
}
func handleSingle(path string, noStdin bool) error {
// Open a socket.
ln, err := net.Listen("unix", path)
if err != nil {
return err
}
defer ln.Close()
// We only accept a single connection, since we can only really have
// one reader for os.Stdin. Plus this is all a PoC.
conn, err := ln.Accept()
if err != nil {
return err
}
defer conn.Close()
// Close ln, to allow for other instances to take over.
ln.Close()
// Get the fd of the connection.
unixconn, ok := conn.(*net.UnixConn)
if !ok {
return errors.New("failed to cast to unixconn")
}
socket, err := unixconn.File()
if err != nil {
return err
}
defer socket.Close()
// Get the master file descriptor from runC.
master, err := utils.RecvFile(socket)
if err != nil {
return err
}
c, err := console.ConsoleFromFile(master)
if err != nil {
return err
}
if err := console.ClearONLCR(c.Fd()); err != nil {
return err
}
// Copy from our stdio to the master fd.
var (
wg sync.WaitGroup
inErr, outErr error
)
wg.Add(1)
go func() {
_, outErr = io.Copy(os.Stdout, c)
wg.Done()
}()
if !noStdin {
wg.Add(1)
go func() {
_, inErr = io.Copy(c, os.Stdin)
wg.Done()
}()
}
// Only close the master fd once we've stopped copying.
wg.Wait()
c.Close()
if outErr != nil {
return outErr
}
return inErr
}
func handleNull(path string) error {
// Open a socket.
ln, err := net.Listen("unix", path)
if err != nil {
return err
}
defer ln.Close()
// As opposed to handleSingle we accept as many connections as we get, but
// we don't interact with Stdin at all (and we copy stdout to /dev/null).
for {
conn, err := ln.Accept()
if err != nil {
return err
}
go func(conn net.Conn) {
// Don't leave references lying around.
defer conn.Close()
// Get the fd of the connection.
unixconn, ok := conn.(*net.UnixConn)
if !ok {
return
}
socket, err := unixconn.File()
if err != nil {
return
}
defer socket.Close()
// Get the master file descriptor from runC.
master, err := utils.RecvFile(socket)
if err != nil {
return
}
_, _ = io.Copy(io.Discard, master)
}(conn)
}
}
func main() {
app := cli.NewApp()
app.Name = "recvtty"
app.Usage = usage
// Set version to be the same as runC.
var v []string
if version != "" {
v = append(v, version)
}
if gitCommit != "" {
v = append(v, "commit: "+gitCommit)
}
app.Version = strings.Join(v, "\n")
// Set the flags.
app.Flags = []cli.Flag{
cli.StringFlag{
Name: "mode, m",
Value: "single",
Usage: "Mode of operation (single or null)",
},
cli.StringFlag{
Name: "pid-file",
Value: "",
Usage: "Path to write daemon process ID to",
},
cli.BoolFlag{
Name: "no-stdin",
Usage: "Disable stdin handling (no-op for null mode)",
},
}
app.Action = func(ctx *cli.Context) error {
args := ctx.Args()
if len(args) != 1 {
return errors.New("need to specify a single socket path")
}
path := ctx.Args()[0]
pidPath := ctx.String("pid-file")
if pidPath != "" {
pid := fmt.Sprintf("%d\n", os.Getpid())
if err := os.WriteFile(pidPath, []byte(pid), 0o644); err != nil {
return err
}
}
noStdin := ctx.Bool("no-stdin")
switch ctx.String("mode") {
case "single":
if err := handleSingle(path, noStdin); err != nil {
return err
}
case "null":
if err := handleNull(path); err != nil {
return err
}
default:
return fmt.Errorf("need to select a valid mode: %s", ctx.String("mode"))
}
return nil
}
if err := app.Run(os.Args); err != nil {
bail(err)
}
}
-143
View File
@@ -1,143 +0,0 @@
package main
import (
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
"syscall"
"github.com/urfave/cli"
"github.com/opencontainers/runtime-spec/specs-go"
)
const usage = `contrib/cmd/remap-rootfs
remap-rootfs is a helper tool to remap the root filesystem of a Open Container
Initiative bundle using user namespaces such that the file owners are remapped
from "host" mappings to the user namespace's mappings.
Effectively, this is a slightly more complicated 'chown -R', and is primarily
used within runc's integration tests to remap the test filesystem to match the
test user namespace. Note that calling remap-rootfs multiple times, or changing
the mapping and then calling remap-rootfs will likely produce incorrect results
because we do not "un-map" any pre-applied mappings from previous remap-rootfs
calls.
Note that the bundle is assumed to be produced by a trusted source, and thus
malicious configuration files will likely not be handled safely.
To use remap-rootfs, simply pass it the path to an OCI bundle (a directory
containing a config.json):
$ sudo remap-rootfs ./bundle
`
func toHostID(mappings []specs.LinuxIDMapping, id uint32) (int, bool) {
for _, m := range mappings {
if m.ContainerID <= id && id < m.ContainerID+m.Size {
return int(m.HostID + id), true
}
}
return -1, false
}
type inodeID struct {
Dev, Ino uint64
}
func toInodeID(st *syscall.Stat_t) inodeID {
return inodeID{Dev: st.Dev, Ino: st.Ino}
}
func remapRootfs(root string, uidMap, gidMap []specs.LinuxIDMapping) error {
seenInodes := make(map[inodeID]struct{})
return filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
mode := info.Mode()
st := info.Sys().(*syscall.Stat_t)
// Skip symlinks.
if mode.Type() == os.ModeSymlink {
return nil
}
// Skip hard-links to files we've already remapped.
id := toInodeID(st)
if _, seen := seenInodes[id]; seen {
return nil
}
seenInodes[id] = struct{}{}
// Calculate the new uid:gid.
uid := st.Uid
newUID, ok1 := toHostID(uidMap, uid)
gid := st.Gid
newGID, ok2 := toHostID(gidMap, gid)
// Skip files that cannot be mapped.
if !ok1 || !ok2 {
niceName := path
if relName, err := filepath.Rel(root, path); err == nil {
niceName = "/" + relName
}
fmt.Printf("skipping file %s: cannot remap user %d:%d -> %d:%d\n", niceName, uid, gid, newUID, newGID)
return nil
}
if err := os.Lchown(path, newUID, newGID); err != nil {
return err
}
// Re-apply any setid bits that would be cleared due to chown(2).
return os.Chmod(path, mode)
})
}
func main() {
app := cli.NewApp()
app.Name = "remap-rootfs"
app.Usage = usage
app.Action = func(ctx *cli.Context) error {
args := ctx.Args()
if len(args) != 1 {
return errors.New("exactly one bundle argument must be provided")
}
bundle := args[0]
configFile, err := os.Open(filepath.Join(bundle, "config.json"))
if err != nil {
return err
}
defer configFile.Close()
var spec specs.Spec
if err := json.NewDecoder(configFile).Decode(&spec); err != nil {
return fmt.Errorf("parsing config.json: %w", err)
}
if spec.Root == nil {
return errors.New("invalid config.json: root section is null")
}
rootfs := filepath.Join(bundle, spec.Root.Path)
if spec.Linux == nil {
return errors.New("invalid config.json: linux section is null")
}
uidMap := spec.Linux.UIDMappings
gidMap := spec.Linux.GIDMappings
if len(uidMap) == 0 && len(gidMap) == 0 {
fmt.Println("skipping remapping -- no userns mappings specified")
return nil
}
return remapRootfs(rootfs, uidMap, gidMap)
}
if err := app.Run(os.Args); err != nil {
fmt.Fprintln(os.Stderr, "error:", err)
os.Exit(1)
}
}
-86
View File
@@ -1,86 +0,0 @@
package main
import (
"flag"
"fmt"
"os"
"github.com/sirupsen/logrus"
"github.com/opencontainers/runc/libcontainer/cgroups"
"github.com/opencontainers/runc/libcontainer/cgroups/systemd"
"github.com/opencontainers/runc/libcontainer/configs"
)
func usage() {
fmt.Print(`Open Container Initiative contrib/cmd/sd-helper
sd-helper is a tool that uses runc/libcontainer/cgroups/systemd package
functionality to communicate to systemd in order to perform various operations.
Currently this is limited to starting and stopping systemd transient slice
units.
Usage:
sd-helper [-debug] [-parent <pname>] {start|stop} <name>
Example:
sd-helper -parent system.slice start system-pod123.slice
`)
os.Exit(1)
}
var (
debug = flag.Bool("debug", false, "enable debug output")
parent = flag.String("parent", "", "parent unit name")
)
func main() {
if !systemd.IsRunningSystemd() {
logrus.Fatal("systemd is required")
}
// Set the flags.
flag.Parse()
if *debug {
logrus.SetLevel(logrus.DebugLevel)
}
if flag.NArg() != 2 {
usage()
}
cmd := flag.Arg(0)
unit := flag.Arg(1)
err := unitCommand(cmd, unit, *parent)
if err != nil {
logrus.Fatal(err)
}
}
func newManager(config *configs.Cgroup) (cgroups.Manager, error) {
if cgroups.IsCgroup2UnifiedMode() {
return systemd.NewUnifiedManager(config, "")
}
return systemd.NewLegacyManager(config, nil)
}
func unitCommand(cmd, name, parent string) error {
podConfig := &configs.Cgroup{
Name: name,
Parent: parent,
Resources: &configs.Resources{},
}
pm, err := newManager(podConfig)
if err != nil {
return err
}
switch cmd {
case "start":
return pm.Apply(-1)
case "stop":
return pm.Destroy()
}
return fmt.Errorf("unknown command: %s", cmd)
}
-70
View File
@@ -1,70 +0,0 @@
# Seccomp Agent
## Warning
Please note this is an example agent, as such it is possible that specially
crafted messages can produce bad behaviour. Please use it as an example only.
Also, this agent is used for integration tests. Be aware that changing the
behaviour can break the integration tests.
## Get started
Compile runc and seccompagent:
```bash
make all
```
Run the seccomp agent in the background:
```bash
sudo ./contrib/cmd/seccompagent/seccompagent &
```
Prepare a container:
```bash
mkdir container-seccomp-notify
cd container-seccomp-notify
mkdir rootfs
docker export $(docker create busybox) | tar -C rootfs -xvf -
```
Then, generate a config.json by running the script gen-seccomp-example-cfg.sh
from the directory where this README.md is in the container directory you
prepared earlier (`container-seccomp-notify`).
Then start the container:
```bash
runc run mycontainerid
```
The container will output something like this:
```bash
+ cd /dev/shm
+ mkdir test-dir
+ touch test-file
+ chmod 777 test-file
chmod: changing permissions of 'test-file': No medium found
+ stat /dev/shm/test-dir-foo
File: /dev/shm/test-dir-foo
Size: 40 Blocks: 0 IO Block: 4096 directory
Device: 3eh/62d Inode: 2 Links: 2
Access: (0755/drwxr-xr-x) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2021-09-09 15:03:13.043716040 +0000
Modify: 2021-09-09 15:03:13.043716040 +0000
Change: 2021-09-09 15:03:13.043716040 +0000
Birth: -
+ ls -l /dev/shm
total 0
drwxr-xr-x 2 root root 40 Sep 9 15:03 test-dir-foo
-rw-r--r-- 1 root root 0 Sep 9 15:03 test-file
+ echo Note the agent added a suffix for the directory name and chmod fails
Note the agent added a suffix for the directory name and chmod fails
```
This shows a simple example that runs in /dev/shm just because it is a tmpfs in
the example config.json.
The agent makes all chmod calls fail with ENOMEDIUM, as the example output shows.
For mkdir, the agent adds a "-foo" suffix: the container runs "mkdir test-dir"
but the directory created is "test-dir-foo".
@@ -1,35 +0,0 @@
#!/usr/bin/env bash
# Detect if we are running inside bats (i.e. inside integration tests) or just
# called by an end-user
# bats-core v1.2.1 defines BATS_RUN_TMPDIR
if [ -z "$BATS_RUN_TMPDIR" ]; then
# When not running in bats, we create the config.json
set -e
runc spec
fi
# We can't source $(dirname $0)/../../../tests/integration/helpers.bash as that
# exits when not running inside bats. We can do hacks, but just to redefine
# update_config() seems clearer. We don't even really need to keep them in sync.
function update_config() {
jq "$1" "./config.json" | awk 'BEGIN{RS="";getline<"-";print>ARGV[1]}' "./config.json"
}
update_config '.linux.seccomp = {
"defaultAction": "SCMP_ACT_ALLOW",
"listenerPath": "/run/seccomp-agent.socket",
"listenerMetadata": "foo",
"architectures": [ "SCMP_ARCH_X86", "SCMP_ARCH_X32", "SCMP_ARCH_X86_64" ],
"syscalls": [
{
"names": [ "chmod", "fchmod", "fchmodat", "mkdir" ],
"action": "SCMP_ACT_NOTIFY"
}
]
}'
update_config '.process.args = [
"sh",
"-c",
"set -x; cd /dev/shm; mkdir test-dir; touch test-file; chmod 777 test-file; stat /dev/shm/test-dir-foo && ls -l /dev/shm && echo \"Note the agent added a suffix for the directory name and chmod fails\" "
]'
-290
View File
@@ -1,290 +0,0 @@
//go:build linux && seccomp
package main
import (
"bytes"
"encoding/json"
"errors"
"flag"
"fmt"
"net"
"os"
"path/filepath"
"strings"
securejoin "github.com/cyphar/filepath-securejoin"
"github.com/opencontainers/runtime-spec/specs-go"
libseccomp "github.com/seccomp/libseccomp-golang"
"github.com/sirupsen/logrus"
"golang.org/x/sys/unix"
)
var (
socketFile string
pidFile string
)
func closeStateFds(recvFds []int) {
for i := range recvFds {
unix.Close(i)
}
}
// parseStateFds returns the seccomp-fd and closes the rest of the fds in recvFds.
// In case of error, no fd is closed.
// StateFds is assumed to be formatted as specs.ContainerProcessState.Fds and
// recvFds the corresponding list of received fds in the same SCM_RIGHT message.
func parseStateFds(stateFds []string, recvFds []int) (uintptr, error) {
// Let's find the index in stateFds of the seccomp-fd.
idx := -1
err := false
for i, name := range stateFds {
if name == specs.SeccompFdName && idx == -1 {
idx = i
continue
}
// We found the seccompFdName twice. Error out!
if name == specs.SeccompFdName && idx != -1 {
err = true
}
}
if idx == -1 || err {
return 0, errors.New("seccomp fd not found or malformed containerProcessState.Fds")
}
if idx >= len(recvFds) || idx < 0 {
return 0, errors.New("seccomp fd index out of range")
}
fd := uintptr(recvFds[idx])
for i := range recvFds {
if i == idx {
continue
}
unix.Close(recvFds[i])
}
return fd, nil
}
func handleNewMessage(sockfd int) (uintptr, string, error) {
const maxNameLen = 4096
stateBuf := make([]byte, maxNameLen)
oobSpace := unix.CmsgSpace(4)
oob := make([]byte, oobSpace)
n, oobn, _, _, err := unix.Recvmsg(sockfd, stateBuf, oob, 0)
if err != nil {
return 0, "", err
}
if n >= maxNameLen || oobn != oobSpace {
return 0, "", fmt.Errorf("recvfd: incorrect number of bytes read (n=%d oobn=%d)", n, oobn)
}
// Truncate.
stateBuf = stateBuf[:n]
oob = oob[:oobn]
scms, err := unix.ParseSocketControlMessage(oob)
if err != nil {
return 0, "", err
}
if len(scms) != 1 {
return 0, "", fmt.Errorf("recvfd: number of SCMs is not 1: %d", len(scms))
}
scm := scms[0]
fds, err := unix.ParseUnixRights(&scm)
if err != nil {
return 0, "", err
}
containerProcessState := &specs.ContainerProcessState{}
err = json.Unmarshal(stateBuf, containerProcessState)
if err != nil {
closeStateFds(fds)
return 0, "", fmt.Errorf("cannot parse OCI state: %w", err)
}
fd, err := parseStateFds(containerProcessState.Fds, fds)
if err != nil {
closeStateFds(fds)
return 0, "", err
}
return fd, containerProcessState.Metadata, nil
}
func readArgString(pid uint32, offset int64) (string, error) {
buffer := make([]byte, 4096) // PATH_MAX
memfd, err := unix.Open(fmt.Sprintf("/proc/%d/mem", pid), unix.O_RDONLY, 0o777)
if err != nil {
return "", err
}
defer unix.Close(memfd)
_, err = unix.Pread(memfd, buffer, offset)
if err != nil {
return "", err
}
buffer[len(buffer)-1] = 0
s := buffer[:bytes.IndexByte(buffer, 0)]
return string(s), nil
}
func runMkdirForContainer(pid uint32, fileName string, mode uint32, metadata string) error {
// We validated before that metadata is not a string that can make
// newFile a file in a different location other than root.
newFile := fmt.Sprintf("%s-%s", fileName, metadata)
root := fmt.Sprintf("/proc/%d/cwd/", pid)
if strings.HasPrefix(fileName, "/") {
// If it starts with /, use the rootfs as base
root = fmt.Sprintf("/proc/%d/root/", pid)
}
path, err := securejoin.SecureJoin(root, newFile)
if err != nil {
return err
}
return unix.Mkdir(path, mode)
}
// notifHandler handles seccomp notifications and responses
func notifHandler(fd libseccomp.ScmpFd, metadata string) {
defer unix.Close(int(fd))
for {
req, err := libseccomp.NotifReceive(fd)
if err != nil {
logrus.Errorf("Error in NotifReceive(): %s", err)
continue
}
syscallName, err := req.Data.Syscall.GetName()
if err != nil {
logrus.Errorf("Error decoding syscall %v(): %s", req.Data.Syscall, err)
continue
}
logrus.Debugf("Received syscall %q, pid %v, arch %q, args %+v", syscallName, req.Pid, req.Data.Arch, req.Data.Args)
resp := &libseccomp.ScmpNotifResp{
ID: req.ID,
Error: 0,
Val: 0,
Flags: libseccomp.NotifRespFlagContinue,
}
// TOCTOU check
if err := libseccomp.NotifIDValid(fd, req.ID); err != nil {
logrus.Errorf("TOCTOU check failed: req.ID is no longer valid: %s", err)
continue
}
switch syscallName {
case "mkdir":
fileName, err := readArgString(req.Pid, int64(req.Data.Args[0]))
if err != nil {
logrus.Errorf("Cannot read argument: %s", err)
resp.Error = int32(unix.ENOSYS)
resp.Val = ^uint64(0) // -1
goto sendResponse
}
logrus.Debugf("mkdir: %q", fileName)
// TOCTOU check
if err := libseccomp.NotifIDValid(fd, req.ID); err != nil {
logrus.Errorf("TOCTOU check failed: req.ID is no longer valid: %s", err)
continue
}
err = runMkdirForContainer(req.Pid, fileName, uint32(req.Data.Args[1]), metadata)
if err != nil {
resp.Error = int32(unix.ENOSYS)
resp.Val = ^uint64(0) // -1
}
resp.Flags = 0
case "chmod", "fchmod", "fchmodat":
resp.Error = int32(unix.ENOMEDIUM)
resp.Val = ^uint64(0) // -1
resp.Flags = 0
}
sendResponse:
if err = libseccomp.NotifRespond(fd, resp); err != nil {
logrus.Errorf("Error in notification response: %s", err)
continue
}
}
}
func main() {
flag.StringVar(&socketFile, "socketfile", "/run/seccomp-agent.socket", "Socket file")
flag.StringVar(&pidFile, "pid-file", "", "Pid file")
logrus.SetLevel(logrus.DebugLevel)
// Parse arguments
flag.Parse()
if flag.NArg() > 0 {
flag.PrintDefaults()
logrus.Fatal("Invalid command")
}
if err := os.Remove(socketFile); err != nil && !errors.Is(err, os.ErrNotExist) {
logrus.Fatalf("Cannot cleanup socket file: %v", err)
}
if pidFile != "" {
pid := fmt.Sprintf("%d", os.Getpid())
if err := os.WriteFile(pidFile, []byte(pid), 0o644); err != nil {
logrus.Fatalf("Cannot write pid file: %v", err)
}
}
logrus.Info("Waiting for seccomp file descriptors")
l, err := net.Listen("unix", socketFile)
if err != nil {
logrus.Fatalf("Cannot listen: %s", err)
}
defer l.Close()
for {
conn, err := l.Accept()
if err != nil {
logrus.Errorf("Cannot accept connection: %s", err)
continue
}
socket, err := conn.(*net.UnixConn).File()
conn.Close()
if err != nil {
logrus.Errorf("Cannot get socket: %v", err)
continue
}
newFd, metadata, err := handleNewMessage(int(socket.Fd()))
socket.Close()
if err != nil {
logrus.Errorf("Error receiving seccomp file descriptor: %v", err)
continue
}
// Make sure we don't allow strings like "/../p", as that means
// a file in a different location than expected. We just want
// safe things to use as a suffix for a file name.
metadata = filepath.Base(metadata)
if strings.Contains(metadata, "/") {
// Fallback to a safe string.
metadata = "agent-generated-suffix"
}
logrus.Infof("Received new seccomp fd: %v", newFd)
go notifHandler(libseccomp.ScmpFd(newFd), metadata)
}
}
-9
View File
@@ -1,9 +0,0 @@
//go:build !linux || !seccomp
package main
import "fmt"
func main() {
fmt.Println("Not supported, to use this compile with build tag: seccomp.")
}