diff --git a/pkg/binfmt/binfmt.go b/pkg/binfmt/binfmt.go new file mode 100644 index 0000000000..e6f0ac62fc --- /dev/null +++ b/pkg/binfmt/binfmt.go @@ -0,0 +1,83 @@ +//go:build linux + +package binfmt + +import ( + "bufio" + "errors" + "fmt" + "os" + "path/filepath" + "strings" + "syscall" + + "github.com/containers/storage/pkg/unshare" + "github.com/sirupsen/logrus" + "golang.org/x/sys/unix" +) + +// MaybeRegister() calls Register() if the current context is a rootless one, +// or if the "container" environment variable suggests that we're in a +// container. +func MaybeRegister(configurationSearchDirectories []string) error { + if unshare.IsRootless() || os.Getenv("container") != "" { // we _also_ own our own mount namespace + return Register(configurationSearchDirectories) + } + return nil +} + +// Register() registers binfmt.d emulators described by configuration files in +// the passed-in slice of directories, or in the union of /etc/binfmt.d, +// /run/binfmt.d, and /usr/lib/binfmt.d if the slice has no items. If any +// emulators are configured, it will attempt to mount a binfmt_misc filesystem +// in the current mount namespace first, ignoring only EPERM and EACCES errors. +func Register(configurationSearchDirectories []string) error { + if len(configurationSearchDirectories) == 0 { + configurationSearchDirectories = []string{"/etc/binfmt.d", "/run/binfmt.d", "/usr/lib/binfmt.d"} + } + mounted := false + for _, searchDir := range configurationSearchDirectories { + globs, err := filepath.Glob(filepath.Join(searchDir, "*.conf")) + if err != nil { + return fmt.Errorf("looking for binfmt.d configuration in %q: %w", searchDir, err) + } + for _, conf := range globs { + f, err := os.Open(conf) + if err != nil { + return fmt.Errorf("reading binfmt.d configuration: %w", err) + } + scanner := bufio.NewScanner(f) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if len(line) == 0 || line[0] == ';' || line[0] == '#' { + continue + } + if !mounted { + if err = unix.Mount("none", "/proc/sys/fs/binfmt_misc", "binfmt_misc", 0, ""); err != nil { + if errors.Is(err, syscall.EPERM) || errors.Is(err, syscall.EACCES) { + // well, we tried. no need to make a stink about it + return nil + } + return fmt.Errorf("mounting binfmt_misc: %w", err) + } + mounted = true + } + reg, err := os.Create("/proc/sys/fs/binfmt_misc/register") + if err != nil { + return fmt.Errorf("registering(open): %w", err) + } + if _, err = fmt.Fprintf(reg, "%s\n", line); err != nil { + return fmt.Errorf("registering(write): %w", err) + } + logrus.Tracef("registered binfmt %q", line) + if err = reg.Close(); err != nil { + return fmt.Errorf("registering(close): %w", err) + } + } + if err := f.Close(); err != nil { + return fmt.Errorf("reading binfmt.d configuration: %w", err) + } + } + } + return nil +} diff --git a/pkg/binfmt/binfmt_unsupported.go b/pkg/binfmt/binfmt_unsupported.go new file mode 100644 index 0000000000..6bb1667e50 --- /dev/null +++ b/pkg/binfmt/binfmt_unsupported.go @@ -0,0 +1,15 @@ +//go:build !linux + +package binfmt + +import "syscall" + +// MaybeRegister() returns no error. +func MaybeRegister(configurationSearchDirectories []string) error { + return nil +} + +// Register() returns an error. +func Register(configurationSearchDirectories []string) error { + return syscall.ENOSYS +} diff --git a/run_linux.go b/run_linux.go index 0087a1f20d..06d8b723a2 100644 --- a/run_linux.go +++ b/run_linux.go @@ -9,6 +9,7 @@ import ( "os" "path/filepath" "strings" + "sync" "syscall" "github.com/containers/buildah/bind" @@ -18,6 +19,7 @@ import ( "github.com/containers/buildah/internal" "github.com/containers/buildah/internal/tmpdir" "github.com/containers/buildah/internal/volumes" + "github.com/containers/buildah/pkg/binfmt" "github.com/containers/buildah/pkg/overlay" "github.com/containers/buildah/pkg/parse" butil "github.com/containers/buildah/pkg/util" @@ -49,14 +51,19 @@ import ( "tags.cncf.io/container-device-interface/pkg/parser" ) -// We dont want to remove destinations with /etc, /dev, /sys, -// /proc as rootfs already contains these files and unionfs -// will create a `whiteout` i.e `.wh` files on removal of -// overlapping files from these directories. everything other -// than these will be cleaned up -var nonCleanablePrefixes = []string{ - "/etc", "/dev", "/sys", "/proc", -} +var ( + // We dont want to remove destinations with /etc, /dev, /sys, + // /proc as rootfs already contains these files and unionfs + // will create a `whiteout` i.e `.wh` files on removal of + // overlapping files from these directories. everything other + // than these will be cleaned up + nonCleanablePrefixes = []string{ + "/etc", "/dev", "/sys", "/proc", + } + // binfmtRegistered makes sure we only try to register binfmt_misc + // interpreters once, the first time we handle a RUN instruction. + binfmtRegistered sync.Once +) func setChildProcess() error { if err := unix.Prctl(unix.PR_SET_CHILD_SUBREAPER, uintptr(1), 0, 0, 0); err != nil { @@ -158,6 +165,21 @@ func separateDevicesFromRuntimeSpec(g *generate.Generator) define.ContainerDevic // Run runs the specified command in the container's root filesystem. func (b *Builder) Run(command []string, options RunOptions) error { + if os.Getenv("container") != "" { + if os, arch, variant, err := parse.Platform(""); err != nil { + return fmt.Errorf("reading the current default platform") + } else { + platform := b.OCIv1.Platform + if os != platform.OS || arch != platform.Architecture || variant != platform.Variant { + binfmtRegistered.Do(func() { + if err := binfmt.Register(nil); err != nil { + logrus.Warnf("registering binfmt_misc interpreters: %v", err) + } + }) + } + } + } + p, err := os.MkdirTemp(tmpdir.GetTempDir(), define.Package) if err != nil { return err