google_guest_agent/uefi/uefi_windows.go (105 lines of code) (raw):
// Copyright 2023 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// https://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package uefi provides utility functions to read UEFI variables.
package uefi
import (
"fmt"
"syscall"
"unsafe"
"golang.org/x/sys/windows"
"github.com/GoogleCloudPlatform/guest-logging-go/logger"
)
var (
// https://en.wikipedia.org/wiki/Microsoft_Windows_library_files#KERNEL32.DLL
kernelDLL = windows.NewLazySystemDLL("kernel32.dll")
// https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getcurrentprocess
// procGetCurrentProcess retrieves a pseudo handle for the current process.
procGetCurrentProcess = kernelDLL.NewProc("GetCurrentProcess")
// https://learn.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-closehandle
// procCloseHandle closes an open process object handle.
procCloseHandle = kernelDLL.NewProc("CloseHandle")
// https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-getfirmwareenvironmentvariablew
// procGetFirmwareEnvironmentVariableW retrieves the value of the specified UEFI.
procGetFirmwareEnvironmentVariableW = kernelDLL.NewProc("GetFirmwareEnvironmentVariableW")
// https://en.wikipedia.org/wiki/Microsoft_Windows_library_files#ADVAPI32.DLL
advapiDLL = windows.NewLazySystemDLL("advapi32.dll")
// https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-openprocesstoken
// procOpenProcessToken opens the access token (contains the security information for a logon session) associated for a process.
// Token identifies the user, the user's groups, and the user's privileges.
procOpenProcessToken = advapiDLL.NewProc("OpenProcessToken")
// https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-lookupprivilegevaluew
// procLookupPrivilegeValueW is used to retrieve the locally unique identifier (LUID)
procLookupPrivilegeValueW = advapiDLL.NewProc("LookupPrivilegeValueW")
// https://learn.microsoft.com/en-us/windows/win32/api/securitybaseapi/nf-securitybaseapi-adjusttokenprivileges
// procAdjustTokenPrivileges is used for enabling the privileges on the access token.
procAdjustTokenPrivileges = advapiDLL.NewProc("AdjustTokenPrivileges")
)
const (
// SE_SYSTEM_ENVIRONMENT_NAME is the privilege required to read a firmware environment variable.
SE_SYSTEM_ENVIRONMENT_NAME = "SeSystemEnvironmentPrivilege"
// PROC_TOKEN_ADJUST_PRIVILEGES is access required to change the specified privileges.
PROC_TOKEN_ADJUST_PRIVILEGES = 0x0020
// PROC_SE_PRIVILEGE_ENABLED is privilege attribute used with LUID_AND_ATTRIBUTES stating to
// enable the specified privilege.
PROC_SE_PRIVILEGE_ENABLED = 0x00000002
)
// https://learn.microsoft.com/en-us/windows/win32/api/ntdef/ns-ntdef-luid
// LUID is the opaque identifier structure that is guaranteed to be unique on the local machine.
// It is used to locally represent the privilege name (e.g. SeSystemEnvironmentPrivilege in this case).
type LUID struct {
LowPart uint32
HighPart int32
}
// https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-luid_and_attributes
// LUID_AND_ATTRIBUTES structure represents a locally unique identifier (LUID) and its attributes.
type LUID_AND_ATTRIBUTES struct {
LUID LUID
Attributes uint32
}
// https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-token_privileges
// TOKEN_PRIVILEGES is the structure that contains information about a set of privileges for an access token.
type TOKEN_PRIVILEGES struct {
PrivilegeCount uint32
Privileges [1]LUID_AND_ATTRIBUTES
}
// ReadVariable reads UEFI variable and returns as byte array.
// Throws an error if variable is invalid or empty.
func ReadVariable(v VariableName) (*Variable, error) {
logger.Debugf("Enabling required %s priviliges for agent process", SE_SYSTEM_ENVIRONMENT_NAME)
if err := enablePrivilege(SE_SYSTEM_ENVIRONMENT_NAME); err != nil {
return nil, err
}
name := unsafe.Pointer(syscall.StringToUTF16Ptr(v.Name))
guid := unsafe.Pointer(syscall.StringToUTF16Ptr("{" + v.GUID + "}"))
buffer := make([]byte, 1024)
// This call returns number of bytes written to the output buffer, unused, error
size, _, err := procGetFirmwareEnvironmentVariableW.Call(
uintptr(name),
uintptr(guid),
uintptr(unsafe.Pointer(&buffer[0])),
uintptr(uint32(len(buffer))),
)
if size == uintptr(0) {
return nil, fmt.Errorf("unable to read UEFI variable %+v: %w", v, err)
}
return &Variable{
Name: v,
Attributes: []byte{},
Content: buffer[:size],
}, nil
}
// enablePrivilege enables the specified privilege for current process.
func enablePrivilege(name string) error {
// Get current process handle.
handle, _, err := procGetCurrentProcess.Call()
if handle == uintptr(0) {
return fmt.Errorf("unable to get current process handle: %w", err)
}
defer procCloseHandle.Call(handle)
// Get access token that contains the privileges to be modified for the current process.
var tHandle uintptr
opRes, _, err := procOpenProcessToken.Call(
uintptr(handle),
uintptr(uint32(PROC_TOKEN_ADJUST_PRIVILEGES)),
uintptr(unsafe.Pointer(&tHandle)),
)
if opRes == uintptr(0) {
return fmt.Errorf("unable to open current process token: %w", err)
}
defer procCloseHandle.Call(tHandle)
// Generate a pointer to a null-terminated string that specifies the name of the privilege.
namePtr, err := syscall.UTF16PtrFromString(name)
if err != nil {
return fmt.Errorf("unable to encode privilege name(%s) to UTF16: %w", name, err)
}
// Retrieve the LUID for the required privilege.
var luid LUID
lpRes, _, err := procLookupPrivilegeValueW.Call(
uintptr(0),
uintptr(unsafe.Pointer(namePtr)),
uintptr(unsafe.Pointer(&luid)),
)
if lpRes == uintptr(0) {
return fmt.Errorf("unable to lookup LUID for privilege %q: %w", name, err)
}
newState := TOKEN_PRIVILEGES{PrivilegeCount: 1}
newState.Privileges[0] = LUID_AND_ATTRIBUTES{
LUID: luid,
Attributes: PROC_SE_PRIVILEGE_ENABLED,
}
// Enable specified privilege on the current process.
ajRes, _, err := procAdjustTokenPrivileges.Call(
uintptr(tHandle),
uintptr(uint32(0)),
uintptr(unsafe.Pointer(&newState)),
uintptr(uint32(0)),
uintptr(0),
uintptr(0),
)
if ajRes == uintptr(0) {
return fmt.Errorf("unable to set privilege %q: %w", name, err)
}
return nil
}