VYPR
Moderate severityNVD Advisory· Published Oct 21, 2024· Updated Oct 21, 2024

CIDR deny policies may not take effect when a more narrow CIDR allow is present

CVE-2024-47825

Description

Cilium is a networking, observability, and security solution with an eBPF-based dataplane. Starting in version 1.14.0 and prior to versions 1.14.16 and 1.15.10, a policy rule denying a prefix that is broader than /32 may be ignored if there is a policy rule referencing a more narrow prefix (CIDRSet or toFQDN) and this narrower policy rule specifies either enableDefaultDeny: false or - toEntities: all. Note that a rule specifying toEntities: world or toEntities: 0.0.0.0/0 is insufficient, it must be to entity all.This issue has been patched in Cilium v1.14.16 and v1.15.10. As this issue only affects policies using enableDefaultDeny: false or that set toEntities to all, some workarounds are available. For users with policies using enableDefaultDeny: false, remove this configuration option and explicitly define any allow rules required. For users with egress policies that explicitly specify toEntities: all, use toEntities: world.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/cilium/ciliumGo
>= 1.15.0, < 1.15.101.15.10
github.com/cilium/ciliumGo
>= 1.14.0, < 1.14.161.14.16

Affected products

1

Patches

2
9c01afb5646a

policy: Fix broad deny with except bug

https://github.com/cilium/ciliumJarno RajahalmeAug 19, 2024via ghsa
3 files changed · +223 92
  • pkg/policy/distillery_test.go+31 8 modified
    @@ -1399,7 +1399,7 @@ var (
     
     	worldIPIdentity    = localIdentity(16324)
     	worldCIDR          = api.CIDR("192.0.2.3/32")
    -	lblWorldIP         = labels.ParseSelectLabelArray(fmt.Sprintf("%s:%s", labels.LabelSourceCIDR, worldCIDR))
    +	lblWorldIP         = cidr.GetCIDRLabels(netip.MustParsePrefix(string(worldCIDR)))
     	ruleL3AllowWorldIP = api.NewRule().WithIngressRules([]api.IngressRule{{
     		IngressCommonRule: api.IngressCommonRule{
     			FromCIDR: api.CIDRSlice{worldCIDR},
    @@ -1528,6 +1528,13 @@ var (
     	mapKeyL3L4Port8080ProtoSCTPWorldSNIngress = Key{worldSubnetIdentity.Uint32(), 8080, 132, trafficdirection.Ingress.Uint8()}
     	mapKeyL3L4Port8080ProtoSCTPWorldSNEgress  = Key{worldSubnetIdentity.Uint32(), 8080, 132, trafficdirection.Egress.Uint8()}
     
    +	mapKeyL3L4Port8080ProtoTCPWorldIPIngress  = Key{worldIPIdentity.Uint32(), 8080, 6, trafficdirection.Ingress.Uint8()}
    +	mapKeyL3L4Port8080ProtoTCPWorldIPEgress   = Key{worldIPIdentity.Uint32(), 8080, 6, trafficdirection.Egress.Uint8()}
    +	mapKeyL3L4Port8080ProtoUDPWorldIPIngress  = Key{worldIPIdentity.Uint32(), 8080, 17, trafficdirection.Ingress.Uint8()}
    +	mapKeyL3L4Port8080ProtoUDPWorldIPEgress   = Key{worldIPIdentity.Uint32(), 8080, 17, trafficdirection.Egress.Uint8()}
    +	mapKeyL3L4Port8080ProtoSCTPWorldIPIngress = Key{worldIPIdentity.Uint32(), 8080, 132, trafficdirection.Ingress.Uint8()}
    +	mapKeyL3L4Port8080ProtoSCTPWorldIPEgress  = Key{worldIPIdentity.Uint32(), 8080, 132, trafficdirection.Egress.Uint8()}
    +
     	ruleL3AllowWorldSubnet = api.NewRule().WithIngressRules([]api.IngressRule{{
     		ToPorts: api.PortRules{
     			api.PortRule{
    @@ -1611,7 +1618,7 @@ func Test_EnsureDeniesPrecedeAllows(t *testing.T) {
     		return cache.IdentityCache{
     			identity.NumericIdentity(identityFoo): labelsFoo,
     			identity.ReservedIdentityWorld:        labels.LabelWorld.LabelArray(),
    -			worldIPIdentity:                       lblWorldIP,                  // "192.0.2.3/32"
    +			worldIPIdentity:                       lblWorldIP.LabelArray(),     // "192.0.2.3/32"
     			worldSubnetIdentity:                   lblWorldSubnet.LabelArray(), // "192.0.2.0/24"
     		}
     	}
    @@ -1654,14 +1661,24 @@ func Test_EnsureDeniesPrecedeAllows(t *testing.T) {
     		result   MapState
     	}{
     		{"deny_world_no_labels", api.Rules{ruleL3DenyWorld, ruleL3AllowWorldIP}, defaultIDs, map[Key]MapStateEntry{
    -			mapKeyL3WorldIngress: mapEntryDeny,
    -			mapKeyL3WorldEgress:  mapEntryDeny,
    +			mapKeyL3WorldIngress:         mapEntryDeny,
    +			mapKeyL3WorldEgress:          mapEntryDeny,
    +			mapKeyL3SubnetIngress:        mapEntryDeny,
    +			mapKeyL3SubnetEgress:         mapEntryDeny,
    +			mapKeyL3SmallerSubnetIngress: mapEntryDeny,
    +			mapKeyL3SmallerSubnetEgress:  mapEntryDeny,
     		}}, {"deny_world_with_labels", api.Rules{ruleL3DenyWorldWithLabels, ruleL3AllowWorldIP}, defaultIDs, map[Key]MapStateEntry{
    -			mapKeyL3WorldIngress: mapEntryWorldDenyWithLabels,
    -			mapKeyL3WorldEgress:  mapEntryWorldDenyWithLabels,
    +			mapKeyL3WorldIngress:         mapEntryWorldDenyWithLabels,
    +			mapKeyL3WorldEgress:          mapEntryWorldDenyWithLabels,
    +			mapKeyL3SubnetIngress:        mapEntryWorldDenyWithLabels,
    +			mapKeyL3SubnetEgress:         mapEntryWorldDenyWithLabels,
    +			mapKeyL3SmallerSubnetIngress: mapEntryWorldDenyWithLabels,
    +			mapKeyL3SmallerSubnetEgress:  mapEntryWorldDenyWithLabels,
     		}}, {"deny_one_ip_with_a_larger_subnet", api.Rules{ruleL3DenySubnet, ruleL3AllowWorldIP}, defaultIDs, map[Key]MapStateEntry{
    -			mapKeyL3SubnetIngress: mapEntryDeny,
    -			mapKeyL3SubnetEgress:  mapEntryDeny,
    +			mapKeyL3SubnetIngress:        mapEntryDeny,
    +			mapKeyL3SubnetEgress:         mapEntryDeny,
    +			mapKeyL3SmallerSubnetIngress: mapEntryDeny,
    +			mapKeyL3SmallerSubnetEgress:  mapEntryDeny,
     		}}, {"deny_part_of_a_subnet_with_an_ip", api.Rules{ruleL3DenySmallerSubnet, ruleL3AllowLargerSubnet}, defaultIDs, map[Key]MapStateEntry{
     			mapKeyL3SmallerSubnetIngress: mapEntryDeny,
     			mapKeyL3SmallerSubnetEgress:  mapEntryDeny,
    @@ -1691,6 +1708,12 @@ func Test_EnsureDeniesPrecedeAllows(t *testing.T) {
     			mapKeyL3L4Port8080ProtoUDPWorldEgress:     mapEntryDeny,
     			mapKeyL3L4Port8080ProtoSCTPWorldIngress:   mapEntryDeny,
     			mapKeyL3L4Port8080ProtoSCTPWorldEgress:    mapEntryDeny,
    +			mapKeyL3L4Port8080ProtoTCPWorldIPIngress:  mapEntryDeny,
    +			mapKeyL3L4Port8080ProtoTCPWorldIPEgress:   mapEntryDeny,
    +			mapKeyL3L4Port8080ProtoUDPWorldIPIngress:  mapEntryDeny,
    +			mapKeyL3L4Port8080ProtoUDPWorldIPEgress:   mapEntryDeny,
    +			mapKeyL3L4Port8080ProtoSCTPWorldIPIngress: mapEntryDeny,
    +			mapKeyL3L4Port8080ProtoSCTPWorldIPEgress:  mapEntryDeny,
     			mapKeyL3SmallerSubnetIngress:              mapEntryAllow,
     			mapKeyL3SmallerSubnetEgress:               mapEntryAllow,
     			mapKeyL3L4Port8080ProtoTCPWorldSNIngress:  mapEntryDeny,
    
  • pkg/policy/mapstate.go+132 46 modified
    @@ -365,12 +365,7 @@ func (keys MapState) addKeyWithChanges(key Key, entry MapStateEntry, changes Cha
     	// Keep all owners that need this entry so that it is deleted only if all the owners delete their contribution
     	oldEntry, exists := keys[key]
     	var datapathEqual bool
    -	if exists {
    -		// Deny entry can only be overridden by another deny entry
    -		if oldEntry.IsDeny && !entry.IsDeny {
    -			return
    -		}
    -
    +	if exists && oldEntry.IsDeny == entry.IsDeny {
     		if entry.DeepEqual(&oldEntry) {
     			return // nothing to do
     		}
    @@ -383,7 +378,7 @@ func (keys MapState) addKeyWithChanges(key Key, entry MapStateEntry, changes Cha
     		datapathEqual = oldEntry.DatapathEqual(&entry)
     		oldEntry.Merge(&entry)
     		keys[key] = oldEntry
    -	} else {
    +	} else if !exists || entry.IsDeny {
     		// Newly inserted entries must have their own containers, so that they
     		// remain separate when new owners/dependents are added to existing entries
     		entry.DerivedFromRules = slices.Clone(entry.DerivedFromRules)
    @@ -564,6 +559,7 @@ func (keys MapState) denyPreferredInsertWithChanges(newKey Key, newEntry MapStat
     		return
     	}
     	if newEntry.IsDeny {
    +		bailed := false
     		for k, v := range keys {
     			// Protocols and traffic directions that don't match ensure that the policies
     			// do not interact in anyway.
    @@ -587,44 +583,96 @@ func (keys MapState) denyPreferredInsertWithChanges(newKey Key, newEntry MapStat
     						// identity is removed.
     						newEntry.AddDependent(newKeyCpy)
     					}
    -				} else if (newKey.Identity == k.Identity ||
    -					identityIsSupersetOf(newKey.Identity, k.Identity, identities)) &&
    -					(newKey.PortProtoIsBroader(k) || newKey.PortProtoIsEqual(k)) {
    -					// If the new-entry is a superset (or equal) of the iterated-allow-entry and
    -					// the new-entry has a broader (or equal) port-protocol then we
    -					// should delete the iterated-allow-entry
    -					keys.deleteKeyWithChanges(k, nil, changes)
    +				} else if newKey.PortProtoIsBroader(k) || newKey.PortProtoIsEqual(k) {
    +					// If newKey has a broader (or equal) port-protocol then we should
    +					// either delete the iterated-allow-entry (if the identity is the
    +					// same or the newKey is L3 wildcard), or change it to a deny entry
    +					// if the newKey's identity is a superset of the iterated identity
    +					// (e.g., newKey has a wider CIDR (say 10/8 covering the iterated
    +					// identity of more specific CIDR (say 10.1.1.1). Note that the
    +					// security identities assigned to these CIDRs have no numerical
    +					// relation to each other (e.g, they could be any numbers X and Y)
    +					// and the datapath does an exact match on them.
    +					if newKey.Identity == 0 || newKey.Identity == k.Identity {
    +						keys.deleteKeyWithChanges(k, nil, changes)
    +					} else if identityIsSupersetOf(newKey.Identity, k.Identity, identities) {
    +						// When newKey.Identity is not ANY and is different from the
    +						// subset key, but still a superset (e.g., in CIDR sense) we
    +						// must keep the subset key and make it a deny instead.
    +						l3l4DenyEntry := NewMapStateEntry(newKey, newEntry.DerivedFromRules, false, true, DefaultAuthType, AuthTypeDisabled)
    +						keys.addKeyWithChanges(k, l3l4DenyEntry, changes)
    +						newEntry.AddDependent(k)
    +					}
    +				} else if newKey.Identity != 0 {
    +					if identityIsSupersetOf(newKey.Identity, k.Identity, identities) {
    +						// k.PortProtoIsBroader(newKey) // due to if statements above
    +
    +						// Deny takes precedence for the port/proto of the newKey
    +						// for each allow with broader port/proto and narrower ID.
    +
    +						// If newKey is a superset of the iterated allow key and newKey has
    +						// more specific port-protocol than the iterated allow key then an
    +						// additional deny entry with port/proto of newKey and with the
    +						// identity of the iterated allow key must be added.
    +						denyKeyCpy := newKey
    +						denyKeyCpy.Identity = k.Identity
    +						l3l4DenyEntry := NewMapStateEntry(newKey, newEntry.DerivedFromRules, false, true, DefaultAuthType, AuthTypeDisabled)
    +						keys.addKeyWithChanges(denyKeyCpy, l3l4DenyEntry, changes)
    +						newEntry.AddDependent(denyKeyCpy)
    +					}
     				}
    -			} else if (newKey.Identity == k.Identity ||
    -				identityIsSupersetOf(k.Identity, newKey.Identity, identities)) &&
    -				k.DestPort == 0 && k.Nexthdr == 0 &&
    -				!v.HasDependent(newKey) {
    -				// If this iterated-deny-entry is a supserset (or equal) of the new-entry and
    -				// the iterated-deny-entry is an L3-only policy then we
    -				// should not insert the new entry (as long as it is not one
    -				// of the special L4-only denies we created to cover the special
    -				// case of a superset-allow with a more specific port-protocol).
     				//
    -				// NOTE: This condition could be broader to reject more deny entries,
    -				// but there *may* be performance tradeoffs.
    -				return
    -			} else if (newKey.Identity == k.Identity ||
    -				identityIsSupersetOf(newKey.Identity, k.Identity, identities)) &&
    -				newKey.DestPort == 0 && newKey.Nexthdr == 0 &&
    -				!newEntry.HasDependent(k) {
    -				// If this iterated-deny-entry is a subset (or equal) of the new-entry and
    -				// the new-entry is an L3-only policy then we
    -				// should delete the iterated-deny-entry (as long as it is not one
    -				// of the special L4-only denies we created to cover the special
    -				// case of a superset-allow with a more specific port-protocol).
    +				// END OF ITERATED-ALLOW PROCESSING, ALL REMAINING CONDITIONS HIT
    +				// ITERATED-DENY ENTRIES
     				//
    -				// NOTE: This condition could be broader to reject more deny entries,
    -				// but there *may* be performance tradeoffs.
    +			} else if bailed {
    +				// Skip processing further deny entries if we are bailing out.
    +				// We still need to loop through all the allow entries, so we can not
    +				// break out when bailing!
    +				continue
    +			} else if (k.Identity == 0 || k.Identity == newKey.Identity) &&
    +				(k.PortProtoIsEqual(newKey) || k.PortProtoIsBroader(newKey)) {
    +				// A narrower of two deny keys is redundant in the datapath only if
    +				// the broader ID is 0, or the IDs are the same. This is because the
    +				// ID will be assigned from the ipcache and datapath has no notion
    +				// of one ID being related to another (e.g., in a CIDR sense).
    +
    +				// If this iterated-deny-entry is an deny-all-L3 or has the same ID
    +				// as the new-entry and the iterated-deny-entry has a broader (or
    +				// equal) port-protocol it will match all the packets the newKey
    +				// would, given that we do not allow more specific allow rules to be
    +				// inserted.
    +
    +				// Identical key needs to be added if entries are different (to merge
    +				// them). This has no effect on the datapath policy map but is
    +				// needed for internal bookkeeping.
    +				if k != newKey || v.DeepEqual(&newEntry) {
    +					bailed = true
    +					continue
    +				}
    +			} else if (newKey.Identity == 0 || newKey.Identity == k.Identity) &&
    +				(newKey.PortProtoIsEqual(k) || newKey.PortProtoIsBroader(k)) &&
    +				!newEntry.HasDependent(k) {
    +				// If this iterated-deny-entry is a subset (or equal) of the
    +				// new-entry and the new-entry has a broader (or equal)
    +				// port-protocol the newKey will match all the packets the iterated
    +				// key would, given that there are no more specific or L4-only allow
    +				// entries. We remove(d) the more specific allow rules in the blocks
    +				// above, and added more specific deny rules if there was an L4-only
    +				// allow rule. We use 'HasDependant' to figure out that 'k' must
    +				// remain to take precedence over the L4-only allow key.
    +
    +				// Identical key would have been captured in the block above, so we
    +				// do not need to check for it here.
     				keys.deleteKeyWithChanges(k, nil, changes)
     			}
     		}
    -		keys.addKeyWithChanges(newKey, newEntry, changes)
    +		if !bailed {
    +			keys.addKeyWithChanges(newKey, newEntry, changes)
    +		}
     	} else {
    +		insertAsDeny := false
    +		var denyEntry MapStateEntry
     		for k, v := range keys {
     			// Protocols and traffic directions that don't match ensure that the policies
     			// do not interact in anyway.
    @@ -650,17 +698,55 @@ func (keys MapState) denyPreferredInsertWithChanges(newKey Key, newEntry MapStat
     						// identity is removed.
     						keys.addDependentOnEntry(k, v, denyKeyCpy, changes)
     					}
    -				} else if (k.Identity == newKey.Identity ||
    -					identityIsSupersetOf(k.Identity, newKey.Identity, identities)) &&
    -					(k.PortProtoIsBroader(newKey) || k.PortProtoIsEqual(newKey)) &&
    -					!v.HasDependent(newKey) {
    -					// If the iterated-deny-entry is a superset (or equal) of the new-entry and has a
    -					// broader (or equal) port-protocol than the new-entry then the new
    -					// entry should not be inserted.
    -					return
    +				} else if k.PortProtoIsBroader(newKey) || k.PortProtoIsEqual(newKey) {
    +					if k.Identity == 0 || k.Identity == newKey.Identity {
    +						// If the iterated-deny-entry is a datapath superset (or
    +						// equal) of the new-entry and has a broader (or equal)
    +						// port-protocol than the new-entry then the new entry
    +						// should not be inserted.
    +						return
    +					} else if identityIsSupersetOf(k.Identity, newKey.Identity, identities) {
    +						// if newKey is not bailed due to being covered in the
    +						// datapath by a deny entry, but is covered by a deny entry
    +						// in the CIDR sense, we must change this allow entry to a
    +						// deny entry so that the covering deny policy is honored
    +						// also for this ID in the datapath.
    +						if !insertAsDeny {
    +							insertAsDeny = true
    +							denyEntry = NewMapStateEntry(k, v.DerivedFromRules, false, true, DefaultAuthType, AuthTypeDisabled)
    +						} else {
    +							// Collect the owners and labels of all the contributing deny rules
    +							denyEntry.Merge(&v)
    +						}
    +					}
    +				} else if k.Identity != 0 {
    +					// newKey.PortProtoIsBroader(k) as per previous if statements
    +					//
    +					// New L3/4 deny entry is not needed if the iterated key has wildcard L3,
    +					// as in that case it has precedence due to it having more specific L4.
    +					// So here we are only concerned about 'k' having a superset identity in
    +					// the CIDR sense, in which case a new L3/4 deny entry is needed.
    +					if identityIsSupersetOf(k.Identity, newKey.Identity, identities) {
    +						// If the new-entry is a subset of the iterated-deny-entry
    +						// and the new-entry has a less specific port-protocol than
    +						// the iterated-deny-entry then an additional copy of the
    +						// iterated-deny-entry with the identity of the new-entry
    +						// must be added.
    +						denyKeyCpy := k
    +						denyKeyCpy.Identity = newKey.Identity
    +						l3l4DenyEntry := NewMapStateEntry(k, v.DerivedFromRules, false, true, DefaultAuthType, AuthTypeDisabled)
    +						keys.addKeyWithChanges(denyKeyCpy, l3l4DenyEntry, changes)
    +						// L3-only entries can be deleted incrementally so we need
    +						// to track their effects on other entries so that those
    +						// effects can be reverted when the identity is removed.
    +						keys.addDependentOnEntry(k, v, denyKeyCpy, changes)
    +					}
     				}
     			}
     		}
    +		if insertAsDeny {
    +			newEntry = denyEntry
    +		}
     		keys.authPreferredInsert(newKey, newEntry, features, changes)
     	}
     }
    
  • pkg/policy/mapstate_test.go+60 38 modified
    @@ -2256,10 +2256,22 @@ func (ds *PolicyTestSuite) TestMapState_AccumulateMapChangesOnVisibilityKeys(c *
     	}
     }
     
    +func (e MapStateEntry) asDeny(denyKey Key) MapStateEntry {
    +	e.IsDeny = true
    +	e.ProxyPort = 0
    +	e.hasAuthType = DefaultAuthType
    +	e.AuthType = AuthTypeDisabled
    +	if e.owners == nil {
    +		e.owners = make(map[MapStateOwner]struct{})
    +	}
    +	e.owners[denyKey] = struct{}{}
    +	return e
    +}
    +
     func (ds *PolicyTestSuite) TestMapState_denyPreferredInsertWithSubnets(c *check.C) {
     	identityCache := cache.IdentityCache{
     		identity.ReservedIdentityWorld: labels.LabelWorld.LabelArray(),
    -		worldIPIdentity:                lblWorldIP,                  // "192.0.2.3/32"
    +		worldIPIdentity:                lblWorldIP.LabelArray(),     // "192.0.2.3/32"
     		worldSubnetIdentity:            lblWorldSubnet.LabelArray(), // "192.0.2.0/24"
     	}
     
    @@ -2275,9 +2287,9 @@ func (ds *PolicyTestSuite) TestMapState_denyPreferredInsertWithSubnets(c *check.
     		insertAWithBProto
     		insertBWithAProto
     
    -		insertBoth            = insertA | insertB
    -		canDeleteAInsertsBoth = insertBoth
    -		canDeleteBInsertsBoth = insertBoth
    +		insertBWithAProtoAsDeny
    +		insertBasDeny
    +		insertBoth = insertA | insertB
     	)
     	// these tests are based on the sheet https://docs.google.com/spreadsheets/d/1WANIoZGB48nryylQjjOw6lKjI80eVgPShrdMTMalLEw#gid=2109052536
     	tests := []struct {
    @@ -2291,46 +2303,48 @@ func (ds *PolicyTestSuite) TestMapState_denyPreferredInsertWithSubnets(c *check.
     		outcome              action
     	}{
     		// deny-allow insertions
    -		{"deny-allow: a superset a|b L3-only", reservedWorldID, worldSubnetID, true, false, 0, 0, 0, 0, insertA},
    +		{"deny-allow: a superset a|b L3-only", reservedWorldID, worldSubnetID, true, false, 0, 0, 0, 0, insertA | insertBasDeny},
     		{"deny-allow: b superset a|b L3-only", worldIPID, worldSubnetID, true, false, 0, 0, 0, 0, insertBoth},
    -		{"deny-allow: a superset a L3-only, b L4", reservedWorldID, worldSubnetID, true, false, 0, 0, 0, 6, insertA},
    +		{"deny-allow: a superset a L3-only, b L4", reservedWorldID, worldSubnetID, true, false, 0, 0, 0, 6, insertA | insertBasDeny},
     		{"deny-allow: b superset a L3-only, b L4", worldIPID, worldSubnetID, true, false, 0, 0, 0, 6, insertBoth | insertAWithBProto},
    -		{"deny-allow: a superset a L3-only, b L3L4", reservedWorldID, worldSubnetID, true, false, 0, 0, 80, 6, insertA},
    +		{"deny-allow: a superset a L3-only, b L3L4", reservedWorldID, worldSubnetID, true, false, 0, 0, 80, 6, insertA | insertBasDeny},
     		{"deny-allow: b superset a L3-only, b L3L4", worldIPID, worldSubnetID, true, false, 0, 0, 80, 6, insertBoth | insertAWithBProto},
    -		{"deny-allow: a superset a L4, b L3-only", reservedWorldID, worldSubnetID, true, false, 0, 6, 0, 0, insertBoth},
    +		{"deny-allow: a superset a L4, b L3-only", reservedWorldID, worldSubnetID, true, false, 0, 6, 0, 0, insertBoth | insertBWithAProtoAsDeny},
     		{"deny-allow: b superset a L4, b L3-only", worldIPID, worldSubnetID, true, false, 0, 6, 0, 0, insertBoth},
    -		{"deny-allow: a superset a L4, b L4", reservedWorldID, worldSubnetID, true, false, 0, 6, 0, 6, insertA},
    +		{"deny-allow: a superset a L4, b L4", reservedWorldID, worldSubnetID, true, false, 0, 6, 0, 6, insertA | insertBasDeny},
     		{"deny-allow: b superset a L4, b L4", worldIPID, worldSubnetID, true, false, 0, 6, 0, 6, insertBoth},
    -		{"deny-allow: a superset a L4, b L3L4", reservedWorldID, worldSubnetID, true, false, 0, 6, 80, 6, insertA},
    +		{"deny-allow: a superset a L4, b L3L4", reservedWorldID, worldSubnetID, true, false, 0, 6, 80, 6, insertA | insertBasDeny},
     		{"deny-allow: b superset a L4, b L3L4", worldIPID, worldSubnetID, true, false, 0, 6, 80, 6, insertBoth | insertAWithBProto},
    -		{"deny-allow: a superset a L3L4, b L3-only", reservedWorldID, worldSubnetID, true, false, 80, 6, 0, 0, insertBoth},
    +		{"deny-allow: a superset a L3L4, b L3-only", reservedWorldID, worldSubnetID, true, false, 80, 6, 0, 0, insertBoth | insertBWithAProtoAsDeny},
     		{"deny-allow: b superset a L3L4, b L3-only", worldIPID, worldSubnetID, true, false, 80, 6, 0, 0, insertBoth},
    -		{"deny-allow: a superset a L3L4, b L4", reservedWorldID, worldSubnetID, true, false, 80, 6, 0, 6, insertBoth},
    +		{"deny-allow: a superset a L3L4, b L4", reservedWorldID, worldSubnetID, true, false, 80, 6, 0, 6, insertBoth | insertBWithAProtoAsDeny},
     		{"deny-allow: b superset a L3L4, b L4", worldIPID, worldSubnetID, true, false, 80, 6, 0, 6, insertBoth},
    -		{"deny-allow: a superset a L3L4, b L3L4", reservedWorldID, worldSubnetID, true, false, 80, 6, 80, 6, insertA},
    +		{"deny-allow: a superset a L3L4, b L3L4", reservedWorldID, worldSubnetID, true, false, 80, 6, 80, 6, insertA | insertBasDeny},
     		{"deny-allow: b superset a L3L4, b L3L4", worldIPID, worldSubnetID, true, false, 80, 6, 80, 6, insertBoth},
     
    -		// deny-deny insertions: Note: We do not delete all redundant deny-deny insertions that we could.
    -		// We only delete entries redundant to L3-only port protocols, all other port-protocol supersets
    -		// *do not* have this effect.
    -		{"deny-deny: a superset a|b L3-only", worldSubnetID, worldIPID, true, true, 0, 0, 0, 0, insertA},
    -		{"deny-deny: b superset a|b L3-only", worldSubnetID, reservedWorldID, true, true, 0, 0, 0, 0, insertB},
    -		{"deny-deny: a superset a L3-only, b L4", worldSubnetID, worldIPID, true, true, 0, 0, 0, 6, insertA},
    +		// deny-deny insertions: Note: There is no dedundancy between different non-zero security IDs on the
    +		// datapath, even if one would be a CIDR subset of another. Situation would be different if we could
    +		// completely remove (or not add in the first place) the redundant ID from the ipcache so that
    +		// datapath could never assign that ID to a packet for policy enforcement.
    +		// These test case are left here for such future improvement.
    +		{"deny-deny: a superset a|b L3-only", worldSubnetID, worldIPID, true, true, 0, 0, 0, 0, insertBoth},
    +		{"deny-deny: b superset a|b L3-only", worldSubnetID, reservedWorldID, true, true, 0, 0, 0, 0, insertBoth},
    +		{"deny-deny: a superset a L3-only, b L4", worldSubnetID, worldIPID, true, true, 0, 0, 0, 6, insertBoth},
     		{"deny-deny: b superset a L3-only, b L4", worldSubnetID, reservedWorldID, true, true, 0, 0, 0, 6, insertBoth},
    -		{"deny-deny: a superset a L3-only, b L3L4", worldSubnetID, worldIPID, true, true, 0, 0, 80, 6, insertA},
    +		{"deny-deny: a superset a L3-only, b L3L4", worldSubnetID, worldIPID, true, true, 0, 0, 80, 6, insertBoth},
     		{"deny-deny: b superset a L3-only, b L3L4", worldSubnetID, reservedWorldID, true, true, 0, 0, 80, 6, insertBoth},
     		{"deny-deny: a superset a L4, b L3-only", worldSubnetID, worldIPID, true, true, 0, 6, 0, 0, insertBoth},
    -		{"deny-deny: b superset a L4, b L3-only", worldSubnetID, reservedWorldID, true, true, 0, 6, 0, 0, insertB},
    -		{"deny-deny: a superset a L4, b L4", worldSubnetID, worldIPID, true, true, 0, 6, 0, 6, canDeleteBInsertsBoth},
    -		{"deny-deny: b superset a L4, b L4", worldSubnetID, reservedWorldID, true, true, 0, 6, 0, 6, canDeleteAInsertsBoth},
    -		{"deny-deny: a superset a L4, b L3L4", worldSubnetID, worldIPID, true, true, 0, 6, 80, 6, canDeleteBInsertsBoth},
    +		{"deny-deny: b superset a L4, b L3-only", worldSubnetID, reservedWorldID, true, true, 0, 6, 0, 0, insertBoth},
    +		{"deny-deny: a superset a L4, b L4", worldSubnetID, worldIPID, true, true, 0, 6, 0, 6, insertBoth},
    +		{"deny-deny: b superset a L4, b L4", worldSubnetID, reservedWorldID, true, true, 0, 6, 0, 6, insertBoth},
    +		{"deny-deny: a superset a L4, b L3L4", worldSubnetID, worldIPID, true, true, 0, 6, 80, 6, insertBoth},
     		{"deny-deny: b superset a L4, b L3L4", worldSubnetID, reservedWorldID, true, true, 0, 6, 80, 6, insertBoth},
     		{"deny-deny: a superset a L3L4, b L3-only", worldSubnetID, worldIPID, true, true, 80, 6, 0, 0, insertBoth},
    -		{"deny-deny: b superset a L3L4, b L3-only", worldSubnetID, reservedWorldID, true, true, 80, 6, 0, 0, insertB},
    +		{"deny-deny: b superset a L3L4, b L3-only", worldSubnetID, reservedWorldID, true, true, 80, 6, 0, 0, insertBoth},
     		{"deny-deny: a superset a L3L4, b L4", worldSubnetID, worldIPID, true, true, 80, 6, 0, 6, insertBoth},
    -		{"deny-deny: b superset a L3L4, b L4", worldSubnetID, reservedWorldID, true, true, 80, 6, 0, 6, canDeleteAInsertsBoth},
    -		{"deny-deny: a superset a L3L4, b L3L4", worldSubnetID, worldIPID, true, true, 80, 6, 80, 6, canDeleteBInsertsBoth},
    -		{"deny-deny: b superset a L3L4, b L3L4", worldSubnetID, reservedWorldID, true, true, 80, 6, 80, 6, canDeleteAInsertsBoth},
    +		{"deny-deny: b superset a L3L4, b L4", worldSubnetID, reservedWorldID, true, true, 80, 6, 0, 6, insertBoth},
    +		{"deny-deny: a superset a L3L4, b L3L4", worldSubnetID, worldIPID, true, true, 80, 6, 80, 6, insertBoth},
    +		{"deny-deny: b superset a L3L4, b L3L4", worldSubnetID, reservedWorldID, true, true, 80, 6, 80, 6, insertBoth},
     		// allow-allow insertions do not need to be tests as they will all be inserted
     	}
     	for _, tt := range tests {
    @@ -2345,21 +2359,29 @@ func (ds *PolicyTestSuite) TestMapState_denyPreferredInsertWithSubnets(c *check.
     		if tt.outcome&insertB > 0 {
     			expectedKeys[bKey] = bEntry
     		}
    +		if tt.outcome&insertBasDeny > 0 {
    +			expectedKeys[bKey] = bEntry.asDeny(aKey)
    +		}
     		if tt.outcome&insertAWithBProto > 0 {
     			aKeyWithBProto := Key{Identity: tt.aIdentity, DestPort: tt.bPort, Nexthdr: tt.bProto}
    -			aEntryCpy := MapStateEntry{IsDeny: tt.aIsDeny}
    -			aEntryCpy.owners = map[MapStateOwner]struct{}{aKey: {}}
    -			aEntry.AddDependent(aKeyWithBProto)
    -			expectedKeys[aKey] = aEntry
    +			aEntryCpy := aEntry.WithOwners(aKey)
    +			aEntryWithDep := aEntry.WithDependents(aKeyWithBProto)
    +			expectedKeys[aKey] = aEntryWithDep
     			expectedKeys[aKeyWithBProto] = aEntryCpy
     		}
     		if tt.outcome&insertBWithAProto > 0 {
    -			bKeyWithBProto := Key{Identity: tt.bIdentity, DestPort: tt.aPort, Nexthdr: tt.aProto}
    -			bEntryCpy := MapStateEntry{IsDeny: tt.bIsDeny}
    -			bEntryCpy.owners = map[MapStateOwner]struct{}{bKey: {}}
    -			bEntry.AddDependent(bKeyWithBProto)
    -			expectedKeys[bKey] = bEntry
    -			expectedKeys[bKeyWithBProto] = bEntryCpy
    +			bKeyWithAProto := Key{tt.bIdentity, tt.aPort, tt.aProto, 0}
    +			bEntryCpy := bEntry.WithOwners(bKey)
    +			bEntryWithDep := bEntry.WithDependents(bKeyWithAProto)
    +			expectedKeys[bKey] = bEntryWithDep
    +			expectedKeys[bKeyWithAProto] = bEntryCpy
    +		}
    +		if tt.outcome&insertBWithAProtoAsDeny > 0 {
    +			bKeyWithAProto := Key{tt.bIdentity, tt.aPort, tt.aProto, 0}
    +			bEntryAsDeny := bEntry.WithOwners(aKey).asDeny(aKey)
    +			aEntryWithDep := aEntry.WithDependents(bKeyWithAProto)
    +			expectedKeys[aKey] = aEntryWithDep
    +			expectedKeys[bKeyWithAProto] = bEntryAsDeny
     		}
     		outcomeKeys := MapState{}
     		outcomeKeys.denyPreferredInsert(aKey, aEntry, selectorCache, allFeatures)
    
02d28d9ac9af

policy: Fix broad deny with except bug

https://github.com/cilium/ciliumChris TaraziAug 1, 2024via ghsa
3 files changed · +221 90
  • pkg/policy/distillery_test.go+31 8 modified
    @@ -1399,7 +1399,7 @@ var (
     
     	worldIPIdentity = localIdentity(16324)
     	worldCIDR       = api.CIDR("192.0.2.3/32")
    -	lblWorldIP      = labels.ParseSelectLabelArray(fmt.Sprintf("%s:%s", labels.LabelSourceCIDR, worldCIDR))
    +	lblWorldIP      = labels.GetCIDRLabels(netip.MustParsePrefix(string(worldCIDR)))
     
     	worldIPIdentity2         = localIdentity(16326)
     	worldCIDR2               = api.CIDR("192.0.2.4/32")
    @@ -1541,6 +1541,13 @@ var (
     	mapKeyL3L4Port8080ProtoSCTPWorldSNIngress = Key{worldSubnetIdentity.Uint32(), 8080, 132, trafficdirection.Ingress.Uint8()}
     	mapKeyL3L4Port8080ProtoSCTPWorldSNEgress  = Key{worldSubnetIdentity.Uint32(), 8080, 132, trafficdirection.Egress.Uint8()}
     
    +	mapKeyL3L4Port8080ProtoTCPWorldIPIngress  = Key{worldIPIdentity.Uint32(), 8080, 6, trafficdirection.Ingress.Uint8()}
    +	mapKeyL3L4Port8080ProtoTCPWorldIPEgress   = Key{worldIPIdentity.Uint32(), 8080, 6, trafficdirection.Egress.Uint8()}
    +	mapKeyL3L4Port8080ProtoUDPWorldIPIngress  = Key{worldIPIdentity.Uint32(), 8080, 17, trafficdirection.Ingress.Uint8()}
    +	mapKeyL3L4Port8080ProtoUDPWorldIPEgress   = Key{worldIPIdentity.Uint32(), 8080, 17, trafficdirection.Egress.Uint8()}
    +	mapKeyL3L4Port8080ProtoSCTPWorldIPIngress = Key{worldIPIdentity.Uint32(), 8080, 132, trafficdirection.Ingress.Uint8()}
    +	mapKeyL3L4Port8080ProtoSCTPWorldIPEgress  = Key{worldIPIdentity.Uint32(), 8080, 132, trafficdirection.Egress.Uint8()}
    +
     	ruleL3AllowWorldSubnet = api.NewRule().WithIngressRules([]api.IngressRule{{
     		ToPorts: api.PortRules{
     			api.PortRule{
    @@ -1624,7 +1631,7 @@ func Test_EnsureDeniesPrecedeAllows(t *testing.T) {
     		return cache.IdentityCache{
     			identity.NumericIdentity(identityFoo): labelsFoo,
     			identity.ReservedIdentityWorld:        labels.LabelWorld.LabelArray(),
    -			worldIPIdentity:                       lblWorldIP,                  // "192.0.2.3/32"
    +			worldIPIdentity:                       lblWorldIP.LabelArray(),     // "192.0.2.3/32"
     			worldSubnetIdentity:                   lblWorldSubnet.LabelArray(), // "192.0.2.0/24"
     		}
     	}
    @@ -1667,14 +1674,24 @@ func Test_EnsureDeniesPrecedeAllows(t *testing.T) {
     		result   MapState
     	}{
     		{"deny_world_no_labels", api.Rules{ruleL3DenyWorld, ruleL3AllowWorldIP}, defaultIDs, newMapState(map[Key]MapStateEntry{
    -			mapKeyL3WorldIngress: mapEntryDeny,
    -			mapKeyL3WorldEgress:  mapEntryDeny,
    +			mapKeyL3WorldIngress:         mapEntryDeny,
    +			mapKeyL3WorldEgress:          mapEntryDeny,
    +			mapKeyL3SubnetIngress:        mapEntryDeny,
    +			mapKeyL3SubnetEgress:         mapEntryDeny,
    +			mapKeyL3SmallerSubnetIngress: mapEntryDeny,
    +			mapKeyL3SmallerSubnetEgress:  mapEntryDeny,
     		})}, {"deny_world_with_labels", api.Rules{ruleL3DenyWorldWithLabels, ruleL3AllowWorldIP}, defaultIDs, newMapState(map[Key]MapStateEntry{
    -			mapKeyL3WorldIngress: mapEntryWorldDenyWithLabels,
    -			mapKeyL3WorldEgress:  mapEntryWorldDenyWithLabels,
    +			mapKeyL3WorldIngress:         mapEntryWorldDenyWithLabels,
    +			mapKeyL3WorldEgress:          mapEntryWorldDenyWithLabels,
    +			mapKeyL3SubnetIngress:        mapEntryDeny,
    +			mapKeyL3SubnetEgress:         mapEntryDeny,
    +			mapKeyL3SmallerSubnetIngress: mapEntryDeny,
    +			mapKeyL3SmallerSubnetEgress:  mapEntryDeny,
     		})}, {"deny_one_ip_with_a_larger_subnet", api.Rules{ruleL3DenySubnet, ruleL3AllowWorldIP}, defaultIDs, newMapState(map[Key]MapStateEntry{
    -			mapKeyL3SubnetIngress: mapEntryDeny,
    -			mapKeyL3SubnetEgress:  mapEntryDeny,
    +			mapKeyL3SubnetIngress:        mapEntryDeny,
    +			mapKeyL3SubnetEgress:         mapEntryDeny,
    +			mapKeyL3SmallerSubnetIngress: mapEntryDeny,
    +			mapKeyL3SmallerSubnetEgress:  mapEntryDeny,
     		})}, {"deny_part_of_a_subnet_with_an_ip", api.Rules{ruleL3DenySmallerSubnet, ruleL3AllowLargerSubnet}, defaultIDs, newMapState(map[Key]MapStateEntry{
     			mapKeyL3SmallerSubnetIngress: mapEntryDeny,
     			mapKeyL3SmallerSubnetEgress:  mapEntryDeny,
    @@ -1710,6 +1727,12 @@ func Test_EnsureDeniesPrecedeAllows(t *testing.T) {
     			mapKeyL3L4Port8080ProtoUDPWorldSNEgress:   mapEntryDeny,
     			mapKeyL3L4Port8080ProtoSCTPWorldSNIngress: mapEntryDeny,
     			mapKeyL3L4Port8080ProtoSCTPWorldSNEgress:  mapEntryDeny,
    +			mapKeyL3L4Port8080ProtoTCPWorldIPIngress:  mapEntryDeny,
    +			mapKeyL3L4Port8080ProtoTCPWorldIPEgress:   mapEntryDeny,
    +			mapKeyL3L4Port8080ProtoUDPWorldIPIngress:  mapEntryDeny,
    +			mapKeyL3L4Port8080ProtoUDPWorldIPEgress:   mapEntryDeny,
    +			mapKeyL3L4Port8080ProtoSCTPWorldIPIngress: mapEntryDeny,
    +			mapKeyL3L4Port8080ProtoSCTPWorldIPEgress:  mapEntryDeny,
     			mapKeyL3SmallerSubnetIngress:              mapEntryAllow,
     			mapKeyL3SmallerSubnetEgress:               mapEntryAllow,
     		})}, {"broad_allow_is_a_portproto_subset_of_a_specific_deny", api.Rules{ruleL3AllowWorldSubnet, ruleL3DenyWorldIP}, defaultIDs, newMapState(map[Key]MapStateEntry{
    
  • pkg/policy/mapstate.go+123 41 modified
    @@ -234,6 +234,21 @@ func (e *MapStateEntry) HasDependent(key Key) bool {
     	return ok
     }
     
    +// HasSameOwners returns true if both MapStateEntries
    +// have the same owners as one another.
    +// MapStateEntries are stored by value, so we do not check for nil pointers here.
    +func (e *MapStateEntry) HasSameOwners(bEntry *MapStateEntry) bool {
    +	if len(e.owners) != len(bEntry.owners) {
    +		return false
    +	}
    +	for owner := range e.owners {
    +		if _, ok := bEntry.owners[owner]; !ok {
    +			return false
    +		}
    +	}
    +	return true
    +}
    +
     var worldNets = map[identity.NumericIdentity][]*net.IPNet{
     	identity.ReservedIdentityWorld: {
     		{IP: net.IPv4zero, Mask: net.CIDRMask(0, net.IPv4len*8)},
    @@ -778,13 +793,41 @@ func (ms *mapState) denyPreferredInsertWithChanges(newKey Key, newEntry MapState
     					// identity is removed.
     					newEntry.AddDependent(newKeyCpy)
     				}
    -			} else if (newKey.Identity == k.Identity ||
    -				identityIsSupersetOf(newKey.Identity, k.Identity, identities)) &&
    -				(newKey.PortProtoIsBroader(k) || newKey.PortProtoIsEqual(k)) {
    -				// If the new-entry is a superset (or equal) of the iterated-allow-entry and
    -				// the new-entry has a broader (or equal) port-protocol then we
    -				// should delete the iterated-allow-entry
    -				ms.deleteKeyWithChanges(k, nil, changes)
    +			} else if newKey.PortProtoIsBroader(k) || newKey.PortProtoIsEqual(k) {
    +				// If newKey has a broader (or equal) port-protocol then we should
    +				// either delete the iterated-allow-entry (if the identity is the
    +				// same or the newKey is L3 wildcard), or change it to a deny entry
    +				// if the newKey's identity is a superset of the iterated identity
    +				// (e.g., newKey has a wider CIDR (say 10/8 covering the iterated
    +				// identity of more specific CIDR (say 10.1.1.1). Note that the
    +				// security identities assigned to these CIDRs have no numerical
    +				// relation to each other (e.g, they could be any numbers X and Y)
    +				// and the datapath does an exact match on them.
    +				if newKey.Identity == 0 || newKey.Identity == k.Identity {
    +					ms.deleteKeyWithChanges(k, nil, changes)
    +				} else if identityIsSupersetOf(newKey.Identity, k.Identity, identities) {
    +					// When newKey.Identity is not ANY and is different from the
    +					// subset key, but still a superset (e.g., in CIDR sense) we
    +					// must keep the subset key and make it a deny instead.
    +					l3l4DenyEntry := NewMapStateEntry(newKey, newEntry.DerivedFromRules, 0, "", 0, true, DefaultAuthType, AuthTypeDisabled)
    +					ms.addKeyWithChanges(k, l3l4DenyEntry, changes)
    +					newEntry.AddDependent(k)
    +				}
    +			} else if identityIsSupersetOf(newKey.Identity, k.Identity, identities) {
    +				// k.PortProtoIsBroader(newKey) // due to if statements above
    +
    +				// Deny takes precedence for the port/proto of the newKey
    +				// for each allow with broader port/proto and narrower ID.
    +
    +				// If newKey is a superset of the iterated allow key and newKey has
    +				// a less specific port-protocol than the iterated allow key then an
    +				// additional deny entry with port/proto of newKey and with the
    +				// identity of the iterated allow key must be added.
    +				denyKeyCpy := newKey
    +				denyKeyCpy.Identity = k.Identity
    +				l3l4DenyEntry := NewMapStateEntry(newKey, newEntry.DerivedFromRules, 0, "", 0, true, DefaultAuthType, AuthTypeDisabled)
    +				ms.addKeyWithChanges(denyKeyCpy, l3l4DenyEntry, changes)
    +				newEntry.AddDependent(denyKeyCpy)
     			}
     			return true
     		})
    @@ -797,32 +840,39 @@ func (ms *mapState) denyPreferredInsertWithChanges(newKey Key, newEntry MapState
     				return true
     			}
     
    -			if (newKey.Identity == k.Identity ||
    -				identityIsSupersetOf(k.Identity, newKey.Identity, identities)) &&
    -				k.DestPort == 0 && k.Nexthdr == 0 &&
    -				!v.HasDependent(newKey) {
    -				// If this iterated-deny-entry is a supserset (or equal) of the new-entry and
    -				// the iterated-deny-entry is an L3-only policy then we
    -				// should not insert the new entry (as long as it is not one
    -				// of the special L4-only denies we created to cover the special
    -				// case of a superset-allow with a more specific port-protocol).
    -				//
    -				// NOTE: This condition could be broader to reject more deny entries,
    -				// but there *may* be performance tradeoffs.
    -				bailed = true
    -				return false
    -			} else if (newKey.Identity == k.Identity ||
    -				identityIsSupersetOf(newKey.Identity, k.Identity, identities)) &&
    -				newKey.DestPort == 0 && newKey.Nexthdr == 0 &&
    +			// A narrower of two deny keys is redundant in the datapath only if
    +			// the broader ID is 0, or the IDs are the same. This is because the
    +			// ID will be assigned from the ipcache and datapath has no notion
    +			// of one ID being related to another (e.g., in a CIDR sense).
    +			if (k.Identity == 0 || k.Identity == newKey.Identity) &&
    +				(k.PortProtoIsEqual(newKey) || k.PortProtoIsBroader(newKey)) {
    +				// If this iterated-deny-entry is an deny-all-L3 or has the same ID
    +				// as the new-entry and the iterated-deny-entry has a broader (or
    +				// equal) port-protocol it will match all the packets the newKey
    +				// would, given that we do not allow more specific allow rules to be
    +				// inserted.
    +
    +				// Identical key needs to be added if owners are different (to merge
    +				// them). This has no effect on the datapath policy map but is
    +				// needed for internal bookkeeping.
    +				if k != newKey || v.HasSameOwners(&newEntry) {
    +					bailed = true
    +					return false
    +				}
    +			} else if (newKey.Identity == 0 || newKey.Identity == k.Identity) &&
    +				(newKey.PortProtoIsEqual(k) || newKey.PortProtoIsBroader(k)) &&
     				!newEntry.HasDependent(k) {
    -				// If this iterated-deny-entry is a subset (or equal) of the new-entry and
    -				// the new-entry is an L3-only policy then we
    -				// should delete the iterated-deny-entry (as long as it is not one
    -				// of the special L4-only denies we created to cover the special
    -				// case of a superset-allow with a more specific port-protocol).
    -				//
    -				// NOTE: This condition could be broader to reject more deny entries,
    -				// but there *may* be performance tradeoffs.
    +				// If this iterated-deny-entry is a subset (or equal) of the
    +				// new-entry and the new-entry has a broader (or equal)
    +				// port-protocol the newKey will match all the packets the iterated
    +				// key would, given that there are no more specific or L4-only allow
    +				// entries. We removed the more specific allow rules in the loop
    +				// above, and added more specific deny rules if there was an L4-only
    +				// allow rule. We use 'HasDependant' to figure out that 'k' must
    +				// remain to take precedence over the L4-only allow key.
    +
    +				// Identical key would have been captured in the block above, so we
    +				// do not need to check for it here.
     				ms.deleteKeyWithChanges(k, nil, changes)
     			}
     			return true
    @@ -834,6 +884,7 @@ func (ms *mapState) denyPreferredInsertWithChanges(newKey Key, newEntry MapState
     	} else {
     		// NOTE: We do not delete redundant allow entries.
     		bailed := false
    +		changeToDeny := false
     		ms.ForEachDeny(func(k Key, v MapStateEntry) bool {
     			// Protocols and traffic directions that don't match ensure that the policies
     			// do not interact in anyway.
    @@ -857,20 +908,51 @@ func (ms *mapState) denyPreferredInsertWithChanges(newKey Key, newEntry MapState
     					// identity is removed.
     					ms.addDependentOnEntry(k, v, denyKeyCpy, changes)
     				}
    -			} else if (k.Identity == newKey.Identity ||
    -				identityIsSupersetOf(k.Identity, newKey.Identity, identities)) &&
    -				(k.PortProtoIsBroader(newKey) || k.PortProtoIsEqual(newKey)) &&
    -				!v.HasDependent(newKey) {
    -				// If the iterated-deny-entry is a superset (or equal) of the new-entry and has a
    -				// broader (or equal) port-protocol than the new-entry then the new
    -				// entry should not be inserted.
    -				bailed = true
    -				return false
    +			} else if k.PortProtoIsBroader(newKey) || k.PortProtoIsEqual(newKey) {
    +				if k.Identity == 0 || k.Identity == newKey.Identity {
    +					// If the iterated-deny-entry is a datapath superset (or
    +					// equal) of the new-entry and has a broader (or equal)
    +					// port-protocol than the new-entry then the new entry
    +					// should not be inserted.
    +					bailed = true
    +					return false
    +				} else if identityIsSupersetOf(k.Identity, newKey.Identity, identities) {
    +					// if newKey is not bailed due to being covered in the
    +					// datapath by a deny entry, but is covered by a deny entry
    +					// in the CIDR sense, we must change this allow entry to a
    +					// deny entry so that the covering deny policy is honored
    +					// also for this ID in the datapath.
    +					changeToDeny = true
    +				}
    +			} else { // newKey.PortProtoIsBroader(k)
    +				if identityIsSupersetOf(k.Identity, newKey.Identity, identities) {
    +					// If the new-entry is a subset of the iterated-deny-entry
    +					// and the new-entry has a less specific port-protocol than
    +					// the iterated-deny-entry then an additional copy of the
    +					// iterated-deny-entry with the identity of the new-entry
    +					// must be added.
    +					denyKeyCpy := k
    +					denyKeyCpy.Identity = newKey.Identity
    +					l3l4DenyEntry := NewMapStateEntry(k, v.DerivedFromRules, 0, "", 0, true, DefaultAuthType, AuthTypeDisabled)
    +					ms.addKeyWithChanges(denyKeyCpy, l3l4DenyEntry, changes)
    +					// L3-only entries can be deleted incrementally so we need
    +					// to track their effects on other entries so that those
    +					// effects can be reverted when the identity is removed.
    +					ms.addDependentOnEntry(k, v, denyKeyCpy, changes)
    +				}
     			}
     
     			return true
     		})
     
    +		if changeToDeny {
    +			newEntry.IsDeny = true
    +			newEntry.ProxyPort = 0
    +			newEntry.Listener = ""
    +			newEntry.priority = 0
    +			newEntry.hasAuthType = DefaultAuthType
    +			newEntry.AuthType = AuthTypeDisabled
    +		}
     		if !bailed {
     			ms.authPreferredInsert(newKey, newEntry, features, changes)
     		}
    
  • pkg/policy/mapstate_test.go+67 41 modified
    @@ -2265,10 +2265,22 @@ func (ds *PolicyTestSuite) TestMapState_AccumulateMapChangesOnVisibilityKeys(c *
     	}
     }
     
    +func (e MapStateEntry) asDeny() MapStateEntry {
    +	if !e.IsDeny {
    +		e.IsDeny = true
    +		e.ProxyPort = 0
    +		e.Listener = ""
    +		e.priority = 0
    +		e.hasAuthType = DefaultAuthType
    +		e.AuthType = AuthTypeDisabled
    +	}
    +	return e
    +}
    +
     func (ds *PolicyTestSuite) TestMapState_denyPreferredInsertWithSubnets(c *check.C) {
     	identityCache := cache.IdentityCache{
     		identity.ReservedIdentityWorld: labels.LabelWorld.LabelArray(),
    -		worldIPIdentity:                lblWorldIP,                  // "192.0.2.3/32"
    +		worldIPIdentity:                lblWorldIP.LabelArray(),     // "192.0.2.3/32"
     		worldSubnetIdentity:            lblWorldSubnet.LabelArray(), // "192.0.2.0/24"
     	}
     
    @@ -2284,9 +2296,10 @@ func (ds *PolicyTestSuite) TestMapState_denyPreferredInsertWithSubnets(c *check.
     		insertAWithBProto
     		insertBWithAProto
     
    -		insertBoth            = insertA | insertB
    -		canDeleteAInsertsBoth = insertBoth
    -		canDeleteBInsertsBoth = insertBoth
    +		insertBWithAProtoAsDeny
    +		insertAasDeny
    +		insertBasDeny
    +		insertBoth = insertA | insertB
     	)
     	// these tests are based on the sheet https://docs.google.com/spreadsheets/d/1WANIoZGB48nryylQjjOw6lKjI80eVgPShrdMTMalLEw#gid=2109052536
     	tests := []struct {
    @@ -2300,46 +2313,48 @@ func (ds *PolicyTestSuite) TestMapState_denyPreferredInsertWithSubnets(c *check.
     		outcome              action
     	}{
     		// deny-allow insertions
    -		{"deny-allow: a superset a|b L3-only", reservedWorldID, worldSubnetID, true, false, 0, 0, 0, 0, insertA},
    +		{"deny-allow: a superset a|b L3-only", reservedWorldID, worldSubnetID, true, false, 0, 0, 0, 0, insertA | insertBasDeny},
     		{"deny-allow: b superset a|b L3-only", worldIPID, worldSubnetID, true, false, 0, 0, 0, 0, insertBoth},
    -		{"deny-allow: a superset a L3-only, b L4", reservedWorldID, worldSubnetID, true, false, 0, 0, 0, 6, insertA},
    +		{"deny-allow: a superset a L3-only, b L4", reservedWorldID, worldSubnetID, true, false, 0, 0, 0, 6, insertA | insertBasDeny},
     		{"deny-allow: b superset a L3-only, b L4", worldIPID, worldSubnetID, true, false, 0, 0, 0, 6, insertBoth | insertAWithBProto},
    -		{"deny-allow: a superset a L3-only, b L3L4", reservedWorldID, worldSubnetID, true, false, 0, 0, 80, 6, insertA},
    +		{"deny-allow: a superset a L3-only, b L3L4", reservedWorldID, worldSubnetID, true, false, 0, 0, 80, 6, insertA | insertBasDeny},
     		{"deny-allow: b superset a L3-only, b L3L4", worldIPID, worldSubnetID, true, false, 0, 0, 80, 6, insertBoth | insertAWithBProto},
    -		{"deny-allow: a superset a L4, b L3-only", reservedWorldID, worldSubnetID, true, false, 0, 6, 0, 0, insertBoth},
    +		{"deny-allow: a superset a L4, b L3-only", reservedWorldID, worldSubnetID, true, false, 0, 6, 0, 0, insertBoth | insertBWithAProtoAsDeny},
     		{"deny-allow: b superset a L4, b L3-only", worldIPID, worldSubnetID, true, false, 0, 6, 0, 0, insertBoth},
    -		{"deny-allow: a superset a L4, b L4", reservedWorldID, worldSubnetID, true, false, 0, 6, 0, 6, insertA},
    +		{"deny-allow: a superset a L4, b L4", reservedWorldID, worldSubnetID, true, false, 0, 6, 0, 6, insertA | insertBasDeny},
     		{"deny-allow: b superset a L4, b L4", worldIPID, worldSubnetID, true, false, 0, 6, 0, 6, insertBoth},
    -		{"deny-allow: a superset a L4, b L3L4", reservedWorldID, worldSubnetID, true, false, 0, 6, 80, 6, insertA},
    +		{"deny-allow: a superset a L4, b L3L4", reservedWorldID, worldSubnetID, true, false, 0, 6, 80, 6, insertA | insertBasDeny},
     		{"deny-allow: b superset a L4, b L3L4", worldIPID, worldSubnetID, true, false, 0, 6, 80, 6, insertBoth | insertAWithBProto},
    -		{"deny-allow: a superset a L3L4, b L3-only", reservedWorldID, worldSubnetID, true, false, 80, 6, 0, 0, insertBoth},
    +		{"deny-allow: a superset a L3L4, b L3-only", reservedWorldID, worldSubnetID, true, false, 80, 6, 0, 0, insertBoth | insertBWithAProtoAsDeny},
     		{"deny-allow: b superset a L3L4, b L3-only", worldIPID, worldSubnetID, true, false, 80, 6, 0, 0, insertBoth},
    -		{"deny-allow: a superset a L3L4, b L4", reservedWorldID, worldSubnetID, true, false, 80, 6, 0, 6, insertBoth},
    +		{"deny-allow: a superset a L3L4, b L4", reservedWorldID, worldSubnetID, true, false, 80, 6, 0, 6, insertBoth | insertBWithAProtoAsDeny},
     		{"deny-allow: b superset a L3L4, b L4", worldIPID, worldSubnetID, true, false, 80, 6, 0, 6, insertBoth},
    -		{"deny-allow: a superset a L3L4, b L3L4", reservedWorldID, worldSubnetID, true, false, 80, 6, 80, 6, insertA},
    +		{"deny-allow: a superset a L3L4, b L3L4", reservedWorldID, worldSubnetID, true, false, 80, 6, 80, 6, insertA | insertBasDeny},
     		{"deny-allow: b superset a L3L4, b L3L4", worldIPID, worldSubnetID, true, false, 80, 6, 80, 6, insertBoth},
     
    -		// deny-deny insertions: Note: We do not delete all redundant deny-deny insertions that we could.
    -		// We only delete entries redundant to L3-only port protocols, all other port-protocol supersets
    -		// *do not* have this effect.
    -		{"deny-deny: a superset a|b L3-only", worldSubnetID, worldIPID, true, true, 0, 0, 0, 0, insertA},
    -		{"deny-deny: b superset a|b L3-only", worldSubnetID, reservedWorldID, true, true, 0, 0, 0, 0, insertB},
    -		{"deny-deny: a superset a L3-only, b L4", worldSubnetID, worldIPID, true, true, 0, 0, 0, 6, insertA},
    +		// deny-deny insertions: Note: There is no dedundancy between different non-zero security IDs on the
    +		// datapath, even if one would be a CIDR subset of another. Situation would be different if we could
    +		// completely remove (or not add in the first place) the redundant ID from the ipcache so that
    +		// datapath could never assign that ID to a packet for policy enforcement.
    +		// These test case are left here for such future improvement.
    +		{"deny-deny: a superset a|b L3-only", worldSubnetID, worldIPID, true, true, 0, 0, 0, 0, insertBoth},
    +		{"deny-deny: b superset a|b L3-only", worldSubnetID, reservedWorldID, true, true, 0, 0, 0, 0, insertBoth},
    +		{"deny-deny: a superset a L3-only, b L4", worldSubnetID, worldIPID, true, true, 0, 0, 0, 6, insertBoth},
     		{"deny-deny: b superset a L3-only, b L4", worldSubnetID, reservedWorldID, true, true, 0, 0, 0, 6, insertBoth},
    -		{"deny-deny: a superset a L3-only, b L3L4", worldSubnetID, worldIPID, true, true, 0, 0, 80, 6, insertA},
    +		{"deny-deny: a superset a L3-only, b L3L4", worldSubnetID, worldIPID, true, true, 0, 0, 80, 6, insertBoth},
     		{"deny-deny: b superset a L3-only, b L3L4", worldSubnetID, reservedWorldID, true, true, 0, 0, 80, 6, insertBoth},
     		{"deny-deny: a superset a L4, b L3-only", worldSubnetID, worldIPID, true, true, 0, 6, 0, 0, insertBoth},
    -		{"deny-deny: b superset a L4, b L3-only", worldSubnetID, reservedWorldID, true, true, 0, 6, 0, 0, insertB},
    -		{"deny-deny: a superset a L4, b L4", worldSubnetID, worldIPID, true, true, 0, 6, 0, 6, canDeleteBInsertsBoth},
    -		{"deny-deny: b superset a L4, b L4", worldSubnetID, reservedWorldID, true, true, 0, 6, 0, 6, canDeleteAInsertsBoth},
    -		{"deny-deny: a superset a L4, b L3L4", worldSubnetID, worldIPID, true, true, 0, 6, 80, 6, canDeleteBInsertsBoth},
    +		{"deny-deny: b superset a L4, b L3-only", worldSubnetID, reservedWorldID, true, true, 0, 6, 0, 0, insertBoth},
    +		{"deny-deny: a superset a L4, b L4", worldSubnetID, worldIPID, true, true, 0, 6, 0, 6, insertBoth},
    +		{"deny-deny: b superset a L4, b L4", worldSubnetID, reservedWorldID, true, true, 0, 6, 0, 6, insertBoth},
    +		{"deny-deny: a superset a L4, b L3L4", worldSubnetID, worldIPID, true, true, 0, 6, 80, 6, insertBoth},
     		{"deny-deny: b superset a L4, b L3L4", worldSubnetID, reservedWorldID, true, true, 0, 6, 80, 6, insertBoth},
     		{"deny-deny: a superset a L3L4, b L3-only", worldSubnetID, worldIPID, true, true, 80, 6, 0, 0, insertBoth},
    -		{"deny-deny: b superset a L3L4, b L3-only", worldSubnetID, reservedWorldID, true, true, 80, 6, 0, 0, insertB},
    +		{"deny-deny: b superset a L3L4, b L3-only", worldSubnetID, reservedWorldID, true, true, 80, 6, 0, 0, insertBoth},
     		{"deny-deny: a superset a L3L4, b L4", worldSubnetID, worldIPID, true, true, 80, 6, 0, 6, insertBoth},
    -		{"deny-deny: b superset a L3L4, b L4", worldSubnetID, reservedWorldID, true, true, 80, 6, 0, 6, canDeleteAInsertsBoth},
    -		{"deny-deny: a superset a L3L4, b L3L4", worldSubnetID, worldIPID, true, true, 80, 6, 80, 6, canDeleteBInsertsBoth},
    -		{"deny-deny: b superset a L3L4, b L3L4", worldSubnetID, reservedWorldID, true, true, 80, 6, 80, 6, canDeleteAInsertsBoth},
    +		{"deny-deny: b superset a L3L4, b L4", worldSubnetID, reservedWorldID, true, true, 80, 6, 0, 6, insertBoth},
    +		{"deny-deny: a superset a L3L4, b L3L4", worldSubnetID, worldIPID, true, true, 80, 6, 80, 6, insertBoth},
    +		{"deny-deny: b superset a L3L4, b L3L4", worldSubnetID, reservedWorldID, true, true, 80, 6, 80, 6, insertBoth},
     		// allow-allow insertions do not need to be tests as they will all be inserted
     	}
     	for _, tt := range tests {
    @@ -2355,39 +2370,50 @@ func (ds *PolicyTestSuite) TestMapState_denyPreferredInsertWithSubnets(c *check.
     				expectedKeys.allows[aKey] = aEntry
     			}
     		}
    +		if tt.outcome&insertAasDeny > 0 {
    +			expectedKeys.denies[aKey] = aEntry.asDeny()
    +		}
     		if tt.outcome&insertB > 0 {
     			if tt.bIsDeny {
     				expectedKeys.denies[bKey] = bEntry
     			} else {
     				expectedKeys.allows[bKey] = bEntry
     			}
     		}
    +		if tt.outcome&insertBasDeny > 0 {
    +			expectedKeys.denies[bKey] = bEntry.asDeny()
    +		}
     		if tt.outcome&insertAWithBProto > 0 {
     			aKeyWithBProto := Key{Identity: tt.aIdentity, DestPort: tt.bPort, Nexthdr: tt.bProto}
    -			aEntryCpy := MapStateEntry{IsDeny: tt.aIsDeny}
    -			aEntryCpy.owners = map[MapStateOwner]struct{}{aKey: {}}
    -			aEntry.AddDependent(aKeyWithBProto)
    +			aEntryCpy := aEntry.WithOwners(aKey)
    +			aEntryWithDep := aEntry.WithDependents(aKeyWithBProto)
     			if tt.aIsDeny {
    -				expectedKeys.denies[aKey] = aEntry
    +				expectedKeys.denies[aKey] = aEntryWithDep
     				expectedKeys.denies[aKeyWithBProto] = aEntryCpy
     			} else {
    -				expectedKeys.allows[aKey] = aEntry
    +				expectedKeys.allows[aKey] = aEntryWithDep
     				expectedKeys.allows[aKeyWithBProto] = aEntryCpy
     			}
     		}
     		if tt.outcome&insertBWithAProto > 0 {
    -			bKeyWithBProto := Key{Identity: tt.bIdentity, DestPort: tt.aPort, Nexthdr: tt.aProto}
    -			bEntryCpy := MapStateEntry{IsDeny: tt.bIsDeny}
    -			bEntryCpy.owners = map[MapStateOwner]struct{}{bKey: {}}
    -			bEntry.AddDependent(bKeyWithBProto)
    +			bKeyWithAProto := Key{tt.bIdentity, tt.aPort, tt.aProto, 0}
    +			bEntryCpy := bEntry.WithOwners(bKey)
    +			bEntryWithDep := bEntry.WithDependents(bKeyWithAProto)
     			if tt.bIsDeny {
    -				expectedKeys.denies[bKey] = bEntry
    -				expectedKeys.denies[bKeyWithBProto] = bEntryCpy
    +				expectedKeys.denies[bKey] = bEntryWithDep
    +				expectedKeys.denies[bKeyWithAProto] = bEntryCpy
     			} else {
    -				expectedKeys.allows[bKey] = bEntry
    -				expectedKeys.allows[bKeyWithBProto] = bEntryCpy
    +				expectedKeys.allows[bKey] = bEntryWithDep
    +				expectedKeys.allows[bKeyWithAProto] = bEntryCpy
     			}
     		}
    +		if tt.outcome&insertBWithAProtoAsDeny > 0 {
    +			bKeyWithAProto := Key{tt.bIdentity, tt.aPort, tt.aProto, 0}
    +			bEntryAsDeny := bEntry.WithOwners(aKey).asDeny()
    +			aEntryWithDep := aEntry.WithDependents(bKeyWithAProto)
    +			expectedKeys.denies[aKey] = aEntryWithDep
    +			expectedKeys.denies[bKeyWithAProto] = bEntryAsDeny
    +		}
     		outcomeKeys := newMapState(nil)
     		outcomeKeys.denyPreferredInsert(aKey, aEntry, selectorCache, allFeatures)
     		outcomeKeys.denyPreferredInsert(bKey, bEntry, selectorCache, allFeatures)
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

0

No linked articles in our index yet.