Skip to content

Commit 0da6c54

Browse files
makubackiJavagedes
authored andcommitted
edk2toolext: Add patch var store
Adds a script that allows the UEFI variable store in a ROM file to be patched with variable data specified in an XML file. Signed-off-by: Michael Kubacki <[email protected]>
1 parent 4332007 commit 0da6c54

File tree

5 files changed

+400
-0
lines changed

5 files changed

+400
-0
lines changed

.cspell.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@
7878
"keyserver",
7979
"levelno",
8080
"linuxize",
81+
"manulipulations",
8182
"markdownlint",
8283
"mkdocs",
8384
"msft",
@@ -141,6 +142,7 @@
141142
"toollib",
142143
"uefi",
143144
"uncrustify",
145+
"unhexlify",
144146
"unittest",
145147
"updatelog",
146148
"urlunsplit",

docs/user/gen_api.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ def main() -> None:
3434
"nuget_publishing.py",
3535
"omnicache.py",
3636
"nuget.py",
37+
"patch_var_store.py",
3738
"versioninfo_tool.py",
3839
"versioninfo_helper.py",
3940
"secureboot_audit.py",

edk2toolext/uefi/patch_var_store.py

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
# @file
2+
#
3+
# Command-line tool to patch a variable store in a ROM image.
4+
5+
# Copyright (c) Microsoft Corporation
6+
#
7+
# SPDX-License-Identifier: BSD-2-Clause-Patent
8+
##
9+
10+
"""Tool for patching a variable store in a ROM image.
11+
12+
Patches the variable store in a ROM image based on a provided XML script file.
13+
"""
14+
15+
import argparse
16+
import base64
17+
import binascii
18+
import os
19+
import uuid
20+
import xml.etree.ElementTree as ET
21+
from typing import Dict, List
22+
23+
import edk2toollib.uefi.edk2.variable_format as VF
24+
import edk2toollib.uefi.edk2.variablestore_manulipulations as VarStore
25+
26+
27+
def GatherArguments() -> argparse.Namespace:
28+
"""Gather command-line arguments for the script.
29+
30+
Returns:
31+
argparse.Namespace: Parsed command-line arguments.
32+
"""
33+
parser = argparse.ArgumentParser(description="Process a command script to alter a ROM image varstore.")
34+
parser.add_argument("rom_file", type=str, help="The rom file that will be read and/or modified")
35+
parser.add_argument(
36+
"var_store_base",
37+
type=str,
38+
help="The hex offset of the var store FV within the ROM image",
39+
)
40+
parser.add_argument(
41+
"var_store_size",
42+
type=str,
43+
help="The hex size of the var store FV within the ROM image",
44+
)
45+
parser.add_argument(
46+
"-s",
47+
"--set",
48+
metavar="set_script",
49+
dest="set_file",
50+
type=str,
51+
help="A script file containing variables that should be updated or created in the variable store",
52+
)
53+
54+
return parser.parse_args()
55+
56+
57+
def load_variable_xml(xml_file_path: str) -> List[Dict]:
58+
"""Load the variable XML file and return a list of variables.
59+
60+
Args:
61+
xml_file_path (str): The XML file path to load.
62+
63+
Raises:
64+
Exception: If an unknown data type is found in the XML file.
65+
66+
Returns:
67+
list: A list of dictionaries containing variable information.
68+
"""
69+
loaded_xml_tree = ET.parse(xml_file_path)
70+
loaded_xml = loaded_xml_tree.getroot()
71+
72+
loaded_vars = []
73+
for node in loaded_xml.findall("Variable"):
74+
new_var = {
75+
"name": node.find("Name").text,
76+
"guid": uuid.UUID(node.find("GUID").text),
77+
"attributes": int(node.find("Attributes").text, 16),
78+
}
79+
80+
# Make sure we can process the data.
81+
DataType = node.find("Data").get("type")
82+
if DataType != "hex" and DataType != "b64":
83+
raise Exception("Unknown data type '%s' found!" % DataType)
84+
85+
# Update the node data.
86+
if DataType == "hex":
87+
new_var["data"] = binascii.unhexlify(node.find("Data").text.strip())
88+
89+
if DataType == "b64":
90+
new_var["data"] = base64.b64decode(node.find("Data").text.strip())
91+
92+
# Add the var to the list.
93+
loaded_vars.append(new_var)
94+
95+
return loaded_vars
96+
97+
98+
def patch_variables(set_vars: list[Dict], var_store: VarStore.VariableStore) -> List[str]:
99+
"""Patch UEFI variables in the variable store based on the provided list.
100+
101+
Args:
102+
set_vars (list[Dict]): List of dictionaries containing variable information.
103+
var_store (VarStore.VariableStore): The variable store to patch.
104+
105+
Returns:
106+
List[str]: List of variables that need to be created.
107+
"""
108+
create_vars = []
109+
110+
# Walk through all the script variables...
111+
# Then walk through all existing variables in the var store...
112+
# If we find a match, we can update it in place.
113+
# Otherwise, add it to a list to create later.
114+
for set_var in set_vars:
115+
match_found = False
116+
117+
for var in var_store.variables:
118+
if var.Name == set_var["name"] and var.VendorGuid == set_var["guid"] and var.State == VF.VAR_ADDED:
119+
print(f"Updating Var '{var.VendorGuid}:{var.Name}'...")
120+
121+
match_found = True
122+
var.Attributes = set_var["attributes"]
123+
var.set_data(set_var["data"])
124+
125+
if not match_found:
126+
create_vars.append(set_var)
127+
128+
return create_vars
129+
130+
131+
def create_variables(create_vars: list[Dict], var_store: VarStore.VariableStore) -> None:
132+
"""Create UEFI variables from a list of variable information.
133+
134+
Args:
135+
create_vars (list[Dict]): List of dictionaries containing variable information.
136+
var_store (VarStore.VariableStore): The variable store to add variables to.
137+
"""
138+
for create_var in create_vars:
139+
print(f"Creating Var '{create_var['guid']}:{create_var['name']}'...")
140+
141+
new_var = var_store.get_new_var_class()
142+
new_var.Attributes = create_var["attributes"]
143+
new_var.VendorGuid = create_var["guid"]
144+
new_var.set_name(create_var["name"])
145+
new_var.set_data(create_var["data"])
146+
147+
var_store.variables.append(new_var)
148+
149+
150+
def main() -> None:
151+
"""Main function to execute the script."""
152+
args = GatherArguments()
153+
154+
if args.set_file is not None and not os.path.isfile(args.set_file):
155+
raise Exception(f"Set Script path '{args.set_file}' does not point to a valid file!")
156+
157+
# Load the variable store from the file.
158+
var_store = VarStore.VariableStore(
159+
args.rom_file,
160+
store_base=int(args.var_store_base, 16),
161+
store_size=int(args.var_store_size, 16),
162+
)
163+
164+
# Print information about the current variables.
165+
for var in var_store.variables:
166+
if var.State == VF.VAR_ADDED:
167+
print(f"Var Found: '{var.VendorGuid}:{var.Name}'")
168+
169+
# Attempt to load the set script file.
170+
set_vars = load_variable_xml(args.set_file)
171+
172+
# Attempt to patch existing variables in the var store.
173+
create_vars = patch_variables(set_vars, var_store)
174+
175+
# If we had variables we were unable to update, let's create them now.
176+
create_variables(create_vars, var_store)
177+
178+
var_store.flush_to_file()
179+
180+
181+
if __name__ == "__main__":
182+
main()

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ stuart_parse = "edk2toolext.invocables.edk2_parse:main"
8181
stuart_report = "edk2toolext.edk2_report:main"
8282
omnicache = "edk2toolext.omnicache:main"
8383
nuget-publish = "edk2toolext.nuget_publishing:go"
84+
patch_var_store_tool = "edk2toolext.uefi.patch_var_store:main"
8485
sig_db_tool = "edk2toolext.uefi.sig_db_tool:main"
8586
firmware_policy_tool = "edk2toolext.windows.policy.firmware_policy_tool:main"
8687
edk2_capsule_tool = "edk2toolext.capsule.capsule_tool:main"

0 commit comments

Comments
 (0)