mirror of
https://github.com/libguestfs/libguestfs.git
synced 2026-03-21 22:53:37 +00:00
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:
9bb2e7d470/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
488 lines
16 KiB
OCaml
488 lines
16 KiB
OCaml
(* guestfs-inspection
|
|
* Copyright (C) 2009-2025 Red Hat Inc.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*)
|
|
|
|
open Printf
|
|
|
|
open Std_utils
|
|
|
|
open Utils
|
|
open Mountable
|
|
open Inspect_types
|
|
|
|
let re_primary_partition = PCRE.compile "^/dev/(?:h|s|v)d.[1234]$"
|
|
|
|
let rec inspect_os () =
|
|
Mount_utils.umount_all ();
|
|
|
|
(* Start with the full list of filesystems, and inspect each one
|
|
* in turn to determine its possible role (root, /usr, homedir, etc.)
|
|
* Then we filter out duplicates and merge some filesystems into
|
|
* others.
|
|
*)
|
|
|
|
let fses =
|
|
Listfs.list_filesystems () |>
|
|
|
|
(* Filter out those filesystems which are mountable, and inspect
|
|
* each one to find its possible role. Converts the list to
|
|
* type: {!Inspect_types.fs} list.
|
|
*)
|
|
List.filter_map (
|
|
fun (mountable, vfs_type) ->
|
|
Inspect_fs.check_for_filesystem_on mountable vfs_type
|
|
) |>
|
|
|
|
debug_list_of_filesystems |>
|
|
|
|
(* The OS inspection information for CoreOS are gathered by inspecting
|
|
* multiple filesystems. Gather all the inspected information in the
|
|
* inspect_fs struct of the root filesystem.
|
|
*)
|
|
collect_coreos_inspection_info |>
|
|
|
|
(* Check if the same filesystem was listed twice as root in fses.
|
|
* This may happen for the *BSD root partition where an MBR partition
|
|
* is a shadow of the real root partition probably /dev/sda5
|
|
*)
|
|
check_for_duplicated_bsd_root |>
|
|
|
|
(* Check if the root filesystems are duplicated by btrfs snapshots.
|
|
* This happens especially for SLES guests.
|
|
*)
|
|
check_for_duplicated_btrfs_snapshots_of_root |>
|
|
|
|
(* For Linux guests with a separate /usr filesystem, merge some of the
|
|
* inspected information in that partition to the inspect_fs struct
|
|
* of the root filesystem.
|
|
*)
|
|
collect_linux_inspection_info in
|
|
|
|
(* Save what we found in a global variable. *)
|
|
Inspect_types.inspect_fses := fses;
|
|
|
|
(* At this point we have (in a global variable) a list of all filesystems
|
|
* found and data about each one. Now we assemble the list of
|
|
* filesystems which are root devices.
|
|
*
|
|
* Fall through to inspect_get_roots to do that.
|
|
*)
|
|
inspect_get_roots ()
|
|
|
|
and debug_list_of_filesystems fses =
|
|
if verbose () then (
|
|
eprintf "inspect_os: fses:\n";
|
|
List.iter (fun fs -> eprintf "%s" (string_of_fs fs)) fses;
|
|
flush stderr
|
|
);
|
|
fses
|
|
|
|
(* Traverse through the filesystem list and find out if it contains
|
|
* the [/] and [/usr] filesystems of a CoreOS image. If this is the
|
|
* case, sum up all the collected information on the root fs.
|
|
*)
|
|
and collect_coreos_inspection_info fses =
|
|
eprintf "inspect_os: collect_coreos_inspection_info\n%!";
|
|
|
|
(* Split the list into CoreOS root(s), CoreOS usr(s), and
|
|
* everything else.
|
|
*)
|
|
let rec loop roots usrs others = function
|
|
| [] -> roots, usrs, others
|
|
| ({ role = RoleRoot { distro = Some DISTRO_COREOS } } as r) :: rest ->
|
|
loop (r::roots) usrs others rest
|
|
| ({ role = RoleUsr { distro = Some DISTRO_COREOS } } as u) :: rest ->
|
|
loop roots (u::usrs) others rest
|
|
| o :: rest ->
|
|
loop roots usrs (o::others) rest
|
|
in
|
|
let roots, usrs, others = loop [] [] [] fses in
|
|
|
|
match roots with
|
|
(* If there are no CoreOS roots, then there's nothing to do. *)
|
|
| [] -> fses
|
|
(* If there are more than one CoreOS roots, we cannot inspect the guest. *)
|
|
| _::_::_ -> failwith "multiple CoreOS root filesystems found"
|
|
| [root] ->
|
|
match usrs with
|
|
(* If there are no CoreOS usr partitions, nothing to do. *)
|
|
| [] -> fses
|
|
| usrs ->
|
|
(* CoreOS is designed to contain 2 /usr partitions (USR-A, USR-B):
|
|
* https://coreos.com/docs/sdk-distributors/sdk/disk-partitions/
|
|
* One is active and one passive. During the initial boot, the
|
|
* passive partition is empty and it gets filled up when an
|
|
* update is performed. Then, when the system reboots, the
|
|
* boot loader is instructed to boot from the passive partition.
|
|
* If both partitions are valid, we cannot determine which the
|
|
* active and which the passive is, unless we peep into the
|
|
* boot loader. As a workaround, we check the OS versions and
|
|
* pick the one with the higher version as active.
|
|
*)
|
|
let compare_versions u1 u2 =
|
|
let v1 =
|
|
match u1 with
|
|
| { role = RoleUsr { version = Some v } } -> v
|
|
| _ -> (0, 0) in
|
|
let v2 =
|
|
match u2 with
|
|
| { role = RoleUsr { version = Some v } } -> v
|
|
| _ -> (0, 0) in
|
|
compare v2 v1 (* reverse order *)
|
|
in
|
|
let usrs = List.sort compare_versions usrs in
|
|
let usr = List.hd usrs in
|
|
|
|
merge usr root;
|
|
root :: others
|
|
|
|
(* On *BSD systems, sometimes [/dev/sda[1234]] is a shadow of the
|
|
* real root filesystem that is probably [/dev/sda5] (see:
|
|
* [http://www.freebsd.org/doc/handbook/disk-organization.html])
|
|
*)
|
|
and check_for_duplicated_bsd_root fses =
|
|
eprintf "inspect_os: check_for_duplicated_bsd_root\n%!";
|
|
|
|
try
|
|
let is_primary_partition = function
|
|
| { m_type = (MountablePath | MountableBtrfsVol _) } -> false
|
|
| { m_type = MountableDevice; m_device = d } ->
|
|
PCRE.matches re_primary_partition d
|
|
in
|
|
|
|
(* Try to find a "BSD primary", if there is one. *)
|
|
let bsd_primary =
|
|
List.find (
|
|
function
|
|
| { fs_location = { mountable };
|
|
role = RoleRoot { os_type = Some t } } ->
|
|
(t = OS_TYPE_FREEBSD || t = OS_TYPE_NETBSD || t = OS_TYPE_OPENBSD)
|
|
&& is_primary_partition mountable
|
|
| _ -> false
|
|
) fses in
|
|
|
|
let bsd_primary_os_type =
|
|
match bsd_primary with
|
|
| { role = RoleRoot { os_type = Some t } } -> t
|
|
| _ -> assert false in
|
|
|
|
(* Try to find a shadow of the primary, and if it is found the
|
|
* primary is removed.
|
|
*)
|
|
let fses_without_bsd_primary = List.filter ((!=) bsd_primary) fses in
|
|
let shadow_exists =
|
|
List.exists (
|
|
function
|
|
| { role = RoleRoot { os_type = Some t } } ->
|
|
t = bsd_primary_os_type
|
|
| _ -> false
|
|
) fses_without_bsd_primary in
|
|
if shadow_exists then fses_without_bsd_primary else fses
|
|
with
|
|
Not_found -> fses
|
|
|
|
(* Check for the case where the root filesystem gets duplicated by
|
|
* btrfs snapshots. Ignore the snapshots in this case (RHEL-93109).
|
|
*)
|
|
and check_for_duplicated_btrfs_snapshots_of_root fses =
|
|
eprintf "inspect_os: check_for_duplicated_btrfs_snapshots_of_root\n%!";
|
|
|
|
let fs_is_btrfs_snapshot_of_root = function
|
|
(* Is this filesystem a btrfs snapshot of root? *)
|
|
| { fs_location =
|
|
{ mountable = { m_type = MountableBtrfsVol _; m_device = dev1 };
|
|
vfs_type = "btrfs" };
|
|
role = RoleRoot inspection_data1 } as fs1 ->
|
|
(* Return true if it duplicates the parent device which has
|
|
* a root role.
|
|
*)
|
|
List.exists (function
|
|
| { fs_location =
|
|
{ mountable = { m_type = MountableDevice; m_device = dev2 };
|
|
vfs_type = "btrfs" };
|
|
role = RoleRoot inspection_data2 }
|
|
when dev1 = dev2 ->
|
|
(* Check the roles are similar enough. In my test I saw
|
|
* that /etc/fstab was slightly different in the parent
|
|
* and snapshot. It's possible this is because the snapshot
|
|
* was created during installation, but it's not clear.
|
|
*)
|
|
let similar =
|
|
inspection_data1.os_type = inspection_data2.os_type &&
|
|
inspection_data1.distro = inspection_data2.distro &&
|
|
inspection_data1.product_name = inspection_data2.product_name &&
|
|
inspection_data1.version = inspection_data2.version in
|
|
if verbose () && similar then
|
|
eprintf "check_for_duplicated_btrfs_snapshots_of_root: \
|
|
dropping duplicate btrfs snapshot:\n%s\n"
|
|
(string_of_fs fs1);
|
|
similar
|
|
| _ -> false
|
|
) fses
|
|
|
|
(* Anything else is not a snapshot. *)
|
|
| _ -> false
|
|
in
|
|
|
|
(* Filter out the duplicates. *)
|
|
List.filter (Fun.negate fs_is_btrfs_snapshot_of_root) fses
|
|
|
|
(* Traverse through the filesystem list and find out if it contains
|
|
* the [/] and [/usr] filesystems of a Linux image (but not CoreOS,
|
|
* for which there is a separate [collect_coreos_inspection_info]).
|
|
*
|
|
* If this is the case, sum up all the collected information on each
|
|
* root fs from the respective [/usr] filesystems.
|
|
*)
|
|
and collect_linux_inspection_info fses =
|
|
eprintf "inspect_os: collect_linux_inspection_info\n%!";
|
|
|
|
List.map (
|
|
function
|
|
| { role = RoleRoot { distro = Some DISTRO_COREOS } } as root -> root
|
|
| { role = RoleRoot _ } as root ->
|
|
collect_linux_inspection_info_for fses root
|
|
| fs -> fs
|
|
) fses
|
|
|
|
(* Traverse through the filesystems and find the /usr filesystem for
|
|
* the specified C<root>: if found, merge its basic inspection details
|
|
* to the root when they were set (i.e. because the /usr had os-release
|
|
* or other ways to identify the OS).
|
|
*)
|
|
and collect_linux_inspection_info_for fses root =
|
|
eprintf "inspect_os: collect_linux_inspection_info_for %s\n"
|
|
(string_of_location root.fs_location);
|
|
|
|
let root_fstab =
|
|
match root with
|
|
| { role = RoleRoot { fstab = f } } -> f
|
|
| _ -> assert false in
|
|
|
|
try
|
|
let usr =
|
|
List.find (
|
|
function
|
|
| { role = RoleUsr _; fs_location = usr_mp } ->
|
|
(* This checks that this usr is found in the fstab of
|
|
* the root filesystem.
|
|
*)
|
|
eprintf "inspect_os: checking if %s found in fstab of this root\n"
|
|
(string_of_location usr_mp);
|
|
List.exists (
|
|
fun (mountable, _) ->
|
|
eprintf "inspect_os: collect_linux_inspection_info_for: \
|
|
compare %s = %s\n"
|
|
(Mountable.to_string usr_mp.mountable)
|
|
(Mountable.to_string mountable);
|
|
usr_mp.mountable = mountable
|
|
) root_fstab
|
|
| _ -> false
|
|
) fses in
|
|
|
|
eprintf "inspect_os: collect_linux_inspection_info_for: merging:\n\
|
|
%sinto:\n%s"
|
|
(string_of_fs usr) (string_of_fs root);
|
|
merge usr root;
|
|
root
|
|
with
|
|
Not_found -> root
|
|
|
|
and inspect_get_roots () =
|
|
let fses = !Inspect_types.inspect_fses in
|
|
|
|
let roots =
|
|
List.filter_map (
|
|
fun fs -> try Some (root_of_fs fs) with Invalid_argument _ -> None
|
|
) fses in
|
|
if verbose () then (
|
|
eprintf "inspect_get_roots: roots:\n";
|
|
List.iter (fun root -> eprintf "%s" (string_of_root root)) roots;
|
|
flush stderr
|
|
);
|
|
|
|
(* Only return the list of mountables, since subsequent calls will
|
|
* be used to retrieve the other information.
|
|
*)
|
|
List.map (fun { root_location = { mountable = m } } -> m) roots
|
|
|
|
and root_of_fs =
|
|
function
|
|
| { fs_location = location; role = RoleRoot data } ->
|
|
{ root_location = location; inspection_data = data }
|
|
| { role = (RoleUsr _ | RoleSwap | RoleOther) } ->
|
|
invalid_arg "root_of_fs"
|
|
|
|
and inspect_get_mountpoints root_mountable =
|
|
let root = search_for_root root_mountable in
|
|
let fstab = root.inspection_data.fstab in
|
|
|
|
(* If no fstab information (Windows) return just the root. *)
|
|
if fstab = [] then
|
|
[ "/", root_mountable ]
|
|
else (
|
|
List.filter_map (
|
|
fun (mountable, mp) ->
|
|
if String.length mp > 0 && mp.[0] = '/' then
|
|
Some (mp, mountable)
|
|
else
|
|
None
|
|
) fstab
|
|
)
|
|
|
|
and inspect_get_filesystems root_mountable =
|
|
let root = search_for_root root_mountable in
|
|
let fstab = root.inspection_data.fstab in
|
|
|
|
(* If no fstab information (Windows) return just the root. *)
|
|
if fstab = [] then
|
|
[ root_mountable ]
|
|
else
|
|
List.map fst fstab
|
|
|
|
and inspect_get_format root = "installed"
|
|
|
|
and inspect_get_type root =
|
|
let root = search_for_root root in
|
|
match root.inspection_data.os_type with
|
|
| Some v -> string_of_os_type v
|
|
| None -> "unknown"
|
|
|
|
and inspect_get_distro root =
|
|
let root = search_for_root root in
|
|
match root.inspection_data.distro with
|
|
| Some v -> string_of_distro v
|
|
| None -> "unknown"
|
|
|
|
and inspect_get_package_format root =
|
|
let root = search_for_root root in
|
|
match root.inspection_data.package_format with
|
|
| Some v -> string_of_package_format v
|
|
| None -> "unknown"
|
|
|
|
and inspect_get_package_management root =
|
|
let root = search_for_root root in
|
|
match root.inspection_data.package_management with
|
|
| Some v -> string_of_package_management v
|
|
| None -> "unknown"
|
|
|
|
and inspect_get_product_name root =
|
|
let root = search_for_root root in
|
|
match root.inspection_data.product_name with
|
|
| Some v -> v
|
|
| None -> "unknown"
|
|
|
|
and inspect_get_product_variant root =
|
|
let root = search_for_root root in
|
|
match root.inspection_data.product_variant with
|
|
| Some v -> v
|
|
| None -> "unknown"
|
|
|
|
and inspect_get_major_version root =
|
|
let root = search_for_root root in
|
|
match root.inspection_data.version with
|
|
| Some (major, _) -> major
|
|
| None -> 0
|
|
|
|
and inspect_get_minor_version root =
|
|
let root = search_for_root root in
|
|
match root.inspection_data.version with
|
|
| Some (_, minor) -> minor
|
|
| None -> 0
|
|
|
|
and inspect_get_arch root =
|
|
let root = search_for_root root in
|
|
match root.inspection_data.arch with
|
|
| Some v -> v
|
|
| None -> "unknown"
|
|
|
|
and inspect_get_hostname root =
|
|
let root = search_for_root root in
|
|
match root.inspection_data.hostname with
|
|
| Some v -> v
|
|
| None -> "unknown"
|
|
|
|
and inspect_get_build_id root =
|
|
let root = search_for_root root in
|
|
match root.inspection_data.build_id with
|
|
| Some v -> v
|
|
| None -> "unknown"
|
|
|
|
and inspect_get_windows_systemroot root =
|
|
let root = search_for_root root in
|
|
match root.inspection_data.windows_systemroot with
|
|
| Some v -> v
|
|
| None ->
|
|
failwith "not a Windows guest, or systemroot could not be determined"
|
|
|
|
and inspect_get_windows_system_hive root =
|
|
let root = search_for_root root in
|
|
match root.inspection_data.windows_system_hive with
|
|
| Some v -> v
|
|
| None ->
|
|
failwith "not a Windows guest, or system hive not found"
|
|
|
|
and inspect_get_windows_software_hive root =
|
|
let root = search_for_root root in
|
|
match root.inspection_data.windows_software_hive with
|
|
| Some v -> v
|
|
| None ->
|
|
failwith "not a Windows guest, or software hive not found"
|
|
|
|
and inspect_get_windows_current_control_set root =
|
|
let root = search_for_root root in
|
|
match root.inspection_data.windows_current_control_set with
|
|
| Some v -> v
|
|
| 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
|
|
|
|
and inspect_is_multipart root = false
|
|
|
|
and inspect_get_drive_mappings root =
|
|
let root = search_for_root root in
|
|
root.inspection_data.drive_mappings
|
|
|
|
and search_for_root root =
|
|
let fses = !Inspect_types.inspect_fses in
|
|
if fses = [] then
|
|
failwith "no inspection data: call guestfs_inspect_os first";
|
|
|
|
let root =
|
|
try
|
|
List.find (
|
|
function
|
|
| { fs_location = { mountable = m }; role = RoleRoot _ } -> root = m
|
|
| _ -> false
|
|
) fses
|
|
with
|
|
Not_found ->
|
|
failwithf "%s: root device not found: only call this function with a root device previously returned by guestfs_inspect_os"
|
|
(Mountable.to_string root) in
|
|
|
|
root_of_fs root
|