master
go 492 lines 11.5 KB
Raw
1 package protocol
2
3 import "bytes"
4
5 const (
6 OrchestratorUnknown uint16 = 0
7 OrchestratorSystemd uint16 = 1
8 OrchestratorDocker uint16 = 2
9 OrchestratorK8s uint16 = 3
10 OrchestratorKvm uint16 = 4
11 OrchestratorLxc uint16 = 5
12 OrchestratorPodman uint16 = 6
13 OrchestratorNspawn uint16 = 7
14
15 LookupDirEntrySize = 8
16 LookupLabelEntrySize = 16
17 )
18
19 // LookupLabelView represents a key-value label pair view in the lookup wire format.
20 type LookupLabelView struct {
21 Key CStringView
22 Value CStringView
23 }
24
25 func invalidSourceString(data []byte, requireNonEmpty bool) bool {
26 return (requireNonEmpty && len(data) == 0) || bytes.IndexByte(data, 0) >= 0
27 }
28
29 // maxIntValue returns the maximum value representable by int on this platform.
30 func maxIntValue() int {
31 return int(^uint(0) >> 1)
32 }
33
34 func checkedU32Int(value int) (uint32, bool) {
35 if value < 0 || uint64(value) > uint64(^uint32(0)) {
36 return 0, false
37 }
38 return uint32(value), true
39 }
40
41 func checkedU16Int(value int) (uint16, bool) {
42 if value < 0 || value > int(^uint16(0)) {
43 return 0, false
44 }
45 return uint16(value), true
46 }
47
48 func checkedWireU32Int(buf []byte, off int) (int, error) {
49 value, ok := checkedInt(uint64(ne.Uint32(buf[off : off+4])))
50 if !ok {
51 return 0, ErrOutOfBounds
52 }
53 return value, nil
54 }
55
56 func lookupDirEntry(buf []byte, base int) (int, int, error) {
57 off, err := checkedWireU32Int(buf, base)
58 if err != nil {
59 return 0, 0, err
60 }
61 length, err := checkedWireU32Int(buf, base+4)
62 if err != nil {
63 return 0, 0, err
64 }
65 return off, length, nil
66 }
67
68 func lookupPayloadSlice(buf []byte, start int, off int, length int) ([]byte, error) {
69 abs, ok := checkedAddInt(start, off)
70 if !ok {
71 return nil, ErrOutOfBounds
72 }
73 end, ok := checkedAddInt(abs, length)
74 if !ok || end > len(buf) {
75 return nil, ErrOutOfBounds
76 }
77 return buf[abs:end], nil
78 }
79
80 func lookupBuilderDataOffset(hdrSize int, maxItems uint32) (int, bool) {
81 dirSize, ok := checkedInt(uint64(maxItems) * uint64(LookupDirEntrySize))
82 if !ok {
83 return 0, false
84 }
85 return checkedAddInt(hdrSize, dirSize)
86 }
87
88 func lookupDirOffset(hdrSize int, index uint32) (int, bool) {
89 dirOff, ok := checkedInt(uint64(index) * uint64(LookupDirEntrySize))
90 if !ok {
91 return 0, false
92 }
93 return checkedAddInt(hdrSize, dirOff)
94 }
95
96 func checkedInt(value uint64) (int, bool) {
97 maxInt := uint64(maxIntValue()) // #nosec G115 -- maxIntValue is non-negative and intentionally widened for the bounds check.
98 if value > maxInt {
99 return 0, false
100 }
101 return int(value), true // #nosec G115 -- value is bounded by maxInt above.
102 }
103
104 func checkedAddInt(a, b int) (int, bool) {
105 if a < 0 || b < 0 {
106 return 0, false
107 }
108 maxInt := maxIntValue()
109 if a > maxInt-b {
110 return 0, false
111 }
112 return a + b, true
113 }
114
115 func checkedMulInt(a, b int) (int, bool) {
116 if a < 0 || b < 0 {
117 return 0, false
118 }
119 maxInt := maxIntValue()
120 if a != 0 && b > maxInt/a {
121 return 0, false
122 }
123 return a * b, true
124 }
125
126 func checkedAlign8(v int) (int, bool) {
127 if v < 0 {
128 return 0, false
129 }
130 maxInt := maxIntValue()
131 if v > maxInt-7 {
132 return 0, false
133 }
134 return Align8(v), true
135 }
136
137 func validateLookupDir(buf []byte, dirStart int, itemCount uint32, packedAreaLen int, minLen int, exactLen int) error {
138 var minLen32 uint32
139 var exactLen32 uint32
140 if minLen >= 0 {
141 converted, ok := checkedU32Int(minLen)
142 if !ok {
143 return ErrBadLayout
144 }
145 minLen32 = converted
146 }
147 if exactLen >= 0 {
148 converted, ok := checkedU32Int(exactLen)
149 if !ok {
150 return ErrBadLayout
151 }
152 exactLen32 = converted
153 }
154
155 dirSize, ok := checkedInt(uint64(itemCount) * uint64(LookupDirEntrySize))
156 if !ok {
157 return ErrBadItemCount
158 }
159 dirEnd, ok := checkedAddInt(dirStart, dirSize)
160 if !ok {
161 return ErrBadItemCount
162 }
163 if dirEnd > len(buf) {
164 return ErrTruncated
165 }
166
167 prevEnd := 0
168 for i := range itemCount {
169 base, ok := lookupDirOffset(dirStart, i)
170 if !ok {
171 return ErrBadItemCount
172 }
173 off, length, err := lookupDirEntry(buf, base)
174 if err != nil {
175 return err
176 }
177 if off%Alignment != 0 {
178 return ErrBadAlignment
179 }
180 length32, ok := checkedU32Int(length)
181 if !ok {
182 return ErrOutOfBounds
183 }
184 if exactLen >= 0 {
185 if length32 != exactLen32 {
186 return ErrBadLayout
187 }
188 } else if length32 < minLen32 {
189 return ErrBadLayout
190 }
191 end, ok := checkedAddInt(off, length)
192 if !ok || end > packedAreaLen {
193 return ErrOutOfBounds
194 }
195 if i > 0 && off < prevEnd {
196 return ErrBadLayout
197 }
198 prevEnd = end
199 }
200 return nil
201 }
202
203 func lookupString(item []byte, hdrSize int, off int, length int) (CStringView, int, error) {
204 if off < hdrSize {
205 return CStringView{}, 0, ErrOutOfBounds
206 }
207 nul, ok := checkedAddInt(off, length)
208 if !ok || nul >= len(item) {
209 return CStringView{}, 0, ErrOutOfBounds
210 }
211 if item[nul] != 0 {
212 return CStringView{}, 0, ErrMissingNul
213 }
214 if bytes.Contains(item[off:nul], []byte{0}) {
215 return CStringView{}, 0, ErrBadLayout
216 }
217 length32, ok := checkedU32Int(length)
218 if !ok {
219 return CStringView{}, 0, ErrOutOfBounds
220 }
221 return NewCStringView(item[off:nul+1], length32), nul + 1, nil
222 }
223
224 func lookupEmptyString(item []byte, hdrSize int, off int) (CStringView, int, error) {
225 if off < hdrSize || off >= len(item) {
226 return CStringView{}, 0, ErrOutOfBounds
227 }
228 if item[off] != 0 {
229 return CStringView{}, 0, ErrMissingNul
230 }
231 return NewCStringView(item[off:off+1], 0), off + 1, nil
232 }
233
234 func overlap(aStart, aEnd, bStart, bEnd int) bool {
235 return aStart < bEnd && bStart < aEnd
236 }
237
238 func validateLabels(item []byte, hdrSize int, labelCount uint16, fixedEnd int) (int, error) {
239 if labelCount == 0 {
240 if fixedEnd != len(item) {
241 return 0, ErrBadLayout
242 }
243 return fixedEnd, nil
244 }
245
246 tableStart, ok := checkedAlign8(fixedEnd)
247 if !ok {
248 return 0, ErrOutOfBounds
249 }
250 if tableStart > len(item) {
251 return 0, ErrOutOfBounds
252 }
253 for _, b := range item[fixedEnd:tableStart] {
254 if b != 0 {
255 return 0, ErrBadLayout
256 }
257 }
258
259 tableBytes, ok := checkedInt(uint64(labelCount) * uint64(LookupLabelEntrySize))
260 if !ok {
261 return 0, ErrOutOfBounds
262 }
263 expected, ok := checkedAddInt(tableStart, tableBytes)
264 if !ok || expected > len(item) {
265 return 0, ErrOutOfBounds
266 }
267
268 for i := range labelCount {
269 entryRel, ok := checkedInt(uint64(i) * uint64(LookupLabelEntrySize))
270 if !ok {
271 return 0, ErrOutOfBounds
272 }
273 base, ok := checkedAddInt(tableStart, entryRel)
274 if !ok {
275 return 0, ErrOutOfBounds
276 }
277 keyOff, err := checkedWireU32Int(item, base)
278 if err != nil {
279 return 0, err
280 }
281 keyLen, err := checkedWireU32Int(item, base+4)
282 if err != nil {
283 return 0, err
284 }
285 valueOff, err := checkedWireU32Int(item, base+8)
286 if err != nil {
287 return 0, err
288 }
289 valueLen, err := checkedWireU32Int(item, base+12)
290 if err != nil {
291 return 0, err
292 }
293 if keyLen == 0 || keyOff != expected {
294 return 0, ErrBadLayout
295 }
296 _, keyEnd, err := lookupString(item, hdrSize, keyOff, keyLen)
297 if err != nil {
298 return 0, err
299 }
300 expected = keyEnd
301 if valueOff != expected {
302 return 0, ErrBadLayout
303 }
304 _, valueEnd, err := lookupString(item, hdrSize, valueOff, valueLen)
305 if err != nil {
306 return 0, err
307 }
308 expected = valueEnd
309 }
310 if expected != len(item) {
311 return 0, ErrBadLayout
312 }
313 return tableStart, nil
314 }
315
316 func lookupLabelAt(item []byte, hdrSize int, labelCount uint16, tableOffset int, index uint32) (LookupLabelView, error) {
317 if index >= uint32(labelCount) {
318 return LookupLabelView{}, ErrOutOfBounds
319 }
320 entryRel, ok := checkedInt(uint64(index) * uint64(LookupLabelEntrySize))
321 if !ok {
322 return LookupLabelView{}, ErrOutOfBounds
323 }
324 base, ok := checkedAddInt(tableOffset, entryRel)
325 if !ok {
326 return LookupLabelView{}, ErrOutOfBounds
327 }
328 keyOff, err := checkedWireU32Int(item, base)
329 if err != nil {
330 return LookupLabelView{}, err
331 }
332 keyLen, err := checkedWireU32Int(item, base+4)
333 if err != nil {
334 return LookupLabelView{}, err
335 }
336 valueOff, err := checkedWireU32Int(item, base+8)
337 if err != nil {
338 return LookupLabelView{}, err
339 }
340 valueLen, err := checkedWireU32Int(item, base+12)
341 if err != nil {
342 return LookupLabelView{}, err
343 }
344 key, _, err := lookupString(item, hdrSize, keyOff, keyLen)
345 if err != nil {
346 return LookupLabelView{}, err
347 }
348 value, _, err := lookupString(item, hdrSize, valueOff, valueLen)
349 if err != nil {
350 return LookupLabelView{}, err
351 }
352 return LookupLabelView{Key: key, Value: value}, nil
353 }
354
355 func writeLookupLabels(item []byte, tableStart, tableBytes int, labels []struct{ Key, Value []byte }) (int, error) {
356 next, ok := checkedAddInt(tableStart, tableBytes)
357 if !ok {
358 return 0, ErrOverflow
359 }
360 for i, label := range labels {
361 keyOff32, ok := checkedU32Int(next)
362 if !ok {
363 return 0, ErrOverflow
364 }
365 keyLen32, ok := checkedU32Int(len(label.Key))
366 if !ok {
367 return 0, ErrOverflow
368 }
369 valueOff, ok := checkedAddInt(next, len(label.Key))
370 if ok {
371 valueOff, ok = checkedAddInt(valueOff, 1)
372 }
373 if !ok {
374 return 0, ErrOverflow
375 }
376 valueOff32, ok := checkedU32Int(valueOff)
377 if !ok {
378 return 0, ErrOverflow
379 }
380 valueLen32, ok := checkedU32Int(len(label.Value))
381 if !ok {
382 return 0, ErrOverflow
383 }
384 entryRel, ok := checkedMulInt(i, LookupLabelEntrySize)
385 if !ok {
386 return 0, ErrOverflow
387 }
388 entry, ok := checkedAddInt(tableStart, entryRel)
389 if !ok {
390 return 0, ErrOverflow
391 }
392 ne.PutUint32(item[entry:entry+4], keyOff32)
393 ne.PutUint32(item[entry+4:entry+8], keyLen32)
394 ne.PutUint32(item[entry+8:entry+12], valueOff32)
395 ne.PutUint32(item[entry+12:entry+16], valueLen32)
396 copy(item[next:], label.Key)
397 item[next+len(label.Key)] = 0
398 next = valueOff
399 copy(item[next:], label.Value)
400 item[next+len(label.Value)] = 0
401 next, ok = checkedAddInt(next, len(label.Value))
402 if ok {
403 next, ok = checkedAddInt(next, 1)
404 }
405 if !ok {
406 return 0, ErrOverflow
407 }
408 }
409 return next, nil
410 }
411
412 func labelLayoutGo(fixedEnd int, labels []struct{ Key, Value []byte }) (int, int, int, error) {
413 if len(labels) == 0 {
414 return fixedEnd, 0, fixedEnd, nil
415 }
416 tableStart, ok := checkedAlign8(fixedEnd)
417 if !ok {
418 return 0, 0, 0, ErrOverflow
419 }
420 tableBytes, ok := checkedMulInt(len(labels), LookupLabelEntrySize)
421 if !ok {
422 return 0, 0, 0, ErrOverflow
423 }
424 itemSize, ok := checkedAddInt(tableStart, tableBytes)
425 if !ok {
426 return 0, 0, 0, ErrOverflow
427 }
428 for _, label := range labels {
429 if invalidSourceString(label.Key, true) || invalidSourceString(label.Value, false) {
430 return 0, 0, 0, ErrBadLayout
431 }
432 keySize, ok := checkedAddInt(len(label.Key), 1)
433 if ok {
434 valueSize, okValue := checkedAddInt(len(label.Value), 1)
435 if okValue {
436 keySize, ok = checkedAddInt(keySize, valueSize)
437 } else {
438 ok = false
439 }
440 }
441 if ok {
442 itemSize, ok = checkedAddInt(itemSize, keySize)
443 }
444 if !ok {
445 return 0, 0, 0, ErrOverflow
446 }
447 }
448 return tableStart, tableBytes, itemSize, nil
449 }
450
451 func finishLookupResponse(buf []byte, hdrSize int, itemCount uint32, dataOffset int, generation uint64) int {
452 ne.PutUint16(buf[0:2], 1)
453 ne.PutUint16(buf[2:4], 0)
454 ne.PutUint32(buf[4:8], itemCount)
455 ne.PutUint64(buf[8:16], generation)
456 if itemCount == 0 {
457 return hdrSize
458 }
459 dirSize, ok := checkedInt(uint64(itemCount) * uint64(LookupDirEntrySize))
460 if !ok {
461 return 0
462 }
463 count := int(itemCount)
464 finalPackedStart, ok := checkedAddInt(hdrSize, dirSize)
465 if !ok {
466 return 0
467 }
468 firstItemAbs, ok := checkedInt(uint64(ne.Uint32(buf[hdrSize : hdrSize+4])))
469 if !ok {
470 return 0
471 }
472 if dataOffset < firstItemAbs {
473 return 0
474 }
475 packedDataLen := dataOffset - firstItemAbs
476 if finalPackedStart < firstItemAbs {
477 copy(buf[finalPackedStart:], buf[firstItemAbs:firstItemAbs+packedDataLen])
478 }
479 for i := range count {
480 entry := hdrSize + i*LookupDirEntrySize
481 abs, ok := checkedInt(uint64(ne.Uint32(buf[entry : entry+4])))
482 if !ok || abs < firstItemAbs {
483 return 0
484 }
485 rel, ok := checkedU32Int(abs - firstItemAbs)
486 if !ok {
487 return 0
488 }
489 ne.PutUint32(buf[entry:entry+4], rel)
490 }
491 return finalPackedStart + packedDataLen
492 }