From 1db2b7837ff05e39389ac1d7a9ece9898a19a701 Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Mon, 3 Nov 2025 15:47:10 +0000 Subject: [PATCH] daemon: inspect_get_windows_group_policy Windows group policy objects (GPOs) are restrictions that can be added by an administrator to Windows to lock down various operations. From our point of view the ones that matter involve restricting the ability to inject device drivers. Previously virt-v2v detected group policy here: https://github.com/libguestfs/virt-v2v/blob/9bb2e7d4705811f0e227103c14757895e5f591d9/convert/convert_windows.ml#L69 We would like to report group policy through the libguestfs API and tools such as virt-inspector, so move the code that is used to detect group policy to libguestfs. A new API is introduced that returns whether group policy was found (only for Windows guests) during inspection of the software registry. Fixes: https://issues.redhat.com/browse/RHEL-125846 --- daemon/inspect.ml | 7 +++++ daemon/inspect_fs_windows.ml | 48 +++++++++++++++++++++++++++++++++ daemon/inspect_types.ml | 6 +++++ daemon/inspect_types.mli | 1 + generator/actions_inspection.ml | 22 +++++++++++++++ generator/proc_nr.ml | 1 + lib/MAX_PROC_NR | 2 +- 7 files changed, 86 insertions(+), 1 deletion(-) diff --git a/daemon/inspect.ml b/daemon/inspect.ml index 84571f582..e4530d3a1 100644 --- a/daemon/inspect.ml +++ b/daemon/inspect.ml @@ -450,6 +450,13 @@ and inspect_get_windows_current_control_set root = | None -> failwith "not a Windows guest, or CurrentControlSet could not be determined" +and inspect_get_windows_group_policy root = + let root = search_for_root root in + match root.inspection_data.windows_group_policy with + | Some v -> v + | None -> + failwith "not a Windows guest, or software hive could not be parsed" + and inspect_is_live root = false and inspect_is_netinst root = false diff --git a/daemon/inspect_fs_windows.ml b/daemon/inspect_fs_windows.ml index e69ab3e12..fde051c0d 100644 --- a/daemon/inspect_fs_windows.ml +++ b/daemon/inspect_fs_windows.ml @@ -288,6 +288,54 @@ and check_windows_software_registry software_hive data = with Not_found -> () ); + + (* If the Windows guest appears to be using group policy, since + * group policy might be used to restrict driver injection. + * + * XXX If group policy is present, it may be possible to + * remove the restriction on driver injection. Microsoft has + * an article about this: + * https://support.microsoft.com/uk-ua/help/2773300/stop-0x0000007b-error-after-you-use-a-group-policy-setting-to-prevent + * Nikolay Ivanets pointed out that in addition to that you also + * have to delete: + * HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\DeviceInstall\Restrictions + *) + let has_group_policy = + try + (* NB This is a subtly different path from CurrentVersion path + * we used above. + *) + let path = [ "Microsoft"; "Windows"; "CurrentVersion"; "Group Policy"; "History" ] in + let node = get_node h root path in + let children = Hivex.node_children h node in + let children = Array.to_list children in + let children = + List.map (fun child -> Hivex.node_name h child) children in + eprintf "check_windows_software_registry: \ + found HKLM\\SOFTWARE\\%s node with children: [%s]\n" + (String.concat "\\" path) + (String.concat ", " children); + + (* Just assume any children looking like "{}" mean that + * some GPOs were installed. + * + * In future we might want to look for nodes which match: + * History\{}\ where is a small integer (the order + * in which policy objects were applied. + * + * For an example registry containing GPOs, see RHBZ#1219651. + * See also: https://support.microsoft.com/en-us/kb/201453 + *) + let is_gpo_guid name = + let len = String.length name in + len > 3 && name.[0] = '{' && + Char.isxdigit name.[1] && name.[len-1] = '}' + in + List.exists is_gpo_guid children + with + Not_found -> false in + data.windows_group_policy <- Some has_group_policy; + with | Not_found -> if verbose () then diff --git a/daemon/inspect_types.ml b/daemon/inspect_types.ml index 4c20070b9..f4ee5d0db 100644 --- a/daemon/inspect_types.ml +++ b/daemon/inspect_types.ml @@ -54,6 +54,7 @@ and inspection_data = { mutable windows_software_hive : string option; mutable windows_system_hive : string option; mutable windows_current_control_set : string option; + mutable windows_group_policy : bool option; mutable drive_mappings : drive_mapping list; } and os_type = @@ -188,6 +189,8 @@ and string_of_inspection_data data = data.windows_system_hive; Option.iter (fun v -> bpf " windows_current_control_set: %s\n" v) data.windows_current_control_set; + Option.iter (fun v -> bpf " windows_group_policy: %b\n" v) + data.windows_group_policy; if data.drive_mappings <> [] then ( let v = List.map (fun (a, b) -> sprintf "(%s, %s)" a b) data.drive_mappings in @@ -289,6 +292,7 @@ let null_inspection_data = { windows_software_hive = None; windows_system_hive = None; windows_current_control_set = None; + windows_group_policy = None; drive_mappings = []; } let null_inspection_data () = { null_inspection_data with os_type = None } @@ -316,6 +320,8 @@ let merge_inspection_data child parent = merge child.windows_system_hive parent.windows_system_hive; parent.windows_current_control_set <- merge child.windows_current_control_set parent.windows_current_control_set; + parent.windows_group_policy <- + merge child.windows_group_policy parent.windows_group_policy; (* This is what the old C code did, but I doubt that it's correct. *) parent.drive_mappings <- child.drive_mappings @ parent.drive_mappings diff --git a/daemon/inspect_types.mli b/daemon/inspect_types.mli index e42bf213c..4c6579fba 100644 --- a/daemon/inspect_types.mli +++ b/daemon/inspect_types.mli @@ -57,6 +57,7 @@ and inspection_data = { mutable windows_software_hive : string option; mutable windows_system_hive : string option; mutable windows_current_control_set : string option; + mutable windows_group_policy : bool option; mutable drive_mappings : drive_mapping list; } (** During inspection, this data is collected incrementally for each diff --git a/generator/actions_inspection.ml b/generator/actions_inspection.ml index 27e8dceb6..3cf6f7dc2 100644 --- a/generator/actions_inspection.ml +++ b/generator/actions_inspection.ml @@ -521,6 +521,28 @@ hive is a valid Windows Registry hive. You can use C to read or write to the hive. +Please read L for more details.|} }; + + { defaults with + name = "inspect_get_windows_group_policy"; added = (1, 57, 6); + style = RBool "hasgrouppolicy", [String (Mountable, "root")], []; + impl = OCaml "Inspect.inspect_get_windows_group_policy"; + shortdesc = "return if Windows guest has group policy"; + longdesc = {|This returns true if the Windows guest +has group policy. Group policy can interfere with device +driver installation, preventing libguestfs features like +driver injection from working. + +Note that the presence of group policy is only an indication that +there may be a problem with driver installation. As group policy +is extremely complex and covers many different aspects of Windows +configuration, and we make no attempt to parse it, presence of +group policy should only raise a warning. + +This call assumes that the guest is Windows and that the +Registry could be examined by inspection. If this is not +the case then an error is returned. + Please read L for more details.|} }; { defaults with diff --git a/generator/proc_nr.ml b/generator/proc_nr.ml index bea19c5c7..11e7b9d1b 100644 --- a/generator/proc_nr.ml +++ b/generator/proc_nr.ml @@ -523,6 +523,7 @@ let proc_nr = [ 518, "btrfs_scrub_full"; 519, "setfiles"; 520, "ntfs_chmod"; +521, "inspect_get_windows_group_policy"; ] (* End of list. If adding a new entry, add it at the end of the list diff --git a/lib/MAX_PROC_NR b/lib/MAX_PROC_NR index 2596e4ad8..5a232f264 100644 --- a/lib/MAX_PROC_NR +++ b/lib/MAX_PROC_NR @@ -1 +1 @@ -520 +521