mirror of https://github.com/containers/podman.git
				
				
				
			
		
			
				
	
	
		
			169 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			169 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			Go
		
	
	
	
| //go:build !remote
 | |
| 
 | |
| package emulation
 | |
| 
 | |
| import (
 | |
| 	"bufio"
 | |
| 	"encoding/hex"
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"io/fs"
 | |
| 	"os"
 | |
| 	"path/filepath"
 | |
| 	"sort"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| )
 | |
| 
 | |
| // registeredBinfmtMisc walks /proc/sys/fs/binfmt_misc and iterates through a
 | |
| // list of known ELF header values to see if there's an emulator registered for
 | |
| // them.  Returns the list of emulated targets (which may be empty), or an
 | |
| // error if something unexpected happened.
 | |
| func registeredBinfmtMisc() ([]string, error) {
 | |
| 	var registered []string
 | |
| 	globalEnabled := false
 | |
| 	err := filepath.WalkDir("/proc/sys/fs/binfmt_misc", func(path string, d fs.DirEntry, err error) error {
 | |
| 		if filepath.Base(path) == "register" { // skip this one
 | |
| 			return nil
 | |
| 		}
 | |
| 		if err != nil && !errors.Is(err, os.ErrNotExist) {
 | |
| 			return err
 | |
| 		}
 | |
| 		info, err := d.Info()
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		if !info.Mode().IsRegular() {
 | |
| 			return nil // skip the directory itself
 | |
| 		}
 | |
| 		f, err := os.Open(path)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		defer f.Close()
 | |
| 		if filepath.Base(path) == "status" {
 | |
| 			b, err := io.ReadAll(f)
 | |
| 			if err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 			status := strings.TrimSpace(string(b))
 | |
| 			switch status {
 | |
| 			case "disabled":
 | |
| 				globalEnabled = false
 | |
| 			case "enabled":
 | |
| 				globalEnabled = true
 | |
| 			default:
 | |
| 				return fmt.Errorf("unrecognized binfmt_misc status value %q in %q", status, path)
 | |
| 			}
 | |
| 			return nil
 | |
| 		}
 | |
| 		offset, magic, mask, err := parseBinfmtMisc(path, f)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		if offset < 0 {
 | |
| 			return nil
 | |
| 		}
 | |
| 		for platform, headers := range getKnownELFPlatformHeaders() {
 | |
| 			for _, header := range headers {
 | |
| 				if magicMatch(header, offset, mask, magic) {
 | |
| 					registered = append(registered, platform)
 | |
| 					break
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 		return nil
 | |
| 	})
 | |
| 	if !globalEnabled {
 | |
| 		return nil, nil
 | |
| 	}
 | |
| 	sort.Strings(registered)
 | |
| 	return registered, err
 | |
| }
 | |
| 
 | |
| // magicMatch compares header, starting at the specified offset, masked with
 | |
| // mask, against the magic value
 | |
| func magicMatch(header []byte, offset int, mask, magic []byte) bool {
 | |
| 	mismatch := 0
 | |
| 	for i := offset; i < offset+len(magic); i++ {
 | |
| 		if i >= len(header) {
 | |
| 			break
 | |
| 		}
 | |
| 		m := magic[i-offset]
 | |
| 		if len(mask) > i-offset {
 | |
| 			m &= mask[i-offset]
 | |
| 		}
 | |
| 		if header[i] != m {
 | |
| 			// mismatch
 | |
| 			break
 | |
| 		}
 | |
| 		mismatch = i + 1
 | |
| 	}
 | |
| 	return mismatch >= offset+len(magic)
 | |
| }
 | |
| 
 | |
| // parseBinfmtMisc parses a binfmt_misc registry entry.  It returns the offset,
 | |
| // magic, and mask values, or an error if there was an error parsing the data.
 | |
| // If the returned offset is negative, the entry was disabled or should be
 | |
| // non-fatally ignored for some other reason.
 | |
| func parseBinfmtMisc(path string, r io.Reader) (int, []byte, []byte, error) {
 | |
| 	offset := 0
 | |
| 	magicString, maskString := "", ""
 | |
| 	scanner := bufio.NewScanner(r)
 | |
| 	for scanner.Scan() {
 | |
| 		text := scanner.Text()
 | |
| 		if strings.TrimSpace(text) == "" {
 | |
| 			continue
 | |
| 		}
 | |
| 		fields := strings.Fields(text)
 | |
| 		switch fields[0] {
 | |
| 		case "disabled":
 | |
| 			return -1, nil, nil, nil // we should ignore this specific one
 | |
| 		case "enabled": // keep scanning this entry
 | |
| 		case "interpreter": // good, but not something we need to record
 | |
| 		case "offset":
 | |
| 			if len(fields) != 2 {
 | |
| 				return -1, nil, nil, fmt.Errorf("invalid format for %q in %q", text, path)
 | |
| 			}
 | |
| 			offset64, err := strconv.ParseInt(fields[1], 10, 8)
 | |
| 			if err != nil {
 | |
| 				return -1, nil, nil, fmt.Errorf("invalid offset %q in %q", fields[1], path)
 | |
| 			}
 | |
| 			offset = int(offset64)
 | |
| 		case "magic":
 | |
| 			if len(fields) != 2 {
 | |
| 				return -1, nil, nil, fmt.Errorf("invalid format for %q in %q", text, path)
 | |
| 			}
 | |
| 			magicString = fields[1]
 | |
| 		case "mask":
 | |
| 			if len(fields) != 2 {
 | |
| 				return -1, nil, nil, fmt.Errorf("invalid format for %q in %q", text, path)
 | |
| 			}
 | |
| 			maskString = fields[1]
 | |
| 		case "flags", "flags:":
 | |
| 			if len(fields) != 2 {
 | |
| 				return -1, nil, nil, fmt.Errorf("invalid format for %q in %q", text, path)
 | |
| 			}
 | |
| 			if !strings.Contains(fields[1], "F") { // won't work in other mount namespaces, so ignore it
 | |
| 				return -1, nil, nil, nil
 | |
| 			}
 | |
| 		default:
 | |
| 			return -1, nil, nil, fmt.Errorf("unrecognized field %q in %q", fields[0], path)
 | |
| 		}
 | |
| 		continue
 | |
| 	}
 | |
| 	if magicString == "" || maskString == "" { // entry is missing some info we need here
 | |
| 		return -1, nil, nil, nil
 | |
| 	}
 | |
| 	magic, err := hex.DecodeString(magicString)
 | |
| 	if err != nil {
 | |
| 		return -1, nil, nil, fmt.Errorf("invalid magic %q in %q", magicString, path)
 | |
| 	}
 | |
| 	mask, err := hex.DecodeString(maskString)
 | |
| 	if err != nil {
 | |
| 		return -1, nil, nil, fmt.Errorf("invalid mask %q in %q", maskString, path)
 | |
| 	}
 | |
| 	return offset, magic, mask, nil
 | |
| }
 |