File: binfmtmisc_linux.go

package info (click to toggle)
podman 5.7.0%2Bds2-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 23,824 kB
  • sloc: sh: 4,700; python: 2,798; perl: 1,885; ansic: 1,484; makefile: 977; ruby: 42; csh: 8
file content (171 lines) | stat: -rw-r--r-- 4,487 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
//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 {
			if errors.Is(err, fs.ErrNotExist) {
				return nil
			}
			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 {
				continue
			}
			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
}