From 7cad7edc1e79c4827e4bc00cb1700ac48e7971ad Mon Sep 17 00:00:00 2001 From: Dalibor Kricka Date: Wed, 22 Apr 2026 16:58:12 +0200 Subject: [PATCH 1/6] pkg/shell: Preserve error types in shell command execution The existing RunContextWithExitCode() wraps all errors from exec.Command in generic "failed to invoke" messages, which prevents callers from distinguishing between actual error types. Add RunContextWithExitCode2() and RunWithExitCode2() that return errors with their original types intact, including for ExitError. This allows callers to use errors.Is() and errors.As() to handle specific failure modes. For example, detecting a missing skopeo binary (exec.ErrNotFound) or an ENOEXEC error from inside non native containers, when an emulator is not set correctly. These new functions are meant to replace its original versions in the future. https://github.com/containers/toolbox/pull/1780 Signed-off-by: Dalibor Kricka --- src/pkg/shell/shell.go | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/pkg/shell/shell.go b/src/pkg/shell/shell.go index 00fa36997..6c9cfcacd 100644 --- a/src/pkg/shell/shell.go +++ b/src/pkg/shell/shell.go @@ -81,8 +81,49 @@ func RunContextWithExitCode(ctx context.Context, return 0, nil } +func RunContextWithExitCode2(ctx context.Context, + name string, + stdin io.Reader, + stdout, stderr io.Writer, + arg ...string) (int, error) { + + logLevel := logrus.GetLevel() + if stderr == nil && logLevel >= logrus.DebugLevel { + stderr = os.Stderr + } + + cmd := exec.CommandContext(ctx, name, arg...) + cmd.Stdin = stdin + cmd.Stdout = stdout + cmd.Stderr = stderr + + if err := cmd.Run(); err != nil { + exitCode := 1 + + if ctxErr := ctx.Err(); ctxErr != nil { + return 1, ctxErr + } + + var exitErr *exec.ExitError + if errors.As(err, &exitErr) { + exitCode = exitErr.ExitCode() + return exitCode, err + } + + return exitCode, err + } + + return 0, nil +} + func RunWithExitCode(name string, stdin io.Reader, stdout, stderr io.Writer, arg ...string) (int, error) { ctx := context.Background() exitCode, err := RunContextWithExitCode(ctx, name, stdin, stdout, stderr, arg...) return exitCode, err } + +func RunWithExitCode2(name string, stdin io.Reader, stdout, stderr io.Writer, arg ...string) (int, error) { + ctx := context.Background() + exitCode, err := RunContextWithExitCode2(ctx, name, stdin, stdout, stderr, arg...) + return exitCode, err +} From fae8f4a0614dc549fe832e343d9bc8ebc43f72a4 Mon Sep 17 00:00:00 2001 From: Dalibor Kricka Date: Wed, 22 Apr 2026 23:21:55 +0200 Subject: [PATCH 2/6] cmd/create: Extract spinner setup into helper functions In /src/cmd/create.go, the same pattern of spinner creation and nil-safe stopping is repeated. Extract this into startSpinner() and stopSpinner() helper functions so that future callers can use spinners without duplicating the code. Replace the existing inline spinner code in createContainer() and pullImage() with calls to these new helpers. https://github.com/containers/toolbox/pull/1781 Signed-off-by: Dalibor Kricka --- src/cmd/create.go | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/src/cmd/create.go b/src/cmd/create.go index bdd86be37..13345289d 100644 --- a/src/cmd/create.go +++ b/src/cmd/create.go @@ -486,19 +486,15 @@ func createContainer(container, image, release, authFile string, showCommandToEn logrus.Debugf("%s", arg) } - s := spinner.New(spinner.CharSets[9], 500*time.Millisecond, spinner.WithWriterFile(os.Stdout)) - if logLevel := logrus.GetLevel(); logLevel < logrus.DebugLevel { - s.Prefix = fmt.Sprintf("Creating container %s: ", container) - s.Start() - defer s.Stop() - } + s := startSpinner(fmt.Sprintf("Creating container %s: ", container)) + defer stopSpinner(s) if err := shell.Run("podman", nil, nil, nil, createArgs...); err != nil { return fmt.Errorf("failed to create container %s", container) } // The spinner must be stopped before showing the 'enter' hint below. - s.Stop() + stopSpinner(s) if showCommandToEnter { fmt.Printf("Created container: %s\n", container) @@ -735,12 +731,8 @@ func pullImage(image, release, authFile string) (bool, error) { logrus.Debugf("Pulling image %s", imageFull) - if logLevel := logrus.GetLevel(); logLevel < logrus.DebugLevel { - s := spinner.New(spinner.CharSets[9], 500*time.Millisecond, spinner.WithWriterFile(os.Stdout)) - s.Prefix = fmt.Sprintf("Pulling %s: ", imageFull) - s.Start() - defer s.Stop() - } + s := startSpinner(fmt.Sprintf("Pulling %s: ", imageFull)) + defer stopSpinner(s) if err := podman.Pull(imageFull, authFile); err != nil { var builder strings.Builder @@ -963,6 +955,22 @@ func showPromptForDownload(imageFull string) bool { return shouldPullImage } +func startSpinner(message string) *spinner.Spinner { + if logLevel := logrus.GetLevel(); logLevel < logrus.DebugLevel { + s := spinner.New(spinner.CharSets[9], 500*time.Millisecond, spinner.WithWriterFile(os.Stdout)) + s.Prefix = message + s.Start() + return s + } + return nil +} + +func stopSpinner(s *spinner.Spinner) { + if s != nil { + s.Stop() + } +} + // systemdNeedsEscape checks whether a byte in a potential dbus ObjectPath needs to be escaped func systemdNeedsEscape(i int, b byte) bool { // Escape everything that is not a-z-A-Z-0-9 From ddebd00b7edb62abb32f17ba81fdec387b1f5e18 Mon Sep 17 00:00:00 2001 From: Dalibor Kricka Date: Wed, 22 Apr 2026 23:30:29 +0200 Subject: [PATCH 3/6] pkg/utils: Add IsSupportedDistroImage for image to supported distro matching Add IsSupportedDistroImage(), which iterates over all supported distros and checks if the image basename matches any of them. This will be used by the architecture resolution code to decide whether to parse architecture suffixes from image tags, as this should be done only for natively supported images [1]. [1] Toolbx supported distributions: https://containertoolbx.org/distros/ https://github.com/containers/toolbox/pull/1781 Signed-off-by: Dalibor Kricka --- src/pkg/utils/utils.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/pkg/utils/utils.go b/src/pkg/utils/utils.go index f3de23b1b..c88f8ef67 100644 --- a/src/pkg/utils/utils.go +++ b/src/pkg/utils/utils.go @@ -664,6 +664,20 @@ func IsP11KitClientPresent() (bool, error) { return false, err } +func IsSupportedDistroImage(image string) bool { + basename := ImageReferenceGetBasename(image) + if basename == "" { + return false + } + + for _, distroObj := range supportedDistros { + if distroObj.ImageBasename == basename { + return true + } + } + return false +} + func SetUpConfiguration() error { logrus.Debug("Setting up configuration") From 211ab9471775dbe50790ed98f6b238e69f20a3b0 Mon Sep 17 00:00:00 2001 From: Dalibor Kricka Date: Thu, 23 Apr 2026 08:12:19 +0200 Subject: [PATCH 4/6] pkg/architecture: Define core architecture types and constants Introduce the architecture package that represents the core of the Toolbx cross-architecture support, which is based on user-mode emulation using QEMU and binfmt_misc. The Architecture struct collects all per-architecture data (ELF magic/mask, OCI and binfmt naming, aliases, binfmt registration parameters) into a single map. Architectures present in the supportedArchitectures map represent the set of supported architectures within Toolbx. Define architecture ID constants NotSpecified, Aarch64, Ppc64le, and X86_64, along with their supportedArchitectures entries. Add core query functions: - ParseArgArchValue() for resolving user-supplied architecture strings - GetArchNameBinfmt() and GetArchNameOCI() for name lookups (one architecture can have multiple valid names [1]) - HasContainerNativeArch() for comparing against the host - ImageReferenceGetArchFromTag() for extracting architecture from image tag suffixes like "42-aarch64" for architecture detection Expose the HostArchID package variable, which is set in the init() function, so the variable can be accessed in the early init() state from every inheritor that utilizes the architecture package (HostArchID serves as a default value for initContainer --arch flag), and the Config struct for preserving the architecture ID and the QEMU emulator path, through the call chain. [1] https://itsfoss.com/arm-aarch64-x86_64/ https://github.com/containers/toolbox/pull/1782 Signed-off-by: Dalibor Kricka --- src/pkg/architecture/architecture.go | 165 +++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 src/pkg/architecture/architecture.go diff --git a/src/pkg/architecture/architecture.go b/src/pkg/architecture/architecture.go new file mode 100644 index 000000000..24f732230 --- /dev/null +++ b/src/pkg/architecture/architecture.go @@ -0,0 +1,165 @@ +/* + * Copyright © 2019 – 2026 Red Hat Inc. + * + * 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 architecture + +import ( + "fmt" + "runtime" + "strings" + + "github.com/containers/toolbox/pkg/utils" + "github.com/sirupsen/logrus" +) + +type Architecture struct { + ID int + NameBinfmt string + NameOCI string + Aliases []string + ELFMagic []byte + ELFMask []byte + + BinfmtFlags string + BinfmtName string + BinfmtMagicType string + BinfmtOffset string +} + +type Config struct { + ID int + QemuEmulatorPath string +} + +const ( + NotSpecified = iota + Aarch64 + Ppc64le + X86_64 +) + +var supportedArchitectures = map[int]Architecture{ + Aarch64: { + ID: Aarch64, + NameBinfmt: "aarch64", + NameOCI: "arm64", + Aliases: []string{"aarch64", "arm64"}, + ELFMagic: []byte{0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0xb7, 0x00}, + ELFMask: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff}, + }, + Ppc64le: { + ID: Ppc64le, + NameBinfmt: "ppc64le", + NameOCI: "ppc64le", + Aliases: []string{"ppc64le"}, + ELFMagic: []byte{0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x15, 0x00}, + ELFMask: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0x00}, + }, + X86_64: { + ID: X86_64, + NameBinfmt: "x86_64", + NameOCI: "amd64", + Aliases: []string{"x86_64", "amd64"}, + ELFMagic: []byte{0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x3e, 0x00}, + ELFMask: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff}, + }, +} + +var ( + HostArchID int + supportedArgArchValues map[string]int +) + +func init() { + supportedArgArchValues = make(map[string]int) + for archID, arch := range supportedArchitectures { + for _, alias := range arch.Aliases { + supportedArgArchValues[alias] = archID + } + } + + HostArchID, _ = ParseArgArchValue(runtime.GOARCH) +} + +func GetArchConfigDefault() Config { + return Config{ + ID: HostArchID, + QemuEmulatorPath: "", + } +} + +func getArchitecture(archID int) (Architecture, bool) { + arch, exists := supportedArchitectures[archID] + return arch, exists +} + +func getArchNameBinfmt(arch int) string { + if arch == NotSpecified { + logrus.Warnf("Getting arch name for not specified architecture") + return "arch_not_specified" + } + if archObj, exists := supportedArchitectures[arch]; exists { + return archObj.NameBinfmt + } + return "" +} + +func GetArchNameOCI(arch int) string { + if arch == NotSpecified { + logrus.Warnf("Getting arch name for not specified architecture") + return "arch_not_specified" + } + if archObj, exists := supportedArchitectures[arch]; exists { + return archObj.NameOCI + } + return "" +} + +func HasContainerNativeArch(archID int) bool { + return archID == HostArchID +} + +func ImageReferenceGetArchFromTag(image string) int { + tag := utils.ImageReferenceGetTag(image) + + if tag == "" { + return NotSpecified + } + + i := strings.LastIndexByte(tag, '-') + if i == -1 { + return NotSpecified + } + + archInTag := tag[i+1:] + + for archID, arch := range supportedArchitectures { + if arch.NameBinfmt == archInTag || arch.NameOCI == archInTag { + return archID + } + } + + return NotSpecified +} + +func ParseArgArchValue(value string) (int, error) { + archID, exists := supportedArgArchValues[value] + if !exists { + return NotSpecified, fmt.Errorf("architecture '%s' is not supported by Toolbx", value) + } + + return archID, nil +} From a0a4ede111d194d263a9219ca7ae9864a6e50748 Mon Sep 17 00:00:00 2001 From: Dalibor Kricka Date: Thu, 23 Apr 2026 08:37:54 +0200 Subject: [PATCH 5/6] pkg/architecture: Add sandboxed binfmt_misc registration support Cross-architecture containers need QEMU binfmt_misc handlers registered within the container so that non-native architecture binaries can be executed via the host's kernel. Add the Registration struct that models a binfmt_misc registration entry, including name, magic type, offset, ELF magic/mask bytes, interpreter path, and flags. Add functions: - MountBinfmtMisc() to mount the sanboxed binfmt_misc filesystem inside a container, which enables setting the C flag in binfmt_misc registration without affecting the host system. The C flag presents a threat of privilege escalation when registered on the host, that why we want to have it isolated [1] - getDefaultRegistration() to fill a Registration struct containing all necessary binfmt_misc information taken from the architecture.supportedArchitectures data - RegisterBinfmtMisc() to write the registration string to /proc/sys/fs/binfmt_misc/register, which makes the non-native binary perception active - bytesToEscapedString() helper that formats byte slices into the \xHH-escaped string format required by the binfmt_misc register interface [1] https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=21ca59b365c0 https://github.com/containers/toolbox/pull/1782 Signed-off-by: Dalibor Kricka --- src/pkg/architecture/binfmt_misc.go | 151 ++++++++++++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 src/pkg/architecture/binfmt_misc.go diff --git a/src/pkg/architecture/binfmt_misc.go b/src/pkg/architecture/binfmt_misc.go new file mode 100644 index 000000000..3dc8eddbb --- /dev/null +++ b/src/pkg/architecture/binfmt_misc.go @@ -0,0 +1,151 @@ +/* + * Copyright © 2019 – 2026 Red Hat Inc. + * + * 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 architecture + +import ( + "bytes" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/containers/toolbox/pkg/shell" + "github.com/sirupsen/logrus" +) + +type Registration struct { + Name string + MagicType string + Offset string + Magic []byte + Mask []byte + Interpreter string + Flags string +} + +const ( + defaultMagicType = "M" + defaultFlags = "FC" + defaultOffset = "0" + binfmtMiscPath = "/proc/sys/fs/binfmt_misc" +) + +func (r *Registration) buildRegistrationString() string { + return fmt.Sprintf(":%s:%s:%s:%s:%s:%s:%s", + r.Name, r.MagicType, r.Offset, + bytesToEscapedString(r.Magic), + bytesToEscapedString(r.Mask), + r.Interpreter, r.Flags) +} + +func (r *Registration) register() error { + logrus.Debugf("Registering binfmt_misc for %s", r.Name) + + regString := r.buildRegistrationString() + logrus.Debugf("Registration string: %s", regString) + + if err := os.WriteFile(filepath.Join(binfmtMiscPath, "register"), []byte(regString), 0200); err != nil { + return fmt.Errorf("failed to register binfmt_misc handler: %w", err) + } + return nil +} + +func bytesToEscapedString(bytes []byte) string { + var result strings.Builder + for _, b := range bytes { + result.WriteString(fmt.Sprintf("\\x%02x", b)) + } + return result.String() +} + +func getDefaultRegistration(archID int, interpreterPath string) *Registration { + arch, exists := getArchitecture(archID) + if !exists { + return nil + } + + var name string + flags := defaultFlags + magicType := defaultMagicType + offset := defaultOffset + + if arch.BinfmtName != "" { + name = arch.BinfmtName + } else { + name = "qemu-" + arch.NameBinfmt + } + + if arch.BinfmtFlags != "" { + flags = arch.BinfmtFlags + } + + if arch.BinfmtMagicType != "" { + magicType = arch.BinfmtMagicType + } + + if arch.BinfmtOffset != "" { + offset = arch.BinfmtOffset + } + + interpreter := interpreterPath + if !strings.HasPrefix(interpreterPath, "/run/host/") { + interpreter = filepath.Join("/run/host", interpreter) + } + + return &Registration{ + Name: name, + MagicType: magicType, + Offset: offset, + Magic: arch.ELFMagic, + Mask: arch.ELFMask, + Interpreter: interpreter, + Flags: flags, + } +} + +func MountBinfmtMisc() error { + args := []string{ + "binfmt_misc", + "-t", + "binfmt_misc", + binfmtMiscPath, + } + + var stdout bytes.Buffer + + if err := shell.Run("mount", nil, &stdout, nil, args...); err != nil { + return fmt.Errorf("failed to mount binfmt_misc: %w", err) + } + + logrus.Debugf("Result of mount command: %s", stdout.String()) + + return nil +} + +func RegisterBinfmtMisc(archID int, interpreterPath string) error { + reg := getDefaultRegistration(archID, interpreterPath) + if reg == nil { + logrus.Debugf("Unable to register binfmt_misc for architecture '%s'", GetArchNameOCI(archID)) + return fmt.Errorf("Toolbx does not support architecture '%s'", GetArchNameOCI(archID)) + } + + if err := reg.register(); err != nil { + return err + } + + return nil +} From 8fe8ef5d4245809c0750f42ce32d9c706a362759 Mon Sep 17 00:00:00 2001 From: Dalibor Kricka Date: Thu, 23 Apr 2026 13:32:40 +0200 Subject: [PATCH 6/6] pkg/architecture: Add architecture support validation Before creating or initializing a cross-architecture container, the system must be checked for the required QEMU emulator and binfmt_misc registration. This prevents users from creating or running non-native containers when their host system doesn't meet the requirements, and provides users with an informative error message referring to the problem. Add IsArchSupportedOnCreation(), which searches for a statically linked QEMU binary on the host using exec.LookPath() and verifies that a matching binfmt_misc registration exists. It returns the path to the QEMU binary for use during container creation, which is meant to be passed to the init-container and registered through sandboxed binfmt_misc within the container. Add IsArchSupportedOnInitialization() which performs similar checks from inside the container, looking at the interpreter path passed from the host and falling back to standard host-mounted locations under /run/host/usr/bin/. Add isStaticallyLinkedELF() helper that uses debug/elf to verify a binary is statically linked. Only a statically linked QEMU interpreter can be used, because a dynamically linked one would cause the kernel to attempt to resolve its host-native shared libraries (such as libc.so) within the container, resulting in an immediate crash. Add validateBinfmtRegistration(), which checks for the presence of qemu- entries in binfmt_misc (or qemu--static, since it can differ based on the system). https://github.com/containers/toolbox/pull/1783 Signed-off-by: Dalibor Kricka --- src/pkg/architecture/architecture.go | 128 +++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) diff --git a/src/pkg/architecture/architecture.go b/src/pkg/architecture/architecture.go index 24f732230..652ed224d 100644 --- a/src/pkg/architecture/architecture.go +++ b/src/pkg/architecture/architecture.go @@ -17,7 +17,11 @@ package architecture import ( + "debug/elf" + "errors" "fmt" + "os" + "os/exec" "runtime" "strings" @@ -155,6 +159,108 @@ func ImageReferenceGetArchFromTag(image string) int { return NotSpecified } +func IsArchSupportedOnCreation(archID int) (string, error) { + archName := getArchNameBinfmt(archID) + archNameDebug := GetArchNameOCI(archID) + logrus.Debugf("Checking QEMU emulation support for architecture %s", archNameDebug) + + qemuBinaryPossibleNames := []string{ + fmt.Sprintf("qemu-%s-static", archName), + fmt.Sprintf("qemu-%s", archName), + } + + foundQemuBinaryPath := "" + for _, qemuName := range qemuBinaryPossibleNames { + qemuBinaryPath, err := exec.LookPath(qemuName) + + if err != nil { + if errors.Is(err, exec.ErrNotFound) { + continue + } + + return "", fmt.Errorf("failed to look up binary '%s': %w", qemuName, err) + } + + if isStaticallyLinkedELF(qemuBinaryPath) { + foundQemuBinaryPath = qemuBinaryPath + break + } + } + + if foundQemuBinaryPath == "" { + err := fmt.Errorf("The host system does not have the required support: No %s statically linked QEMU emulator binary found", archNameDebug) + return "", err + } + + if !validateBinfmtRegistration(archID, false) { + err := fmt.Errorf("The host system does not have the required support: No %s binfmt_misc registration found", archNameDebug) + return "", err + } + + return foundQemuBinaryPath, nil +} + +func IsArchSupportedOnInitialization(archID int, interpreterPath string) (string, error) { + archName := getArchNameBinfmt(archID) + archNameDebug := GetArchNameOCI(archID) + logrus.Debugf("Checking QEMU emulation support for architecture %s", archNameDebug) + + if isStaticallyLinkedELF(interpreterPath) { + if !validateBinfmtRegistration(archID, true) { + return "", fmt.Errorf("The host system does not have the required support: No %s binfmt_misc registration found", archNameDebug) + } + return interpreterPath, nil + } + + // Fallback: check standard locations on the host + logrus.Debugf("Interpreter at %s not found or not statically linked, checking fallback locations in '/run/host/usr/bin/'", interpreterPath) + fmt.Fprintf(os.Stderr, "Warning: QEMU emulator not found at expected path '%s', using fallback at '/run/host/usr/bin/'\n", interpreterPath) + + qemuBinaryPossiblePaths := []string{ + fmt.Sprintf("/run/host/usr/bin/qemu-%s-static", archName), + fmt.Sprintf("/run/host/usr/bin/qemu-%s", archName), + } + + for _, qemuPath := range qemuBinaryPossiblePaths { + if isStaticallyLinkedELF(qemuPath) { + logrus.Debugf("Found valid QEMU binary at %s", qemuPath) + + if !validateBinfmtRegistration(archID, true) { + return "", fmt.Errorf("The host system does not have the required support: No %s binfmt_misc registration found", archNameDebug) + } + return qemuPath, nil + } + } + + return "", fmt.Errorf("The host system does not have the required support: No %s statically linked QEMU emulator binary found", archNameDebug) +} + +func isStaticallyLinkedELF(filePath string) bool { + if !utils.PathExists(filePath) { + logrus.Debugf("File '%s' does not exist\n", filePath) + return false + } + + f, err := elf.Open(filePath) + if err != nil { + logrus.Debugf("File '%s' is not an ELF file\n", filePath) + return false + } + defer f.Close() + + // Check for PT_INTERP program header + for _, prog := range f.Progs { + if prog.Type == elf.PT_INTERP { + // Dynamically linked + logrus.Debugf("File '%s' is dynamically linked\n", filePath) + return false + } + } + + // Statically linked + return true +} + func ParseArgArchValue(value string) (int, error) { archID, exists := supportedArgArchValues[value] if !exists { @@ -163,3 +269,25 @@ func ParseArgArchValue(value string) (int, error) { return archID, nil } + +func validateBinfmtRegistration(archID int, withinContainer bool) bool { + archName := getArchNameBinfmt(archID) + inContainerPathPrefix := "" + + if withinContainer { + inContainerPathPrefix = "/run/host" + } + + qemuBinfmtPossiblePaths := []string{ + fmt.Sprintf("%s/proc/sys/fs/binfmt_misc/qemu-%s", inContainerPathPrefix, archName), + fmt.Sprintf("%s/proc/sys/fs/binfmt_misc/qemu-%s-static", inContainerPathPrefix, archName), + } + + for _, binfmtPath := range qemuBinfmtPossiblePaths { + if utils.PathExists(binfmtPath) { + logrus.Debugf("Architecture %s is supported", archName) + return true + } + } + return false +}