From: Ming Lu Subject: CA-430074: Network.create lets vm-admin reconfigure dom0 IP and HIMN listener The minimum allowed role of invoking Network.create is vm-admin. But the [other_config] map of a Network object (except the key specified by [map_keys_roles]) are of minimum role of pool-operator. So the [other_config] parameter of Network.create should be checked to not allow two particular keys which might impact the host internal management network (HIMN) when the caller of the API is of vm-admin role. The HIMN is allowed to be managed with pool-operator or higher privilege levels. Additionally, there is another path (import) to get the privilege escalation happen. This is CVE-2026-102707, part of XSA-530. Signed-off-by: Ming Lu Reviewed-by: Andrii Sultanov diff --git a/ocaml/idl/datamodel.ml b/ocaml/idl/datamodel.ml index 861e48852207..97e26278ccc3 100644 --- a/ocaml/idl/datamodel.ml +++ b/ocaml/idl/datamodel.ml @@ -1493,6 +1493,11 @@ module Network = struct ("folder", _R_VM_OP) ; ("XenCenter.CustomFields.*", _R_VM_OP) ; ("XenCenterCreateInProgress", _R_VM_OP) + ; (* Host Internal Management Network markers. Redundant for + the writer_roles, but they generate the per-key rules that + Network.create checks, its default role being VM admin. *) + ("is_guest_installer_network", _R_POOL_OP) + ; ("is_host_internal_management_network", _R_POOL_OP) ] ~published:rel_rio ; field diff --git a/ocaml/xapi/import.ml b/ocaml/xapi/import.ml index 16ebb4c9264c..080582b47309 100644 --- a/ocaml/xapi/import.ml +++ b/ocaml/xapi/import.ml @@ -1273,6 +1273,17 @@ module Net : HandlerTools = struct | Found_net _ -> handle_dry_run __context config rpc session_id state x precheck_result | Create net_record -> + (* The [session_id] passed to Client.Network.create_from_record + (and hence to Network.create) may have been created by + Helpers.call_api_functions with a higher privilege level than the + requesting client session. This is intentional, but it means the + per-key RBAC check inside Network.create would evaluate that + elevated session and let protected keys through. The import + metadata is caller-controlled, so enforce the check here instead, + where Helpers.assert_map_with_rbac can still resolve the + originating session. *) + Xapi_network_helpers.assert_protected_map_keys_allowed ~__context + ~field_name:"other_config" ~value:net_record.API.network_other_config ; let net = log_reraise ("failed to create Network with name_label " diff --git a/ocaml/xapi/xapi_network.ml b/ocaml/xapi/xapi_network.ml index dfa15d0a18ab..d35de92696a5 100644 --- a/ocaml/xapi/xapi_network.ml +++ b/ocaml/xapi/xapi_network.ml @@ -235,6 +235,8 @@ let rec choose_bridge_name bridges = let create ~__context ~name_label ~name_description ~mTU ~other_config ~bridge ~managed ~tags = + Xapi_network_helpers.assert_protected_map_keys_allowed ~__context + ~field_name:"other_config" ~value:other_config ; with_lock mutex (fun () -> let networks = Db.Network.get_all ~__context in let bridges = diff --git a/ocaml/xapi/xapi_network_helpers.ml b/ocaml/xapi/xapi_network_helpers.ml index 0a6bcd47c0e2..0e1932abfda6 100644 --- a/ocaml/xapi/xapi_network_helpers.ml +++ b/ocaml/xapi/xapi_network_helpers.ml @@ -123,3 +123,8 @@ let assert_network_compatible_with_sriov ~__context ~pif ~network = ) ) ) + +let assert_protected_map_keys_allowed ~__context ~field_name ~value = + let match_protected = Helpers.match_protected_key "network" in + Helpers.assert_map_with_rbac ~__context ~match_protected + ~object_name:"network" ~field_name ~value