//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 }