Cilium agent's race condition may lead to policy bypass for Host Firewall policy
Description
Cilium is a networking, observability, and security solution with an eBPF-based dataplane. Prior to versions 1.14.14 and 1.15.8, a race condition in the Cilium agent can cause the agent to ignore labels that should be applied to a node. This could in turn cause CiliumClusterwideNetworkPolicies intended for nodes with the ignored label to not apply, leading to policy bypass. This issue has been patched in Cilium v1.14.14 and v1.15.8 As the underlying issue depends on a race condition, users unable to upgrade can restart the Cilium agent on affected nodes until the affected policies are confirmed to be working as expected.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/cilium/ciliumGo | < 1.14.14 | 1.14.14 |
github.com/cilium/ciliumGo | >= 1.15.0, < 1.15.8 | 1.15.8 |
Affected products
1Patches
37877db09b3f3Track node labels propagated to the endpoint manager correctly
3 files changed · +51 −7
pkg/endpoint/endpoint.go+5 −0 modified@@ -470,6 +470,11 @@ func (e *Endpoint) IsHost() bool { return e.isHost } +// SetIsHost is a convenient method to create host endpoints for testing. +func (ep *Endpoint) SetIsHost(isHost bool) { + ep.isHost = isHost +} + // closeBPFProgramChannel closes the channel that signals whether the endpoint // has had its BPF program compiled. If the channel is already closed, this is // a no-op.
pkg/endpointmanager/host.go+14 −7 modified@@ -35,24 +35,31 @@ func (mgr *endpointManager) startNodeLabelsObserver(old map[string]string) { return } - mgr.updateHostEndpointLabels(old, ln.Labels) - old = ln.Labels + if mgr.updateHostEndpointLabels(old, ln.Labels) { + // Endpoint's label update logic rejects a request if any of the old labels are + // not present in the endpoint manager's state. So, overwrite old labels only if + // the update is successful to avoid node labels being outdated indefinitely (GH-29649). + old = ln.Labels + } + }, func(error) { /* Executed only when we are shutting down */ }) } -func (mgr *endpointManager) updateHostEndpointLabels(oldNodeLabels, newNodeLabels map[string]string) { +// updateHostEndpointLabels updates the local node labels in the endpoint manager. +// Returns true if the update is successful. +func (mgr *endpointManager) updateHostEndpointLabels(oldNodeLabels, newNodeLabels map[string]string) bool { nodeEP := mgr.GetHostEndpoint() if nodeEP == nil { log.Error("Host endpoint not found") - return + return false } - err := nodeEP.UpdateLabelsFrom(oldNodeLabels, newNodeLabels, labels.LabelSourceK8s) - if err != nil { + if err := nodeEP.UpdateLabelsFrom(oldNodeLabels, newNodeLabels, labels.LabelSourceK8s); err != nil { // An error can only occur if either the endpoint is terminating, or the // old labels are not found. Both are impossible, hence there's no point // in retrying. log.WithError(err).Error("Unable to update host endpoint labels") - return + return false } + return true }
pkg/endpointmanager/manager_test.go+32 −0 modified@@ -21,8 +21,11 @@ import ( "github.com/cilium/cilium/pkg/endpoint/regeneration" "github.com/cilium/cilium/pkg/fqdn/restore" "github.com/cilium/cilium/pkg/hive/cell" + "github.com/cilium/cilium/pkg/labelsfilter" "github.com/cilium/cilium/pkg/lock" monitorAPI "github.com/cilium/cilium/pkg/monitor/api" + "github.com/cilium/cilium/pkg/node" + "github.com/cilium/cilium/pkg/node/types" "github.com/cilium/cilium/pkg/option" "github.com/cilium/cilium/pkg/policy" "github.com/cilium/cilium/pkg/revert" @@ -967,3 +970,32 @@ func (s *EndpointManagerSuite) TestWaitForEndpointsAtPolicyRev(c *C) { tt.postTestRun() } } + +func (s *EndpointManagerSuite) TestMissingNodeLabelsUpdate(c *C) { + // Initialize label filter config. + labelsfilter.ParseLabelPrefixCfg(nil, "") + mgr := New(&dummyEpSyncher{}, nil, nil) + hostEPID := uint16(17) + + // Initialize the local node watcher before the host endpoint is created. + // These labels are not propagated to the endpoint manager. + mgr.localNodeStore = node.NewTestLocalNodeStore(node.LocalNode{Node: types.Node{}}) + mgr.startNodeLabelsObserver(nil) + mgr.localNodeStore.Update(func(ln *node.LocalNode) { ln.Labels = map[string]string{"k1": "v1"} }) + _, ok := mgr.endpoints[hostEPID] + c.Assert(ok, checker.Equals, false) + + // Create host endpoint and expose it in the endpoint manager. + ep := endpoint.NewEndpointWithState(s, s, testipcache.NewMockIPCache(), &endpoint.FakeEndpointProxy{}, testidentity.NewMockIdentityAllocator(nil), 1, endpoint.StateReady) + ep.SetIsHost(true) + ep.ID = hostEPID + c.Assert(mgr.expose(ep), IsNil) + + // Update node labels and verify that the node labels are updated correctly even if the old + // labels {k1=v1} are not present in the endpoint manager's state. + mgr.localNodeStore.Update(func(ln *node.LocalNode) { ln.Labels = map[string]string{"k2": "v2"} }) + hostEP, ok := mgr.endpoints[hostEPID] + c.Assert(ok, checker.Equals, true) + got := hostEP.OpLabels.IdentityLabels().K8sStringMap() + c.Assert(map[string]string{"k2": "v2"}, checker.DeepEquals, got) +}
aa44dd148a9bTrack node labels propagated to the endpoint manager correctly
3 files changed · +52 −7
pkg/endpoint/endpoint.go+5 −0 modified@@ -506,6 +506,11 @@ func (e *Endpoint) IsHost() bool { return e.isHost } +// SetIsHost is a convenient method to create host endpoints for testing. +func (ep *Endpoint) SetIsHost(isHost bool) { + ep.isHost = isHost +} + // closeBPFProgramChannel closes the channel that signals whether the endpoint // has had its BPF program compiled. If the channel is already closed, this is // a no-op.
pkg/endpointmanager/host.go+14 −7 modified@@ -35,24 +35,31 @@ func (mgr *endpointManager) startNodeLabelsObserver(old map[string]string) { return } - mgr.updateHostEndpointLabels(old, ln.Labels) - old = ln.Labels + if mgr.updateHostEndpointLabels(old, ln.Labels) { + // Endpoint's label update logic rejects a request if any of the old labels are + // not present in the endpoint manager's state. So, overwrite old labels only if + // the update is successful to avoid node labels being outdated indefinitely (GH-29649). + old = ln.Labels + } + }, func(error) { /* Executed only when we are shutting down */ }) } -func (mgr *endpointManager) updateHostEndpointLabels(oldNodeLabels, newNodeLabels map[string]string) { +// updateHostEndpointLabels updates the local node labels in the endpoint manager. +// Returns true if the update is successful. +func (mgr *endpointManager) updateHostEndpointLabels(oldNodeLabels, newNodeLabels map[string]string) bool { nodeEP := mgr.GetHostEndpoint() if nodeEP == nil { log.Error("Host endpoint not found") - return + return false } - err := nodeEP.UpdateLabelsFrom(oldNodeLabels, newNodeLabels, labels.LabelSourceK8s) - if err != nil { + if err := nodeEP.UpdateLabelsFrom(oldNodeLabels, newNodeLabels, labels.LabelSourceK8s); err != nil { // An error can only occur if either the endpoint is terminating, or the // old labels are not found. Both are impossible, hence there's no point // in retrying. log.WithError(err).Error("Unable to update host endpoint labels") - return + return false } + return true }
pkg/endpointmanager/manager_test.go+33 −0 modified@@ -19,7 +19,10 @@ import ( "github.com/cilium/cilium/pkg/endpoint" endpointid "github.com/cilium/cilium/pkg/endpoint/id" "github.com/cilium/cilium/pkg/fqdn/restore" + "github.com/cilium/cilium/pkg/labelsfilter" monitorAPI "github.com/cilium/cilium/pkg/monitor/api" + "github.com/cilium/cilium/pkg/node" + "github.com/cilium/cilium/pkg/node/types" "github.com/cilium/cilium/pkg/option" "github.com/cilium/cilium/pkg/policy" testidentity "github.com/cilium/cilium/pkg/testutils/identity" @@ -958,3 +961,33 @@ func TestWaitForEndpointsAtPolicyRev(t *testing.T) { tt.postTestRun() } } + +func TestMissingNodeLabelsUpdate(t *testing.T) { + // Initialize label filter config. + labelsfilter.ParseLabelPrefixCfg(nil, nil, "") + s := setupEndpointManagerSuite(t) + mgr := New(&dummyEpSyncher{}, nil, nil) + hostEPID := uint16(17) + + // Initialize the local node watcher before the host endpoint is created. + // These labels are not propagated to the endpoint manager. + mgr.localNodeStore = node.NewTestLocalNodeStore(node.LocalNode{Node: types.Node{}}) + mgr.startNodeLabelsObserver(nil) + mgr.localNodeStore.Update(func(ln *node.LocalNode) { ln.Labels = map[string]string{"k1": "v1"} }) + _, ok := mgr.endpoints[hostEPID] + require.EqualValues(t, ok, false) + + // Create host endpoint and expose it in the endpoint manager. + ep := endpoint.NewTestEndpointWithState(t, s, s, testipcache.NewMockIPCache(), &endpoint.FakeEndpointProxy{}, testidentity.NewMockIdentityAllocator(nil), 1, endpoint.StateReady) + ep.SetIsHost(true) + ep.ID = hostEPID + require.Nil(t, mgr.expose(ep)) + + // Update node labels and verify that the node labels are updated correctly even if the old + // labels {k1=v1} are not present in the endpoint manager's state. + mgr.localNodeStore.Update(func(ln *node.LocalNode) { ln.Labels = map[string]string{"k2": "v2"} }) + hostEP, ok := mgr.endpoints[hostEPID] + require.EqualValues(t, ok, true) + got := hostEP.OpLabels.IdentityLabels().K8sStringMap() + require.EqualValues(t, map[string]string{"k2": "v2"}, got) +}
f81a1ee0cfdefix: set node labels correctly for first node update
3 files changed · +82 −23
pkg/endpoint/endpoint.go+5 −0 modified@@ -410,6 +410,11 @@ func (e *Endpoint) IsHost() bool { return e.isHost } +// SetIsHost is a convinient method to create host endpoints for testing. +func (ep *Endpoint) SetIsHost(isHost bool) { + ep.isHost = isHost +} + // closeBPFProgramChannel closes the channel that signals whether the endpoint // has had its BPF program compiled. If the channel is already closed, this is // a no-op.
pkg/endpointmanager/host.go+22 −23 modified@@ -4,16 +4,18 @@ package endpointmanager import ( - "context" - "github.com/cilium/cilium/pkg/endpoint" slim_corev1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/api/core/v1" "github.com/cilium/cilium/pkg/labels" - "github.com/cilium/cilium/pkg/labelsfilter" "github.com/cilium/cilium/pkg/lock" "github.com/cilium/cilium/pkg/node" ) +// isFirstNodeUpdate tracks the first node update to the endpoint manager. +// This is used to resolve a race condition during agent startup where the +// k8s node label updates are rejected indefinitely by the host endpoint (GH-29649). +var isFirstNodeUpdate = true + // GetHostEndpoint returns the host endpoint. func (mgr *endpointManager) GetHostEndpoint() *endpoint.Endpoint { mgr.mutex.RLock() @@ -36,20 +38,7 @@ func (mgr *endpointManager) HostEndpointExists() bool { // This adheres to the subscriber.NodeHandler interface. func (mgr *endpointManager) OnAddNode(newNode *slim_corev1.Node, swg *lock.StoppableWaitGroup) error { - - node.SetLabels(newNode.GetLabels()) - - nodeEP := mgr.GetHostEndpoint() - if nodeEP == nil { - // if host endpoint does not exist yet, labels will be set when it'll be created. - return nil - } - - newLabels := labels.Map2Labels(newNode.GetLabels(), labels.LabelSourceK8s) - newIdtyLabels, _ := labelsfilter.Filter(newLabels) - nodeEP.UpdateLabels(context.TODO(), labels.LabelSourceAny, newIdtyLabels, nil, false) - - return nil + return mgr.OnUpdateNode(nil, newNode, swg) } // OnUpdateNode implements the endpointManager's logic for reacting to updated @@ -58,21 +47,31 @@ func (mgr *endpointManager) OnAddNode(newNode *slim_corev1.Node, func (mgr *endpointManager) OnUpdateNode(oldNode, newNode *slim_corev1.Node, swg *lock.StoppableWaitGroup) error { - oldNodeLabels := oldNode.GetLabels() + var oldNodeLabels map[string]string + // Endpoint's label update logic rejects a request if any of the old labels are + // not present in the endpoint manager's state. Overwrite the old labels to nil + // for the first node update to avoid node labels being outdated indefinitely (GH-29649). + if oldNode == nil || isFirstNodeUpdate { + oldNodeLabels = make(map[string]string) + } else { + oldNodeLabels = oldNode.GetLabels() + } newNodeLabels := newNode.GetLabels() + // Set the labels early so host endpoint is created with latest labels. + node.SetLabels(newNodeLabels) nodeEP := mgr.GetHostEndpoint() if nodeEP == nil { - log.Error("Host endpoint not found") + log.Debug("Host endpoint not found") return nil } - node.SetLabels(newNodeLabels) - - err := nodeEP.UpdateLabelsFrom(oldNodeLabels, newNodeLabels, labels.LabelSourceK8s) - if err != nil { + if err := nodeEP.UpdateLabelsFrom(oldNodeLabels, newNodeLabels, labels.LabelSourceK8s); err != nil { + log.WithError(err).Error("Unable to update host endpoint labels") return err } + // Set isFirstNodeUpdate to false only on successful update. + isFirstNodeUpdate = false return nil }
pkg/endpointmanager/manager_test.go+55 −0 modified@@ -20,8 +20,12 @@ import ( endpointid "github.com/cilium/cilium/pkg/endpoint/id" "github.com/cilium/cilium/pkg/endpoint/regeneration" "github.com/cilium/cilium/pkg/fqdn/restore" + slim_corev1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/api/core/v1" + slim_metav1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/apis/meta/v1" + "github.com/cilium/cilium/pkg/labelsfilter" "github.com/cilium/cilium/pkg/lock" monitorAPI "github.com/cilium/cilium/pkg/monitor/api" + "github.com/cilium/cilium/pkg/node" "github.com/cilium/cilium/pkg/option" "github.com/cilium/cilium/pkg/policy" "github.com/cilium/cilium/pkg/revert" @@ -815,3 +819,54 @@ func (s *EndpointManagerSuite) TestWaitForEndpointsAtPolicyRev(c *C) { tt.postTestRun() } } + +func (s *EndpointManagerSuite) TestMissingNodeLabelsUpdate(c *C) { + node.SetTestLocalNodeStore() + defer node.UnsetTestLocalNodeStore() + // Initialize label filter config. + labelsfilter.ParseLabelPrefixCfg(nil, "") + mgr := New(&dummyEpSyncher{}) + hostEPID := uint16(17) + hostEP := endpoint.NewEndpointWithState(s, s, testipcache.NewMockIPCache(), &endpoint.FakeEndpointProxy{}, testidentity.NewMockIdentityAllocator(nil), 1, endpoint.StateReady) + hostEP.SetIsHost(true) + hostEP.ID = hostEPID + + // Helper function to create test node object. + newTestNode := func(labels map[string]string) *slim_corev1.Node { + return &slim_corev1.Node{ + ObjectMeta: slim_metav1.ObjectMeta{ + Labels: labels, + }, + } + } + + // Validate that labels for host endpoint are correctly set. + validateHostEPLabels := func(wantLabels map[string]string) { + currHostIP, ok := mgr.endpoints[hostEPID] + c.Assert(ok, Equals, true) + c.Assert(currHostIP.OpLabels.IdentityLabels().K8sStringMap(), checker.DeepEquals, wantLabels) + } + + oldNode := newTestNode(make(map[string]string)) + newNode := newTestNode(map[string]string{"k1": "v1"}) + + // Host endpoint is not created in endpoint manager so the node update + // is not consumed by endpoint manager. + c.Assert(mgr.OnUpdateNode(oldNode, newNode, lock.NewStoppableWaitGroup()), IsNil) + _, ok := mgr.endpoints[hostEPID] + c.Assert(ok, Equals, false) + + // Now expose host endpoint. + mgr.expose(hostEP) + + // Even though endpoint manager does not have k1=v1, the request is still accepted. + oldNode = newTestNode(map[string]string{"k1": "v1"}) + newNode = newTestNode(map[string]string{"k2": "v2"}) + c.Assert(mgr.OnUpdateNode(oldNode, newNode, lock.NewStoppableWaitGroup()), IsNil) + validateHostEPLabels(map[string]string{"k2": "v2"}) + + // k3=v3 is not present in the endpoint manager so an error is not returned. + oldNode = newTestNode(map[string]string{"k3": "v3"}) + newNode = newTestNode(map[string]string{"k4": "v4"}) + c.Assert(mgr.OnUpdateNode(oldNode, newNode, lock.NewStoppableWaitGroup()), NotNil) +}
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
7- github.com/advisories/GHSA-q7w8-72mr-vpgwghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2024-42488ghsaADVISORY
- github.com/cilium/cilium/commit/7877db09b3f34d3081a1d66459b8fa6603dc3d30ghsaWEB
- github.com/cilium/cilium/commit/aa44dd148a9be95e07782e4f990e61678ef0abf8ghsax_refsource_MISCWEB
- github.com/cilium/cilium/commit/f81a1ee0cfdec928980db8640def984b2eeaa134ghsaWEB
- github.com/cilium/cilium/pull/33511ghsax_refsource_MISCWEB
- github.com/cilium/cilium/security/advisories/GHSA-q7w8-72mr-vpgwghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.