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)) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## 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. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -16,7 +16,6 @@ var (
|
||||
type UUIDSource string
|
||||
|
||||
const (
|
||||
UUIDSourceInvalid = "UUID_Source_Invalid"
|
||||
UUIDSourceWMI = "UUID_Source_WMI"
|
||||
UUIDSourceHardware = "UUID_Source_Hardware"
|
||||
)
|
||||
|
||||
@@ -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() {
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user