Skip to content

Commit 0b97666

Browse files
committed
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-<arch> entries in binfmt_misc (or qemu-<arch>-static, since it can differ based on the system). containers#1783 Signed-off-by: Dalibor Kricka <dalidalk@seznam.cz>
1 parent 05c607a commit 0b97666

1 file changed

Lines changed: 131 additions & 0 deletions

File tree

src/pkg/architecture/architecture.go

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,15 @@
1717
package architecture
1818

1919
import (
20+
"debug/elf"
21+
"errors"
2022
"fmt"
23+
<<<<<<< HEAD
2124
"runtime"
25+
=======
26+
"os"
27+
"os/exec"
28+
>>>>>>> 8374177 (pkg/architecture: Add architecture support validation)
2229
"strings"
2330

2431
"github.com/containers/toolbox/pkg/utils"
@@ -155,6 +162,108 @@ func ImageReferenceGetArchFromTag(image string) int {
155162
return NotSpecified
156163
}
157164

165+
func IsArchSupportedOnCreation(archID int) (string, error) {
166+
archName := getArchNameBinfmt(archID)
167+
archNameDebug := GetArchNameOCI(archID)
168+
logrus.Debugf("Checking QEMU emulation support for architecture %s", archNameDebug)
169+
170+
qemuBinaryPossibleNames := []string{
171+
fmt.Sprintf("qemu-%s-static", archName),
172+
fmt.Sprintf("qemu-%s", archName),
173+
}
174+
175+
foundQemuBinaryPath := ""
176+
for _, qemuName := range qemuBinaryPossibleNames {
177+
qemuBinaryPath, err := exec.LookPath(qemuName)
178+
179+
if err != nil {
180+
if errors.Is(err, exec.ErrNotFound) {
181+
continue
182+
}
183+
184+
return "", fmt.Errorf("failed to look up binary '%s': %w", qemuName, err)
185+
}
186+
187+
if isStaticallyLinkedELF(qemuBinaryPath) {
188+
foundQemuBinaryPath = qemuBinaryPath
189+
break
190+
}
191+
}
192+
193+
if foundQemuBinaryPath == "" {
194+
err := fmt.Errorf("The host system does not have the required support: No %s statically linked QEMU emulator binary found", archNameDebug)
195+
return "", err
196+
}
197+
198+
if !validateBinfmtRegistration(archID, false) {
199+
err := fmt.Errorf("The host system does not have the required support: No %s binfmt_misc registration found", archNameDebug)
200+
return "", err
201+
}
202+
203+
return foundQemuBinaryPath, nil
204+
}
205+
206+
func IsArchSupportedOnInitialization(archID int, interpreterPath string) error {
207+
archName := getArchNameBinfmt(archID)
208+
archNameDebug := GetArchNameOCI(archID)
209+
logrus.Debugf("Checking QEMU emulation support for architecture %s", archNameDebug)
210+
211+
if isStaticallyLinkedELF(interpreterPath) {
212+
if !validateBinfmtRegistration(archID, true) {
213+
return fmt.Errorf("The host system does not have the required support: No %s binfmt_misc registration found", archNameDebug)
214+
}
215+
return nil
216+
}
217+
218+
// Fallback: check standard locations on the host
219+
logrus.Debugf("Interpreter at %s not found or not statically linked, checking fallback locations in '/run/host/usr/bin/'", interpreterPath)
220+
fmt.Fprintf(os.Stderr, "Warning: QEMU emulator not found at expected path '%s', using fallback at '/run/host/usr/bin/'\n", interpreterPath)
221+
222+
qemuBinaryPossiblePaths := []string{
223+
fmt.Sprintf("/run/host/usr/bin/qemu-%s-static", archName),
224+
fmt.Sprintf("/run/host/usr/bin/qemu-%s", archName),
225+
}
226+
227+
for _, qemuPath := range qemuBinaryPossiblePaths {
228+
if isStaticallyLinkedELF(qemuPath) {
229+
logrus.Debugf("Found valid QEMU binary at %s", qemuPath)
230+
231+
if !validateBinfmtRegistration(archID, true) {
232+
return fmt.Errorf("The host system does not have the required support: No %s binfmt_misc registration found", archNameDebug)
233+
}
234+
return nil
235+
}
236+
}
237+
238+
return fmt.Errorf("The host system does not have the required support: No %s statically linked QEMU emulator binary found", archNameDebug)
239+
}
240+
241+
func isStaticallyLinkedELF(filePath string) bool {
242+
if !utils.PathExists(filePath) {
243+
logrus.Debugf("File '%s' does not exist\n", filePath)
244+
return false
245+
}
246+
247+
f, err := elf.Open(filePath)
248+
if err != nil {
249+
logrus.Debugf("File '%s' is not an ELF file\n", filePath)
250+
return false
251+
}
252+
defer f.Close()
253+
254+
// Check for PT_INTERP program header
255+
for _, prog := range f.Progs {
256+
if prog.Type == elf.PT_INTERP {
257+
// Dynamically linked
258+
logrus.Debugf("File '%s' is dynamically linked\n", filePath)
259+
return false
260+
}
261+
}
262+
263+
// Statically linked
264+
return true
265+
}
266+
158267
func ParseArgArchValue(value string) (int, error) {
159268
archID, exists := supportedArgArchValues[value]
160269
if !exists {
@@ -163,3 +272,25 @@ func ParseArgArchValue(value string) (int, error) {
163272

164273
return archID, nil
165274
}
275+
276+
func validateBinfmtRegistration(archID int, withinContainer bool) bool {
277+
archName := getArchNameBinfmt(archID)
278+
inContainerPathPrefix := ""
279+
280+
if withinContainer {
281+
inContainerPathPrefix = "/run/host"
282+
}
283+
284+
qemuBinfmtPossiblePaths := []string{
285+
fmt.Sprintf("%s/proc/sys/fs/binfmt_misc/qemu-%s", inContainerPathPrefix, archName),
286+
fmt.Sprintf("%s/proc/sys/fs/binfmt_misc/qemu-%s-static", inContainerPathPrefix, archName),
287+
}
288+
289+
for _, binfmtPath := range qemuBinfmtPossiblePaths {
290+
if utils.PathExists(binfmtPath) {
291+
logrus.Debugf("Architecture %s is supported", archName)
292+
return true
293+
}
294+
}
295+
return false
296+
}

0 commit comments

Comments
 (0)