genpolicy: add rule for AddARPNeighbors

When the network interface provisioned by the CNI has static ARP table entries,
the runtime calls AddARPNeighbor to propagate these to the agent. As of today,
these calls are simply rejected.

In order to allow the calls, we do some sanity checks on the arguments:

We must ensure that we don't unexpectedly route traffic to the host that was
not intended to leave the VM. In a first approximation, this applies to
loopback IPs and devices. However, there may be other sensitive ranges (for
example, VPNs between VMs), so there should be some flexibility for users to
restrict this further. This is why we introduce a setting, similar to
UpdateRoutes, that allows restricting the neighbor IPs further.

The only valid state of an ARP neighbor entry is NUD_PERMANENT, which has a
value of 128 [1]. This is already enforced by the runtime.

According to rtnetlink(7), valid flag values are 8 and 128, respectively [2],
thus we allow any combination of these.

[1]: https://github.com/torvalds/linux/blob/4790580/include/uapi/linux/neighbour.h#L72
[2]: https://github.com/torvalds/linux/blob/4790580/include/uapi/linux/neighbour.h#L49C20-L53

Fixes: #11664

Signed-off-by: Markus Rudy <mr@edgeless.systems>
This commit is contained in:
Markus Rudy 2025-08-06 10:43:51 +02:00
parent af01434226
commit 3eb0641431
6 changed files with 214 additions and 2 deletions

View File

@ -347,6 +347,14 @@
"^127\\.(?:[0-9]{1,3}\\.){2}[0-9]{1,3}$" "^127\\.(?:[0-9]{1,3}\\.){2}[0-9]{1,3}$"
] ]
}, },
"AddARPNeighborsRequest": {
"forbidden_device_names": [
"lo"
],
"forbidden_cidrs_regex": [
"^127\\.(?:[0-9]{1,3}\\.){2}[0-9]{1,3}$"
]
},
"CloseStdinRequest": false, "CloseStdinRequest": false,
"ReadStreamRequest": false, "ReadStreamRequest": false,
"UpdateEphemeralMountsRequest": false, "UpdateEphemeralMountsRequest": false,

View File

@ -1400,6 +1400,25 @@ UpdateInterfaceRequest if {
print("UpdateInterfaceRequest: true") print("UpdateInterfaceRequest: true")
} }
AddARPNeighborsRequest if {
p_defaults := policy_data.request_defaults.AddARPNeighborsRequest
print("AddARPNeighborsRequest: policy =", p_defaults)
every i_neigh in input.neighbors.ARPNeighbors {
print("AddARPNeighborsRequest: i_neigh =", i_neigh)
not i_neigh.device in p_defaults.forbidden_device_names
i_neigh.toIPAddress.mask == ""
every p_cidr in p_defaults.forbidden_cidrs_regex {
not regex.match(p_cidr, i_neigh.toIPAddress.address)
}
i_neigh.state == 128
bits.or(i_neigh.flags, 136) == 136
}
print("AddARPNeighborsRequest: true")
}
CloseStdinRequest if { CloseStdinRequest if {
policy_data.request_defaults.CloseStdinRequest == true policy_data.request_defaults.CloseStdinRequest == true
} }

View File

@ -355,6 +355,16 @@ pub struct UpdateInterfaceRequestDefaults {
forbidden_hw_addrs: Vec<String>, forbidden_hw_addrs: Vec<String>,
} }
/// UpdateInterfaceRequest settings from genpolicy-settings.json.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct AddARPNeighborsRequestDefaults {
/// Explicitly blocked interface names. Intent is to block changes to loopback interface.
forbidden_device_names: Vec<String>,
/// Explicitly blocked IP address ranges.
/// Should include loopback addresses and other CIDRs that should not be routed outside the VM.
forbidden_cidrs_regex: Vec<String>,
}
/// Settings specific to each kata agent endpoint, loaded from /// Settings specific to each kata agent endpoint, loaded from
/// genpolicy-settings.json. /// genpolicy-settings.json.
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
@ -374,6 +384,9 @@ pub struct RequestDefaults {
/// Allow the host to configure only used raw_flags and reject names/mac addresses of the loopback. /// Allow the host to configure only used raw_flags and reject names/mac addresses of the loopback.
pub UpdateInterfaceRequest: UpdateInterfaceRequestDefaults, pub UpdateInterfaceRequest: UpdateInterfaceRequestDefaults,
/// Allow the host to configure only used raw_flags and reject names/mac addresses of the loopback.
pub AddARPNeighborsRequest: AddARPNeighborsRequestDefaults,
/// Allow the Host to close stdin for a container. Typically used with WriteStreamRequest. /// Allow the Host to close stdin for a container. Typically used with WriteStreamRequest.
pub CloseStdinRequest: bool, pub CloseStdinRequest: bool,

View File

@ -13,8 +13,8 @@ mod tests {
use std::str; use std::str;
use protocols::agent::{ use protocols::agent::{
CopyFileRequest, CreateContainerRequest, CreateSandboxRequest, ExecProcessRequest, AddARPNeighborsRequest, CopyFileRequest, CreateContainerRequest, CreateSandboxRequest,
RemoveContainerRequest, UpdateInterfaceRequest, UpdateRoutesRequest, ExecProcessRequest, RemoveContainerRequest, UpdateInterfaceRequest, UpdateRoutesRequest,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -32,6 +32,7 @@ mod tests {
RemoveContainer(RemoveContainerRequest), RemoveContainer(RemoveContainerRequest),
UpdateInterface(UpdateInterfaceRequest), UpdateInterface(UpdateInterfaceRequest),
UpdateRoutes(UpdateRoutesRequest), UpdateRoutes(UpdateRoutesRequest),
AddARPNeighbors(AddARPNeighborsRequest),
} }
impl Display for TestRequest { impl Display for TestRequest {
@ -44,6 +45,7 @@ mod tests {
TestRequest::RemoveContainer(_) => write!(f, "RemoveContainerRequest"), TestRequest::RemoveContainer(_) => write!(f, "RemoveContainerRequest"),
TestRequest::UpdateInterface(_) => write!(f, "UpdateInterfaceRequest"), TestRequest::UpdateInterface(_) => write!(f, "UpdateInterfaceRequest"),
TestRequest::UpdateRoutes(_) => write!(f, "UpdateRoutesRequest"), TestRequest::UpdateRoutes(_) => write!(f, "UpdateRoutesRequest"),
TestRequest::AddARPNeighbors(_) => write!(f, "AddARPNeighborsRequest"),
} }
} }
} }
@ -240,6 +242,11 @@ mod tests {
runtests("updateinterface").await; runtests("updateinterface").await;
} }
#[tokio::test]
async fn test_add_arp_neighbors() {
runtests("addarpneighbors").await;
}
#[tokio::test] #[tokio::test]
async fn test_create_container_network_namespace() { async fn test_create_container_network_namespace() {
runtests("createcontainer/network_namespace").await; runtests("createcontainer/network_namespace").await;

View File

@ -0,0 +1,9 @@
apiVersion: v1
kind: Pod
metadata:
name: dummy
spec:
runtimeClassName: kata-cc-isolation
containers:
- name: dummy
image: registry.k8s.io/pause:3.6@sha256:3d380ca8864549e74af4b29c10f9cb0956236dfb01c40ca076fb6c37253234db

View File

@ -0,0 +1,156 @@
[
{
"description": "compliant neighbors",
"allowed": true,
"request": {
"type": "AddARPNeighbors",
"neighbors": {
"ARPNeighbors": [
{
"toIPAddress": {
"family": 0,
"address": "10.0.0.1",
"mask": ""
},
"device": "eth0",
"lladdr": "00:00:5e:00:53:01",
"state": 128,
"flags": 0
}
]
}
}
},
{
"description": "allowed flags: NTF_PROXY",
"allowed": true,
"request": {
"type": "AddARPNeighbors",
"neighbors": {
"ARPNeighbors": [
{
"toIPAddress": {
"family": 0,
"address": "10.0.0.1",
"mask": ""
},
"device": "eth0",
"lladdr": "00:00:5e:00:53:01",
"state": 128,
"flags": 8
}
]
}
}
},
{
"description": "allowed flags: NTF_ROUTER",
"allowed": true,
"request": {
"type": "AddARPNeighbors",
"neighbors": {
"ARPNeighbors": [
{
"toIPAddress": {
"family": 0,
"address": "10.0.0.1",
"mask": ""
},
"device": "eth0",
"lladdr": "00:00:5e:00:53:01",
"state": 128,
"flags": 128
}
]
}
}
},
{
"description": "bad interface",
"allowed": false,
"request": {
"type": "AddARPNeighbors",
"neighbors": {
"ARPNeighbors": [
{
"toIPAddress": {
"family": 0,
"address": "10.0.0.1",
"mask": ""
},
"device": "lo",
"lladdr": "00:00:5e:00:53:01",
"state": 128,
"flags": 0
}
]
}
}
},
{
"description": "bad IP",
"allowed": false,
"request": {
"type": "AddARPNeighbors",
"neighbors": {
"ARPNeighbors": [
{
"toIPAddress": {
"family": 0,
"address": "127.1.2.3",
"mask": ""
},
"device": "eth0",
"lladdr": "00:00:5e:00:53:01",
"state": 128,
"flags": 0
}
]
}
}
},
{
"description": "bad state",
"allowed": false,
"request": {
"type": "AddARPNeighbors",
"neighbors": {
"ARPNeighbors": [
{
"toIPAddress": {
"family": 0,
"address": "10.0.0.1",
"mask": ""
},
"device": "eth0",
"lladdr": "00:00:5e:00:53:01",
"state": 0,
"flags": 0
}
]
}
}
},
{
"description": "bad flags",
"allowed": false,
"request": {
"type": "AddARPNeighbors",
"neighbors": {
"ARPNeighbors": [
{
"toIPAddress": {
"family": 0,
"address": "10.0.0.1",
"mask": ""
},
"device": "eth0",
"lladdr": "00:00:5e:00:53:01",
"state": 128,
"flags": 5
}
]
}
}
}
]