CIDR deny policies may not take effect when a more narrow CIDR allow is present
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.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/cilium/ciliumGo | >= 1.15.0, < 1.15.10 | 1.15.10 |
github.com/cilium/ciliumGo | >= 1.14.0, < 1.14.16 | 1.14.16 |
Affected products
1Patches
29c01afb5646apolicy: Fix broad deny with except bug
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)
02d28d9ac9afpolicy: Fix broad deny with except bug
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- github.com/advisories/GHSA-3wwx-63fv-pfq6ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2024-47825ghsaADVISORY
- github.com/cilium/cilium/commit/02d28d9ac9afcaddd301fae6fb4d6cda8c2d0c45ghsaWEB
- github.com/cilium/cilium/commit/9c01afb5646af3f0c696421a410dc66c513b6524ghsaWEB
- github.com/cilium/cilium/security/advisories/GHSA-3wwx-63fv-pfq6ghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.