Skip to content

Commit ef85141

Browse files
authored
Fix unit16 overflow issue in network policy priority assigner (#7496) (#7498)
Fix overflow in initialOFPriority() function where calculations exceeded uint16 max value (65535). In addition, there are potential uint16 underflow issues in insertConsecutivePriorities at the edge case where upperBound is equal to lowerBound, as well as potential invalid range calculations in cost map entries. This commit fixes all these issues and added UT to make sure no overflow/underflow is still occurring and the priority registration and reassignment is working as expected. Signed-off-by: Dyanngg <[email protected]>
1 parent e067938 commit ef85141

File tree

2 files changed

+210
-25
lines changed

2 files changed

+210
-25
lines changed

pkg/agent/controller/networkpolicy/priority.go

Lines changed: 98 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -116,14 +116,28 @@ func (pa *priorityAssigner) initialOFPriority(p types.Priority) uint16 {
116116
tierOffsetBase = tierOffsetBaselineTier
117117
priorityOffsetBase = priorityOffsetBaselineTier
118118
}
119-
tierOffset := tierOffsetBase * uint16(p.TierPriority)
120-
priorityOffset := uint16(p.PolicyPriority * priorityOffsetBase)
121-
offSet := tierOffset + priorityOffset + uint16(p.RulePriority)
122-
// Cannot return a negative OF priority.
123-
if pa.policyTopPriority-pa.policyBottomPriority < offSet {
119+
// Use uint32 to prevent arithmetic overflow during calculation
120+
tierOffset := uint32(tierOffsetBase) * uint32(p.TierPriority)
121+
priorityOffset := uint32(p.PolicyPriority * priorityOffsetBase)
122+
offSet := tierOffset + priorityOffset + uint32(p.RulePriority)
123+
124+
// Check if the calculated offset exceeds the available priority range
125+
availableRange := uint32(pa.policyTopPriority) - uint32(pa.policyBottomPriority)
126+
if availableRange < offSet {
124127
return pa.policyBottomPriority
125128
}
126-
return pa.policyTopPriority - offSet
129+
// Safe to cast offSet to uint16: the preceding if statement guarantees that
130+
// offSet <= availableRange <= (policyTopPriority - policyBottomPriority) <= math.MaxUint16
131+
return pa.policyTopPriority - uint16(offSet)
132+
}
133+
134+
// findGap calculates the gap between upperBound and lowerBound with uint16 underflow protection.
135+
// Returns the gap and whether there is any space available.
136+
func findGap(upperBound, lowerBound uint16) (gap uint16, hasSpace bool) {
137+
if upperBound <= lowerBound {
138+
return 0, false
139+
}
140+
return upperBound - lowerBound - 1, true
127141
}
128142

129143
// updatePriorityAssignment updates all the local maps to correlate input ofPriority and Priority.
@@ -154,12 +168,26 @@ func (pa *priorityAssigner) findReassignBoundaries(lowerBound, upperBound uint16
154168
costMap := map[int]*reassignCost{}
155169
reassignBoundLow, reassignBoundHigh := lowerBound, upperBound
156170
costSiftDown, costSiftUp, emptiedSlotsLow, emptiedSlotsHigh := 0, 0, 0, 0
171+
// Search for empty slots below lowerBound, but don't go below policyBottomPriority
157172
for reassignBoundLow >= pa.policyBottomPriority && emptiedSlotsLow < target {
158173
if _, exists := pa.ofPriorityMap[reassignBoundLow]; exists {
159174
costSiftDown++
160175
} else {
161176
emptiedSlotsLow++
162-
costMap[emptiedSlotsLow] = &reassignCost{reassignBoundLow, upperBound - 1, costSiftDown}
177+
// When upperBound == lowerBound, use upperBound directly to create a valid single-slot range
178+
// When upperBound > lowerBound, use upperBound - 1 to avoid overlapping with the gap
179+
upperBoundForCost := upperBound
180+
if upperBound > lowerBound {
181+
upperBoundForCost = upperBound - 1
182+
}
183+
// Special case: when we're at the boundary itself, include it in the range
184+
if reassignBoundLow == lowerBound && upperBound == lowerBound {
185+
upperBoundForCost = upperBound // Include the boundary slot
186+
}
187+
// Only create cost map entry if it results in a valid range
188+
if reassignBoundLow <= upperBoundForCost {
189+
costMap[emptiedSlotsLow] = &reassignCost{reassignBoundLow, upperBoundForCost, costSiftDown}
190+
}
163191
}
164192
reassignBoundLow--
165193
}
@@ -178,7 +206,12 @@ func (pa *priorityAssigner) findReassignBoundaries(lowerBound, upperBound uint16
178206
c.cost = costSiftDown + costSiftUp
179207
c.upperBound = reassignBoundHigh
180208
} else if mapIndex == 0 {
181-
costMap[mapIndex] = &reassignCost{lowerBound + 1, reassignBoundHigh, costSiftUp}
209+
// When upperBound == lowerBound, start from lowerBound itself, not lowerBound + 1
210+
startBound := lowerBound + 1
211+
if upperBound == lowerBound {
212+
startBound = lowerBound // Include the boundary slot in the reassignment range
213+
}
214+
costMap[mapIndex] = &reassignCost{startBound, reassignBoundHigh, costSiftUp}
182215
}
183216
}
184217
reassignBoundHigh++
@@ -187,7 +220,9 @@ func (pa *priorityAssigner) findReassignBoundaries(lowerBound, upperBound uint16
187220
for i := target; i >= 0; i-- {
188221
if cost, exists := costMap[i]; exists && cost.cost < minCost {
189222
// make sure that the reassign range adds up to the number of all Priorities to be registered.
190-
if int(cost.upperBound-cost.lowerBound)+1 == numNewPriorities+cost.cost {
223+
rangeSize := int(cost.upperBound-cost.lowerBound) + 1
224+
requiredSize := numNewPriorities + cost.cost
225+
if rangeSize == requiredSize {
191226
minCost = cost.cost
192227
minCostIndex = i
193228
}
@@ -205,20 +240,29 @@ func (pa *priorityAssigner) findReassignBoundaries(lowerBound, upperBound uint16
205240
// map of updates, which is passed to it as parameter.
206241
func (pa *priorityAssigner) reassignBoundaryPriorities(lowerBound, upperBound uint16, prioritiesToRegister types.ByPriority,
207242
updates map[types.Priority]*priorityUpdate) error {
208-
numNewPriorities, gap := len(prioritiesToRegister), int(upperBound-lowerBound-1)
243+
numNewPriorities := len(prioritiesToRegister)
244+
gapUint16, hasSpace := findGap(upperBound, lowerBound)
245+
var gap int
246+
if hasSpace {
247+
gap = int(gapUint16)
248+
} else {
249+
gap = 0 // No available slots when upperBound <= lowerBound
250+
}
209251
low, high, err := pa.findReassignBoundaries(lowerBound, upperBound, numNewPriorities, gap)
210252
if err != nil {
211253
return err
212254
}
213255
// siftedPrioritiesLow and siftedPrioritiesHigh keep track of Priorities that need to be reassigned,
214-
// below the lowerBound and above the upperBound respectively.
256+
// below the lowerBound and above the upperBound respectively. Note that we do not include the priority
257+
// currently assigned to policyBottomPriority since it cannot be sifted down further, and vice versa
258+
// for policyTopPriority.
215259
var siftedPrioritiesLow, siftedPrioritiesHigh types.ByPriority
216-
for i := low; i <= lowerBound; i++ {
260+
for i := low; i <= lowerBound && i != pa.policyBottomPriority; i++ {
217261
if p, exists := pa.ofPriorityMap[i]; exists {
218262
siftedPrioritiesLow = append(siftedPrioritiesLow, p)
219263
}
220264
}
221-
for i := upperBound; i <= high; i++ {
265+
for i := upperBound; i <= high && i != pa.policyTopPriority; i++ {
222266
if p, exists := pa.ofPriorityMap[i]; exists {
223267
siftedPrioritiesHigh = append(siftedPrioritiesHigh, p)
224268
}
@@ -236,7 +280,12 @@ func (pa *priorityAssigner) reassignBoundaryPriorities(lowerBound, upperBound ui
236280
}
237281
// assign ofPriorities by the order of siftedPrioritiesLow, prioritiesToRegister and siftedPrioritiesHigh.
238282
for i, p := range allPriorities {
239-
pa.updatePriorityAssignment(low+uint16(i), p)
283+
// Protect against overflow when low + i > 65535
284+
ofPriority := uint64(low) + uint64(i)
285+
if ofPriority > math.MaxUint16 {
286+
return fmt.Errorf("priority assignment overflow: low=%d + i=%d exceeds uint16 range", low, i)
287+
}
288+
pa.updatePriorityAssignment(uint16(ofPriority), p)
240289
}
241290
// record the ofPriorities of the reassigned Priorities after the reassignment.
242291
for _, p := range reassignedPriorities {
@@ -273,8 +322,13 @@ func (pa *priorityAssigner) registerPriorities(priorities []types.Priority) (map
273322
klog.V(2).Infof("%v new priorities need to be registered", numPriorityToRegister)
274323
if numPriorityToRegister == 0 {
275324
return nil, nil, nil
276-
} else if uint16(numPriorityToRegister+len(pa.sortedPriorities)) > pa.policyTopPriority-pa.policyBottomPriority+1 {
277-
return nil, nil, fmt.Errorf("number of priorities to be registered is greater than available openflow priorities")
325+
} else {
326+
// Check for overflow before casting to uint16
327+
totalPriorities := numPriorityToRegister + len(pa.sortedPriorities)
328+
availableRange := uint32(pa.policyTopPriority) - uint32(pa.policyBottomPriority) + 1
329+
if uint32(totalPriorities) > availableRange {
330+
return nil, nil, fmt.Errorf("number of priorities to be registered (%d) is greater than available openflow priorities (%d)", totalPriorities, availableRange)
331+
}
278332
}
279333
sort.Sort(types.ByPriority(prioritiesToRegister))
280334
var consecutivePriorities [][]types.Priority
@@ -340,32 +394,51 @@ func (pa *priorityAssigner) insertConsecutivePriorities(priorities types.ByPrior
340394
// set upperBound to the ofPriority of the registered Priority that is immediately higher than the inserting Priorities.
341395
upperBound = pa.priorityMap[pa.sortedPriorities[insertionIdx]]
342396
}
343-
// not enough space to insert Priorities.
344-
if upperBound-lowerBound-1 < uint16(numPriorities) {
397+
gap, hasSpace := findGap(upperBound, lowerBound)
398+
// not enough space currently to insert Priorities.
399+
if !hasSpace || gap < uint16(numPriorities) {
345400
return pa.reassignBoundaryPriorities(lowerBound, upperBound, priorities, updates)
346401
}
347402
switch {
348403
// ofPriorities provided by the heuristic function are good.
349404
case insertionPointLow > lowerBound && insertionPointHigh < upperBound:
350405
break
351-
// ofPriorities returned by the heuristic function overlap with existing Priorities/are out of place.
406+
// ofPriorities returned by the heuristic function are out of place/overlap with existing Priorities.
352407
// If the Priorities to be registered overlap with lower Priorities/are lower than the lower Priorities,
353408
// and the gap between lowerBound and upperBound for insertion is large, then we insert these Priorities
354-
// above the lowerBound, offsetted by a constant zoneOffset. Vice versa for the other way around.
409+
// above the lowerBound, offsetted by a constant zoneOffset, and vice versa.
355410
// 5 is chosen as the zoneOffset here since it gives some buffer in case Priorities are again created
356411
// in between those zones, while in the meantime keeps priority assignments compact.
357-
case upperBound-lowerBound-1 >= uint16(numPriorities)+2*zoneOffset:
412+
case gap >= uint16(numPriorities)+2*zoneOffset:
358413
if insertionPointLow <= lowerBound {
359-
insertionPointLow = lowerBound + zoneOffset + 1
414+
// Protect against overflow: lowerBound + zoneOffset + 1
415+
if uint32(lowerBound)+uint32(zoneOffset)+1 > math.MaxUint16 {
416+
insertionPointLow = math.MaxUint16 - uint16(numPriorities) + 1
417+
} else {
418+
insertionPointLow = lowerBound + zoneOffset + 1
419+
}
360420
} else {
361-
insertionPointLow = upperBound - zoneOffset - uint16(len(priorities))
421+
// Protect against underflow: upperBound - zoneOffset - uint16(len(priorities))
422+
requiredSpace := uint32(zoneOffset) + uint32(len(priorities))
423+
if uint32(upperBound) < requiredSpace {
424+
insertionPointLow = pa.policyBottomPriority
425+
} else {
426+
insertionPointLow = upperBound - zoneOffset - uint16(len(priorities))
427+
}
362428
}
363429
// when the window between upper/lowerBound is small, simply put the Priorities in the middle of the window.
364430
default:
365-
insertionPointLow = lowerBound + (upperBound-lowerBound-uint16(numPriorities))/2 + 1
431+
// gap is guaranteed >= numPriorities by the condition check above
432+
// So gap - uint16(numPriorities) will not underflow
433+
insertionPointLow = lowerBound + (gap-uint16(numPriorities))/2 + 1
366434
}
367435
for i := 0; i < len(priorities); i++ {
368-
pa.updatePriorityAssignment(insertionPointLow+uint16(i), priorities[i])
436+
// Protect against overflow: insertionPointLow + uint16(i)
437+
ofPriority := uint32(insertionPointLow) + uint32(i)
438+
if ofPriority > math.MaxUint16 {
439+
return fmt.Errorf("priority assignment overflow: insertionPoint=%d + i=%d exceeds uint16 range", insertionPointLow, i)
440+
}
441+
pa.updatePriorityAssignment(uint16(ofPriority), priorities[i])
369442
}
370443
return nil
371444
}

pkg/agent/controller/networkpolicy/priority_test.go

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,12 @@
1515
package networkpolicy
1616

1717
import (
18+
"slices"
19+
"sort"
1820
"testing"
1921

2022
"github.com/stretchr/testify/assert"
23+
"github.com/stretchr/testify/require"
2124

2225
"antrea.io/antrea/pkg/agent/types"
2326
)
@@ -315,6 +318,85 @@ func TestRegisterDuplicatePriorities(t *testing.T) {
315318
assert.Equal(t, ofPriority1131, ofPriority1131Dup)
316319
}
317320

321+
func TestRegister500PrioritiesNoOverflow(t *testing.T) {
322+
tests := []struct {
323+
name string
324+
generateFunc func() []types.Priority
325+
}{
326+
{
327+
"500-default-tier-priorities",
328+
func() []types.Priority {
329+
var priorities []types.Priority
330+
for i := 1; i <= 500; i++ {
331+
priority := types.Priority{
332+
TierPriority: defaultTierPriority, // 250
333+
PolicyPriority: float64(i),
334+
RulePriority: 0,
335+
}
336+
priorities = append(priorities, priority)
337+
}
338+
return priorities
339+
},
340+
},
341+
{
342+
"500-tier1-small-policy-priorities",
343+
func() []types.Priority {
344+
var priorities []types.Priority
345+
for i := 1; i <= 500; i++ {
346+
priority := types.Priority{
347+
TierPriority: 1,
348+
PolicyPriority: float64(i) * 0.01,
349+
RulePriority: 0,
350+
}
351+
priorities = append(priorities, priority)
352+
}
353+
return priorities
354+
},
355+
},
356+
{
357+
"500-tier20-with-rule-priorities",
358+
func() []types.Priority {
359+
var priorities []types.Priority
360+
for i := 1; i <= 500; i++ {
361+
priority := types.Priority{
362+
TierPriority: 20,
363+
PolicyPriority: float64(i) * 5,
364+
RulePriority: int32(i),
365+
}
366+
priorities = append(priorities, priority)
367+
}
368+
return priorities
369+
},
370+
},
371+
}
372+
373+
for _, tt := range tests {
374+
t.Run(tt.name, func(t *testing.T) {
375+
pa := newPriorityAssigner(false)
376+
377+
// Generate priorities for this test case
378+
priorities := tt.generateFunc()
379+
// Make a copy since registerPriorities modifies the slice order
380+
originalPriorities := make([]types.Priority, len(priorities))
381+
copy(originalPriorities, priorities)
382+
sort.Sort(types.ByPriority(originalPriorities))
383+
384+
_, _, err := pa.registerPriorities(priorities)
385+
require.NoError(t, err)
386+
387+
var sortedOFPriorities []uint16
388+
for _, priority := range originalPriorities {
389+
ofPriority, exists := pa.getOFPriority(priority)
390+
require.True(t, exists, "Priority %v should be registered", priority)
391+
sortedOFPriorities = append(sortedOFPriorities, ofPriority)
392+
}
393+
394+
// Verify that OpenFlow priorities are sorted in ascending order (higher precedence gets lower OpenFlow priority)
395+
require.True(t, slices.IsSorted(sortedOFPriorities), "OpenFlow priorities should be sorted in ascending order (higher precedence gets lower OpenFlow priority)")
396+
})
397+
}
398+
}
399+
318400
func generatePriorities(tierPriority, start, end int32, policyPriority float64) []types.Priority {
319401
priorities := make([]types.Priority, end-start+1)
320402
for i := start; i <= end; i++ {
@@ -329,6 +411,20 @@ func TestRegisterAllOFPriorities(t *testing.T) {
329411
_, _, err := pa.registerPriorities(maxPriorities)
330412
assert.NoError(t, err, "Error occurred in registering max number of allowed priorities in baseline tier")
331413

414+
// Check that all priorities registered in baseline tier are sorted in ofPriorities space
415+
sortedMaxPriorities := make([]types.Priority, len(maxPriorities))
416+
copy(sortedMaxPriorities, maxPriorities)
417+
sort.Sort(types.ByPriority(sortedMaxPriorities))
418+
419+
var baselineOFPriorities []uint16
420+
for _, priority := range sortedMaxPriorities {
421+
ofPriority, exists := pa.getOFPriority(priority)
422+
require.True(t, exists, "Priority %v should be registered", priority)
423+
baselineOFPriorities = append(baselineOFPriorities, ofPriority)
424+
}
425+
// Verify that OpenFlow priorities are sorted in ascending order (higher precedence gets lower OpenFlow priority)
426+
require.True(t, slices.IsSorted(baselineOFPriorities), "Baseline tier OpenFlow priorities should be sorted in ascending order")
427+
332428
extraPriority := types.Priority{
333429
TierPriority: 253,
334430
PolicyPriority: 5,
@@ -346,6 +442,22 @@ func TestRegisterAllOFPriorities(t *testing.T) {
346442
_, _, err = pa.registerPriorities(consecPriorities2)
347443
assert.NoError(t, err, "Error occurred in registering max number of allowed priorities")
348444

445+
// Create a combined sorted copy of all registered priorities
446+
allRegisteredPriorities := make([]types.Priority, 0, len(consecPriorities1)+len(consecPriorities2))
447+
allRegisteredPriorities = append(allRegisteredPriorities, consecPriorities1...)
448+
allRegisteredPriorities = append(allRegisteredPriorities, consecPriorities2...)
449+
sort.Sort(types.ByPriority(allRegisteredPriorities))
450+
451+
// Get OpenFlow priorities for all registered priorities in sorted order
452+
var allOFPriorities []uint16
453+
for _, priority := range allRegisteredPriorities {
454+
ofPriority, exists := pa.getOFPriority(priority)
455+
require.True(t, exists, "Priority %v should be registered", priority)
456+
allOFPriorities = append(allOFPriorities, ofPriority)
457+
}
458+
// Verify that OpenFlow priorities are sorted in ascending order (higher precedence gets lower OpenFlow priority)
459+
require.True(t, slices.IsSorted(allOFPriorities), "All registered OpenFlow priorities should be sorted in ascending order")
460+
349461
_, _, err = pa.registerPriorities([]types.Priority{extraPriority})
350462
assert.Errorf(t, err, "Error should be raised after max number of priorities are registered")
351463
}

0 commit comments

Comments
 (0)