Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Linux Variable Writing Script #353

Merged
merged 10 commits into from
May 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,9 @@
"profilenames",
"profileid",
"profileids",
"prettyname"
"prettyname",
"efivars",
"efivarfs",
"chattr"
]
}
5 changes: 4 additions & 1 deletion SetupDataPkg/SetupDataPkg.ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,10 @@
"dmpstore",
"mschange",
"DDTHH",
"prettyname"
"prettyname",
"efivars",
"efivarfs",
"chattr"
], # words to extend to the dictionary for this package
"IgnoreStandardPaths": [], # Standard Plugin defined paths that should be ignore
"AdditionalIncludePaths": [] # Additional paths to spell check (wildcards supported)
Expand Down
20 changes: 14 additions & 6 deletions SetupDataPkg/Tools/ReadUefiVarsToConfVarList.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@
import struct
import uuid
import ctypes
from SettingSupport.UefiVariablesSupportLib import UefiVariable
if os.name == 'nt':
from SettingSupport.UefiVariablesSupportLib import UefiVariable
else:
from SettingSupport.UefiVariablesSupportLinuxLib import UefiVariable
from VariableList import Schema, UEFIVariable, create_vlist_buffer


Expand Down Expand Up @@ -53,7 +56,7 @@ def option_parser():
# array
#
def read_variable_into_variable_list(uefi_var, name, namespace):
(rc, var, _) = uefi_var.GetUefiVar(name, namespace)
(rc, var) = uefi_var.GetUefiVar(name, namespace)
if rc != 0:
if rc != UefiVariable.ERROR_ENVVAR_NOT_FOUND:
# only log the errors other than EFI_NOT_FOUND, because not found is normal in this case...
Expand All @@ -78,7 +81,7 @@ def main():
ret = b''
if arguments.configuration_file is None:
# Read all the variables
(rc, efi_var_names, error_string) = UefiVar.GetUefiAllVarNames()
(rc, efi_var_names) = UefiVar.GetUefiAllVarNames()
if rc != 0:
logging.error(f"Error returned from GetUefiAllVarNames: {rc}")

Expand Down Expand Up @@ -117,9 +120,14 @@ def main():
console.setLevel(logging.CRITICAL)

# check the privilege level and report error
if not ctypes.windll.shell32.IsUserAnAdmin():
print("Administrator privilege required. Please launch from an Administrator privilege level.")
sys.exit(1)
if os.name == 'nt':
if not ctypes.windll.shell32.IsUserAnAdmin():
print("Administrator privilege required. Please launch from an Administrator privilege level.")
sys.exit(1)
else:
if os.geteuid() != 0:
print("Root permission required, please run script with sudo.")
sys.exit(1)

# call main worker function
retcode = main()
Expand Down
34 changes: 5 additions & 29 deletions SetupDataPkg/Tools/SettingSupport/UefiVariablesSupportLib.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
c_wchar_p,
c_void_p,
c_int,
c_char,
create_string_buffer,
WinError,
pointer
Expand Down Expand Up @@ -90,27 +89,6 @@ def __init__(self):
)
pass

#
# Helper function to create buffer for var read/write
#
def CreateBuffer(self, init, size=None):
"""CreateBuffer(aString) -> character array
CreateBuffer(anInteger) -> character array
CreateBuffer(aString, anInteger) -> character array
"""
if isinstance(init, str):
if size is None:
size = len(init) + 1
buftype = c_char * size
buf = buftype()
buf.value = init
return buf
elif isinstance(init, int):
buftype = c_char * init
buf = buftype()
return buf
raise TypeError(init)

#
# Function to get variable
# return a tuple of error code and variable data as string
Expand All @@ -135,8 +113,8 @@ def GetUefiVar(self, name, guid):
)
logging.error(WinError())
if efi_var is None:
return (err, None, WinError(err))
return (err, efi_var[:length], WinError(err))
return (err, None)
return (err, efi_var[:length])

#
# Function to get all variable names
Expand Down Expand Up @@ -176,8 +154,8 @@ def GetUefiAllVarNames(self):
logging.error(
"EnumerateFirmwareEnvironmentVariable failed (GetLastError = 0x%x)" % status
)
return (status, None, WinError(status))
return (status, efi_var_names, None)
return (status, None)
return (status, efi_var_names)

#
# Function to set variable
Expand All @@ -186,7 +164,6 @@ def GetUefiAllVarNames(self):
def SetUefiVar(self, name, guid, var=None, attrs=None):
var_len = 0
err = 0
error_string = None
if var is None:
var = bytes(0)
else:
Expand Down Expand Up @@ -221,5 +198,4 @@ def SetUefiVar(self, name, guid, var=None, attrs=None):
"SetFirmwareEnvironmentVariable failed (GetLastError = 0x%x)" % err
)
logging.error(WinError())
error_string = WinError(err)
return (success, err, error_string)
return success
143 changes: 143 additions & 0 deletions SetupDataPkg/Tools/SettingSupport/UefiVariablesSupportLinuxLib.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
# @file
#
# Python lib to support Reading and writing UEFI variables from Linux
#
#
# Copyright (c), Microsoft Corporation
# SPDX-License-Identifier: BSD-2-Clause-Patent
#
# GetUefiAllVarNames is based on information from
# https://github.com/awslabs/python-uefivars/blob/main/pyuefivars/efivarfs.py
#
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: MIT

import os
import uuid
import struct

from ctypes import (
create_string_buffer
)

EFI_VAR_MAX_BUFFER_SIZE = 1024 * 1024


class UefiVariable(object):
ERROR_ENVVAR_NOT_FOUND = 0xcb

def __init__(self):
pass

#
# Function to get variable
# return a tuple of error code and variable data as string
#
def GetUefiVar(self, name, guid):
# success
err = 0
# the variable name is VariableName-Guid
path = '/sys/firmware/efi/efivars/' + name + '-%s' % guid

if not os.path.exists(path):
err = UefiVariable.ERROR_ENVVAR_NOT_FOUND
return (err, None)

efi_var = create_string_buffer(EFI_VAR_MAX_BUFFER_SIZE)
with open(path, 'rb') as fd:
efi_var = fd.read()

return (err, efi_var)

#
# Function to get all variable names
# return a tuple of error code and variable names byte array formatted as:
#
# typedef struct _VARIABLE_NAME {
# ULONG NextEntryOffset;
# GUID VendorGuid;
# WCHAR Name[ANYSIZE_ARRAY];
# } VARIABLE_NAME, *PVARIABLE_NAME;
#
def GetUefiAllVarNames(self):
# success
status = 0

# implementation borrowed from https://github.com/awslabs/python-uefivars/blob/main/pyuefivars/efivarfs.py
apop5 marked this conversation as resolved.
Show resolved Hide resolved
path = '/sys/firmware/efi/efivars'
if not os.path.exists(path):
status = UefiVariable.ERROR_ENVVAR_NOT_FOUND
return (status, None)

vars = os.listdir(path)

# get the total buffer length, converting to unicode
length = 0
offset = 0
for var in vars:
split_string = var.split('-')
name = '-'.join(split_string[:-5])
name = name.encode('utf-16-le')
name_len = len(name)
length += (4 + 16 + name_len)

efi_var_names = create_string_buffer(length)

for var in vars:
# efivarfs stores vars as NAME-GUID
split_string = var.split('-')
try:
# GUID is last 5 elements of split_string
guid = uuid.UUID('-'.join(split_string[-5:])).bytes_le
except ValueError:
raise Exception(f'Could not parse "{var}"')

# the other part is the name
name = '-'.join(split_string[:-5])
name = name.encode('utf-16-le')
name_len = len(name)

# NextEntryOffset
struct.pack_into('<I', efi_var_names, offset, 4 + 16 + name_len)
offset += 4

# VendorGuid
struct.pack_into('=16s', efi_var_names, offset, guid)
offset += 16

# Name
struct.pack_into(f'={name_len}s', efi_var_names, offset, name)
offset += name_len

return (status, efi_var_names)

#
# Function to set variable
# return a tuple of boolean status, error_code, error_string (None if not error)
#
def SetUefiVar(self, name, guid, var=None, attrs=None):
success = 0 # Fail

# There is a null terminator at the end of the name
path = '/sys/firmware/efi/efivars/' + name[:-1] + '-' + str(guid)
if var is None:
# we are deleting the variable
if (os.path.exists(path)):
os.remove(path)
success = 1 # expect non-zero success
return success

if attrs is None:
attrs = 0x7

# if the file exists, remove the immutable flag
if (os.path.exists(path)):
os.system('sudo chattr -i ' + path)

with open(path, 'wb') as fd:
# var data is attribute (UINT32) followed by data
packed = struct.pack('=I', attrs)
packed += var
fd.write(packed)

return 1
18 changes: 13 additions & 5 deletions SetupDataPkg/Tools/WriteConfVarListToUefiVars.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@
import struct
import uuid
import ctypes
from SettingSupport.UefiVariablesSupportLib import UefiVariable
if os.name == 'nt':
from SettingSupport.UefiVariablesSupportLib import UefiVariable
else:
from SettingSupport.UefiVariablesSupportLinuxLib import UefiVariable

gEfiGlobalVariableGuid = "8BE4DF61-93CA-11D2-AA0D-00E098032B8C"

Expand Down Expand Up @@ -93,7 +96,7 @@ def extract_single_var_from_file_and_write_nvram(var):
logging.debug(f"Found Variable: {VarName} {Guid} {Attributes}")

UefiVar = UefiVariable()
(rc, err, error_string) = UefiVar.SetUefiVar(
rc = UefiVar.SetUefiVar(
VarName,
Guid,
Data,
Expand Down Expand Up @@ -136,9 +139,14 @@ def main():
console.setLevel(logging.CRITICAL)

# check the privilege level and report error
if not ctypes.windll.shell32.IsUserAnAdmin():
print("Administrator privilege required. Please launch from an Administrator privilege level.")
sys.exit(1)
if os.name == 'nt':
if not ctypes.windll.shell32.IsUserAnAdmin():
print("Administrator privilege required. Please launch from an Administrator privilege level.")
sys.exit(1)
else:
if os.geteuid() != 0:
print("Root permission required, please run script with sudo.")
sys.exit(1)

# call main worker function
retcode = main()
Expand Down
Loading