1
2
3 package userns
4
5 import (
6 "bufio"
7 "bytes"
8 "fmt"
9 "io"
10 "os"
11 "unsafe"
12
13 "github.com/opencontainers/runc/libcontainer/configs"
14 "github.com/sirupsen/logrus"
15 )
16
17
21 import "C"
22
23 func parseIdmapData(data []byte) (ms []configs.IDMap, err error) {
24 scanner := bufio.NewScanner(bytes.NewReader(data))
25 for scanner.Scan() {
26 var m configs.IDMap
27 line := scanner.Text()
28 if _, err := fmt.Sscanf(line, "%d %d %d", &m.ContainerID, &m.HostID, &m.Size); err != nil {
29 return nil, fmt.Errorf("parsing id map failed: invalid format in line %q: %w", line, err)
30 }
31 ms = append(ms, m)
32 }
33 if err := scanner.Err(); err != nil {
34 return nil, fmt.Errorf("parsing id map failed: %w", err)
35 }
36 return ms, nil
37 }
38
39
40
41
42 func spawnUserNamespaceCat(nsPath string, path string) ([]byte, error) {
43 rdr, wtr, err := os.Pipe()
44 if err != nil {
45 return nil, fmt.Errorf("create pipe for userns spawn failed: %w", err)
46 }
47 defer rdr.Close()
48 defer wtr.Close()
49
50 errRdr, errWtr, err := os.Pipe()
51 if err != nil {
52 return nil, fmt.Errorf("create error pipe for userns spawn failed: %w", err)
53 }
54 defer errRdr.Close()
55 defer errWtr.Close()
56
57 cNsPath := C.CString(nsPath)
58 defer C.free(unsafe.Pointer(cNsPath))
59 cPath := C.CString(path)
60 defer C.free(unsafe.Pointer(cPath))
61
62 childPid := C.spawn_userns_cat(cNsPath, cPath, C.int(wtr.Fd()), C.int(errWtr.Fd()))
63
64 if childPid < 0 {
65 return nil, fmt.Errorf("failed to spawn fork for userns")
66 } else if childPid == 0 {
67
68 panic("runc executing inside fork child -- unsafe state!")
69 }
70
71
72 wtr.Close()
73 output, err := io.ReadAll(rdr)
74 rdr.Close()
75 if err != nil {
76 return nil, fmt.Errorf("reading from userns spawn failed: %w", err)
77 }
78
79
80 errWtr.Close()
81 errOutput, err := io.ReadAll(errRdr)
82 errRdr.Close()
83 if err != nil {
84 return nil, fmt.Errorf("reading from userns spawn error pipe failed: %w", err)
85 }
86 errOutput = bytes.TrimSpace(errOutput)
87
88
89 child, err := os.FindProcess(int(childPid))
90 if err != nil {
91 return nil, fmt.Errorf("could not find userns spawn process: %w", err)
92 }
93 state, err := child.Wait()
94 if err != nil {
95 return nil, fmt.Errorf("failed to wait for userns spawn process: %w", err)
96 }
97 if !state.Success() {
98 errStr := string(errOutput)
99 if errStr == "" {
100 errStr = fmt.Sprintf("unknown error (status code %d)", state.ExitCode())
101 }
102 return nil, fmt.Errorf("userns spawn: %s", errStr)
103 } else if len(errOutput) > 0 {
104
105
106 logrus.Debugf("userns spawn succeeded but unexpected error message found: %s", string(errOutput))
107 }
108
109 return output, nil
110 }
111
112 func GetUserNamespaceMappings(nsPath string) (uidMap, gidMap []configs.IDMap, err error) {
113 var (
114 pid int
115 extra rune
116 tryFastPath bool
117 )
118
119
120
121
122
123
124
125
126 if n, _ := fmt.Sscanf(nsPath, "/proc/%d/ns/user%c", &pid, &extra); n == 1 {
127 tryFastPath = pid > 0
128 }
129
130 for _, mapType := range []struct {
131 name string
132 idMap *[]configs.IDMap
133 }{
134 {"uid_map", &uidMap},
135 {"gid_map", &gidMap},
136 } {
137 var mapData []byte
138
139 if tryFastPath {
140 path := fmt.Sprintf("/proc/%d/%s", pid, mapType.name)
141 data, err := os.ReadFile(path)
142 if err != nil {
143
144
145 logrus.Debugf("failed to use fast path to read %s from userns %s (error: %s), falling back to slow userns-join path", mapType.name, nsPath, err)
146 } else {
147 mapData = data
148 }
149 } else {
150 logrus.Debugf("cannot use fast path to read %s from userns %s, falling back to slow userns-join path", mapType.name, nsPath)
151 }
152
153 if mapData == nil {
154
155
156
157 data, err := spawnUserNamespaceCat(nsPath, "/proc/self/"+mapType.name)
158 if err != nil {
159 return nil, nil, err
160 }
161 mapData = data
162 }
163 idMap, err := parseIdmapData(mapData)
164 if err != nil {
165 return nil, nil, fmt.Errorf("failed to parse %s of userns %s: %w", mapType.name, nsPath, err)
166 }
167 *mapType.idMap = idMap
168 }
169
170 return uidMap, gidMap, nil
171 }
172
173
174
175
176 func IsSameMapping(a, b []configs.IDMap) bool {
177 if len(a) != len(b) {
178 return false
179 }
180 for idx := range a {
181 if a[idx] != b[idx] {
182 return false
183 }
184 }
185 return true
186 }
187
View as plain text