From 9aa4a3375b0da5c5968d6941bf8e76e9cf75bb8d Mon Sep 17 00:00:00 2001 From: Lucas Manuel Rodriguez Date: Thu, 16 Jul 2026 11:42:28 -0300 Subject: [PATCH] Remove the wmic.exe dependency in mdm_bridge table (#49296) Resolves #34311. It's not urgent because: - Orbit uses a fallback mechanism to fetch the device UUID (using SMBIOS): https://github.com/fleetdm/fleet/blob/d3092bbc640ebd8e92c13476f0ca8772b98d425e/orbit/pkg/platform/platform_windows.go#L354-L359 - Only used by the `mdm_bridge` table implementation. Which is only used by CIS policies (not for critical MDM functionality). - [X] Changes file added for user-visible changes in `changes/`, `orbit/changes/` or `ee/fleetd-chrome/changes`. ## Testing - [X] QA'd all new/changed functionality manually Tested on both a Windows 11 VM with 25H2 and real Windows 11 device with 23H2. The extracted UUID matches the UUID reported by osquery. ## fleetd/orbit/Fleet Desktop - [X] Verified compatibility with the latest released version of Fleet (see [Must rule](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/workflows/fleetd-development-and-release-strategy.md)) - [X] If the change applies to only one platform, confirmed that `runtime.GOOS` is used as needed to isolate changes - [X] Verified that fleetd runs on macOS, Linux and Windows - [X] Verified auto-update works from the released version of component to the new version (see [tools/tuf/test](../tools/tuf/test/README.md)) ## Summary by CodeRabbit - **Bug Fixes** - Updated Windows device identification to obtain the system UUID using COM-based WMI querying instead of relying on the deprecated WMIC utility. - Removed the WMIC dependency from the MDM bridge table implementation. - Improved cross-platform UUID handling by removing unused non-Windows UUID placeholder logic and related constants. --- ...emove-wmic-dependency-for-mdm_bridge-table | 1 + orbit/pkg/platform/platform.go | 1 - orbit/pkg/platform/platform_notwindows.go | 4 - orbit/pkg/platform/platform_windows.go | 104 ++++++++++++++++-- 4 files changed, 94 insertions(+), 16 deletions(-) create mode 100644 orbit/changes/34311-remove-wmic-dependency-for-mdm_bridge-table diff --git a/orbit/changes/34311-remove-wmic-dependency-for-mdm_bridge-table b/orbit/changes/34311-remove-wmic-dependency-for-mdm_bridge-table new file mode 100644 index 0000000000..7d98e903e2 --- /dev/null +++ b/orbit/changes/34311-remove-wmic-dependency-for-mdm_bridge-table @@ -0,0 +1 @@ +* Removed the wmic.exe dependency in the `mdm_bridge` table implementation. diff --git a/orbit/pkg/platform/platform.go b/orbit/pkg/platform/platform.go index 7ce2866536..09ed769d64 100644 --- a/orbit/pkg/platform/platform.go +++ b/orbit/pkg/platform/platform.go @@ -16,7 +16,6 @@ var ( type UUIDSource string const ( - UUIDSourceInvalid = "UUID_Source_Invalid" UUIDSourceWMI = "UUID_Source_WMI" UUIDSourceHardware = "UUID_Source_Hardware" ) diff --git a/orbit/pkg/platform/platform_notwindows.go b/orbit/pkg/platform/platform_notwindows.go index 4d574e4f81..ac154d4f7d 100644 --- a/orbit/pkg/platform/platform_notwindows.go +++ b/orbit/pkg/platform/platform_notwindows.go @@ -87,10 +87,6 @@ func GetProcessesByName(name string) ([]*gopsutil_process.Process, error) { return foundProcesses, nil } -func GetSMBiosUUID() (string, UUIDSource, error) { - return "", UUIDSourceInvalid, errors.New("not implemented.") -} - // RunUpdateQuirks is a no-op on non-windows platforms func PreUpdateQuirks() { } diff --git a/orbit/pkg/platform/platform_windows.go b/orbit/pkg/platform/platform_windows.go index bb190eda4f..151064383e 100644 --- a/orbit/pkg/platform/platform_windows.go +++ b/orbit/pkg/platform/platform_windows.go @@ -10,6 +10,7 @@ import ( "os" "os/exec" "path/filepath" + "runtime" "strings" "syscall" "time" @@ -17,6 +18,8 @@ import ( "github.com/digitalocean/go-smbios/smbios" "github.com/fleetdm/fleet/v4/orbit/pkg/constant" + "github.com/go-ole/go-ole" + "github.com/go-ole/go-ole/oleutil" "github.com/google/uuid" "github.com/hectane/go-acl" "github.com/rs/zerolog/log" @@ -224,22 +227,101 @@ func GetProcessesByName(name string) ([]*gopsutil_process.Process, error) { return processes, nil } -// It obtains the BIOS UUID by calling "cmd.exe /c wmic csproduct get UUID" and parsing the results +// wmiGetSMBiosUUID obtains the BIOS/hardware UUID by querying the WMI +// Win32_ComputerSystemProduct class directly over COM. +// +// This replaces the previous implementation that shelled out to +// "wmic csproduct get UUID". The wmic.exe CLI is removed as of Windows 11 25H2 +// (https://github.com/fleetdm/fleet/issues/34311), but the underlying WMI +// service and Win32_ComputerSystemProduct class remain available. Querying WMI +// over COM returns the exact same UUID string wmic did, so an existing host's +// UUID — and the SHA256 hash derived from it for Windows MDM local management +// registration — is unchanged. func wmiGetSMBiosUUID() (string, error) { - args := []string{"/C", "wmic csproduct get UUID"} - out, err := exec.Command("cmd", args...).Output() + // COM calls must be issued from a thread that has been initialized with + // CoInitializeEx, so pin this goroutine to its OS thread for the duration. + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + if err := ole.CoInitializeEx(0, ole.COINIT_MULTITHREADED); err != nil { + var code uintptr + if oleErr, ok := errors.AsType[*ole.OleError](err); ok { + code = oleErr.Code() + } + switch code { + case uintptr(windows.S_FALSE): + // COM was already initialized on this thread with the same model; + // our call still counts as a reference that must be balanced. + defer ole.CoUninitialize() + case uintptr(windows.RPC_E_CHANGED_MODE): + // COM was already initialized with a different concurrency model. + // We can still make calls, but must not uninitialize it. + default: + return "", fmt.Errorf("CoInitializeEx: %w", err) + } + } else { + defer ole.CoUninitialize() + } + + unknown, err := oleutil.CreateObject("WbemScripting.SWbemLocator") if err != nil { - return "", err + return "", fmt.Errorf("create SWbemLocator: %w", err) } - uuidOutputStr := string(out) - if len(uuidOutputStr) == 0 { - return "", errors.New("get UUID: output from wmi is empty") + defer unknown.Release() + + locator, err := unknown.QueryInterface(ole.IID_IDispatch) + if err != nil { + return "", fmt.Errorf("query IDispatch: %w", err) } - outputByLines := strings.Split(strings.TrimRight(uuidOutputStr, "\n"), "\n") - if len(outputByLines) < 2 { - return "", errors.New("get UUID: unexpected output") + defer locator.Release() + + serviceRaw, err := oleutil.CallMethod(locator, "ConnectServer", nil, `\\.\ROOT\CIMV2`) + if err != nil { + return "", fmt.Errorf("connect to WMI: %w", err) } - return strings.TrimSpace(outputByLines[1]), nil + service := serviceRaw.ToIDispatch() + defer service.Release() + + resultRaw, err := oleutil.CallMethod(service, "ExecQuery", "SELECT UUID FROM Win32_ComputerSystemProduct") + if err != nil { + return "", fmt.Errorf("execute WMI query: %w", err) + } + result := resultRaw.ToIDispatch() + defer result.Release() + + itemRaw, err := oleutil.CallMethod(result, "ItemIndex", 0) + if err != nil { + return "", fmt.Errorf("fetch WMI result row: %w", err) + } + item := itemRaw.ToIDispatch() + defer item.Release() + + uuidVariant, err := oleutil.GetProperty(item, "UUID") + if err != nil { + return "", fmt.Errorf("read UUID property: %w", err) + } + defer func() { _ = uuidVariant.Clear() }() + + uuidStr := strings.TrimSpace(uuidVariant.ToString()) + if uuidStr == "" { + return "", errors.New("get UUID: WMI returned an empty UUID") + } + + // Reject documented placeholder/filler UUIDs (all-zero, all-0xFF) that some + // firmware reports. WMI sources the UUID from the same SMBIOS System + // Information structure as the hardware fallback, so returning a sentinel + // value here would let it be used as the device UUID instead of falling + // through to hardwareGetSMBiosUUID (which already rejects these). Reuse the + // same sentinel check for parity between both paths. + parsedUUID, err := uuid.Parse(uuidStr) + if err != nil { + return "", fmt.Errorf("parse WMI UUID: %w", err) + } + if valid, err := isValidUUID(parsedUUID[:]); !valid { + return "", fmt.Errorf("get UUID: WMI returned an unusable UUID: %w", err) + } + + return uuidStr, nil } // It performs a UUID sanity check on a given byte array