Skip to content

Commit 3e51ee2

Browse files
corthonBret Barkelew
and
Bret Barkelew
authored
Helper classes for variable attributes and policies (#128)
uefi: Add helper class for Variable Attributes uefi/edk2: Add helper classes to work with VariablePolicy structures Co-authored-by: Bret Barkelew <[email protected]>
1 parent b019ce4 commit 3e51ee2

File tree

4 files changed

+354
-0
lines changed

4 files changed

+354
-0
lines changed
+188
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
# @file
2+
# Module contains helper classes and functions to work with Variable Policy structures
3+
# and substructures.
4+
#
5+
# Copyright (c) Microsoft Corporation
6+
#
7+
# SPDX-License-Identifier: BSD-2-Clause-Patent
8+
##
9+
10+
import uuid
11+
import struct
12+
from edk2toollib.uefi.uefi_multi_phase import EfiVariableAttributes
13+
14+
15+
class VariableLockOnVarStatePolicy(object):
16+
# typedef struct {
17+
# EFI_GUID Namespace;
18+
# UINT8 Value;
19+
# UINT8 Reserved;
20+
# // CHAR16 Name[]; // Variable Length Field
21+
# } VARIABLE_LOCK_ON_VAR_STATE_POLICY;
22+
_HdrStructFormat = "<16sBB"
23+
_HdrStructSize = struct.calcsize(_HdrStructFormat)
24+
25+
def __init__(self):
26+
self.Namespace = uuid.UUID(bytes=b'\x00' * 16)
27+
self.Value = 0
28+
self.Name = None
29+
30+
def __str__(self):
31+
return "VARIABLE_LOCK_ON_VAR_STATE_POLICY(%s, %d, %s)" % (self.Namespace, self.Value, self.Name)
32+
33+
def decode(self, buffer):
34+
"""
35+
load this object from a bytes buffer
36+
37+
return any remaining buffer
38+
"""
39+
(_namespace, self.Value, _) = struct.unpack(
40+
self._HdrStructFormat, buffer[:self._HdrStructSize])
41+
42+
self.Namespace = uuid.UUID(bytes_le=_namespace)
43+
44+
# Scan the rest of the buffer for a \x00\x00 to terminate the string.
45+
buffer = buffer[self._HdrStructSize:]
46+
if len(buffer) < 4:
47+
raise ValueError("Buffer too short!")
48+
49+
string_end = None
50+
for i in range(0, len(buffer), 2):
51+
if buffer[i] == 0 and buffer[i + 1] == 0:
52+
string_end = i + 2
53+
break
54+
55+
if string_end is None:
56+
raise ValueError("String end not detected!")
57+
58+
self.Name = buffer[:string_end].decode('utf-16').strip('\x00')
59+
60+
return buffer[string_end:]
61+
62+
63+
class VariablePolicyEntry(object):
64+
# typedef struct {
65+
# UINT32 Version;
66+
# UINT16 Size;
67+
# UINT16 OffsetToName;
68+
# EFI_GUID Namespace;
69+
# UINT32 MinSize;
70+
# UINT32 MaxSize;
71+
# UINT32 AttributesMustHave;
72+
# UINT32 AttributesCantHave;
73+
# UINT8 LockPolicyType;
74+
# UINT8 Reserved[3];
75+
# // UINT8 LockPolicy[]; // Variable Length Field
76+
# // CHAR16 Name[] // Variable Length Field
77+
# } VARIABLE_POLICY_ENTRY;
78+
_HdrStructFormat = "<IHH16sIIIIB3s" # spell-checker:disable-line
79+
_HdrStructSize = struct.calcsize(_HdrStructFormat)
80+
81+
ENTRY_REVISION = 0x0001_0000
82+
83+
NO_MIN_SIZE = 0
84+
NO_MAX_SIZE = 0xFFFF_FFFF
85+
NO_MUST_ATTR = 0
86+
NO_CANT_ATTR = 0
87+
88+
TYPE_NO_LOCK = 0
89+
TYPE_LOCK_NOW = 1
90+
TYPE_LOCK_ON_CREATE = 2
91+
TYPE_LOCK_ON_VAR_STATE = 3
92+
93+
LOCK_POLICY_STRING_MAP = {
94+
TYPE_NO_LOCK: "NONE",
95+
TYPE_LOCK_NOW: "NOW",
96+
TYPE_LOCK_ON_CREATE: "ON_CREATE",
97+
TYPE_LOCK_ON_VAR_STATE: "ON_VAR_STATE",
98+
}
99+
100+
def __init__(self):
101+
self.Version = VariablePolicyEntry.ENTRY_REVISION
102+
self.Size = VariablePolicyEntry._HdrStructSize
103+
self.OffsetToName = self.Size
104+
self.Namespace = uuid.UUID(bytes=b'\x00' * 16)
105+
self.MinSize = VariablePolicyEntry.NO_MIN_SIZE
106+
self.MaxSize = VariablePolicyEntry.NO_MAX_SIZE
107+
self.AttributesMustHave = VariablePolicyEntry.NO_MUST_ATTR
108+
self.AttributesCantHave = VariablePolicyEntry.NO_CANT_ATTR
109+
self.LockPolicyType = VariablePolicyEntry.TYPE_NO_LOCK
110+
self.LockPolicy = None
111+
self.Name = None
112+
113+
def __str__(self):
114+
result = "VARIABLE_POLICY_ENTRY(%s, %s)\n" % (self.Namespace, self.Name)
115+
116+
if self.LockPolicyType in (VariablePolicyEntry.TYPE_NO_LOCK,
117+
VariablePolicyEntry.TYPE_LOCK_NOW,
118+
VariablePolicyEntry.TYPE_LOCK_ON_CREATE):
119+
result += "\tLock = %s\n" % VariablePolicyEntry.LOCK_POLICY_STRING_MAP[self.LockPolicyType]
120+
elif self.LockPolicyType is VariablePolicyEntry.TYPE_LOCK_ON_VAR_STATE:
121+
result += "\tLock = %s\n" % self.LockPolicy
122+
123+
result += "\tMin = 0x%08X, Max = 0x%08X, Must = 0x%08X, Cant = 0x%08X\n" % (
124+
self.MinSize, self.MaxSize, self.AttributesMustHave, self.AttributesCantHave)
125+
126+
return result
127+
128+
@staticmethod
129+
def csv_header():
130+
"""returns a list containing the names of the ordered columns that are produced by csv_row()"""
131+
return ['Namespace', 'Name', 'LockPolicyType', 'VarStateNamespace', 'VarStateName',
132+
'VarStateValue', 'MinSize', 'MaxSize', 'AttributesMustHave', 'AttributesCantHave']
133+
134+
def csv_row(self, guid_xref: dict = None):
135+
"""
136+
returns a list containing the elements of this structure (in the same order as the csv_header)
137+
ready to be written to a csv file
138+
139+
guid_xref - a dictionary of GUID/name substitutions where the key is a uuid object
140+
and the value is a string
141+
"""
142+
if guid_xref is None:
143+
guid_xref = {}
144+
145+
result = [guid_xref.get(self.Namespace, self.Namespace),
146+
self.Name, VariablePolicyEntry.LOCK_POLICY_STRING_MAP[self.LockPolicyType]]
147+
148+
if self.LockPolicyType in (VariablePolicyEntry.TYPE_NO_LOCK,
149+
VariablePolicyEntry.TYPE_LOCK_NOW,
150+
VariablePolicyEntry.TYPE_LOCK_ON_CREATE):
151+
result += ['N/A', 'N/A', 'N/A']
152+
elif self.LockPolicyType is VariablePolicyEntry.TYPE_LOCK_ON_VAR_STATE:
153+
result += [guid_xref.get(self.LockPolicy.Namespace, self.LockPolicy.Namespace),
154+
self.LockPolicy.Name, self.LockPolicy.Value]
155+
156+
result += ["0x%08X" % self.MinSize,
157+
"0x%08X" % self.MaxSize,
158+
str(EfiVariableAttributes(self.AttributesMustHave)),
159+
str(EfiVariableAttributes(self.AttributesCantHave))]
160+
161+
return result
162+
163+
def decode(self, buffer):
164+
"""
165+
load this object from a bytes buffer
166+
167+
return any remaining buffer
168+
"""
169+
(self.Version, self.Size, self.OffsetToName, _namespace,
170+
self.MinSize, self.MaxSize, self.AttributesMustHave,
171+
self.AttributesCantHave, self.LockPolicyType, _) = struct.unpack(
172+
self._HdrStructFormat, buffer[:self._HdrStructSize])
173+
174+
if self.Version != VariablePolicyEntry.ENTRY_REVISION:
175+
raise ValueError("Unknown structure version!")
176+
if self.LockPolicyType not in VariablePolicyEntry.LOCK_POLICY_STRING_MAP:
177+
raise ValueError("Unknown LockPolicyType!")
178+
179+
self.Namespace = uuid.UUID(bytes_le=_namespace)
180+
181+
if self.OffsetToName != self.Size:
182+
self.Name = buffer[self.OffsetToName:self.Size].decode('utf-16').strip('\x00')
183+
184+
if self.LockPolicyType == VariablePolicyEntry.TYPE_LOCK_ON_VAR_STATE:
185+
self.LockPolicy = VariableLockOnVarStatePolicy()
186+
self.LockPolicy.decode(buffer[self._HdrStructSize:self.OffsetToName])
187+
188+
return buffer[self.Size:]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
# @file
2+
# Unit test harness for the VariablePolicy module/classes.
3+
#
4+
# Copyright (c) Microsoft Corporation
5+
# SPDX-License-Identifier: BSD-2-Clause-Patent
6+
##
7+
8+
9+
import unittest
10+
import uuid
11+
from edk2toollib.uefi.edk2.variable_policy import VariableLockOnVarStatePolicy, VariablePolicyEntry
12+
13+
14+
TEST_GUID_1 = uuid.UUID("48B5F961-3F7D-4B88-9BEE-D305ED8256DA")
15+
TEST_GUID_2 = uuid.UUID("65D16747-FCBC-4FAE-A727-7B679A7B23F9")
16+
17+
TEST_POLICY_ENTRY = b''.fromhex("000001006A004600E222FFB0EA4A2547A6E55317FB8FD39C00000000FFFFFFFF000000000000000003AFAFAFC690F5ECF9F887438422486E3CCD8B2001AF45004F00440000004C0061007300740041007400740065006D00700074005300740061007400750073000000") # noqa
18+
TEST_POLICY_ENTRY_BAD_VERSION = b''.fromhex("010001006A004600E222FFB0EA4A2547A6E55317FB8FD39C00000000FFFFFFFF000000000000000003AFAFAFC690F5ECF9F887438422486E3CCD8B2001AF45004F00440000004C0061007300740041007400740065006D00700074005300740061007400750073000000") # noqa
19+
TEST_POLICY_ENTRY_BAD_LOCK_TYPE = b''.fromhex("000001006A004600E222FFB0EA4A2547A6E55317FB8FD39C00000000FFFFFFFF000000000000000004AFAFAFC690F5ECF9F887438422486E3CCD8B2001AF45004F00440000004C0061007300740041007400740065006D00700074005300740061007400750073000000") # noqa
20+
TEST_POLICY_ENTRY_GUID = uuid.UUID("B0FF22E2-4AEA-4725-A6E5-5317FB8FD39C")
21+
22+
23+
class TestVariableLockOnVarStatePolicy(unittest.TestCase):
24+
def test_remaining_buffer(self):
25+
test_vpl = VariableLockOnVarStatePolicy()
26+
test_remainder = b'123'
27+
test_buffer = TEST_GUID_2.bytes_le + b'\x00\x00' + b'\x00A\x00\x00' + test_remainder
28+
29+
self.assertEqual(test_remainder, test_vpl.decode(test_buffer))
30+
31+
def test_missing_name(self):
32+
test_vpl = VariableLockOnVarStatePolicy()
33+
34+
# Test with no Name field at all.
35+
test1 = TEST_GUID_1.bytes_le + b'\x00\x00'
36+
with self.assertRaises(Exception):
37+
test_vpl.decode(test1)
38+
39+
# Test with an empty string.
40+
test2 = test1 + b'\x00\x00'
41+
with self.assertRaises(Exception):
42+
test_vpl.decode(test2)
43+
44+
# Test successful.
45+
test3 = test1 + b'\x00A\x00\x00'
46+
_ = test_vpl.decode(test3)
47+
48+
def test_malformed_name(self):
49+
test_vpl = VariableLockOnVarStatePolicy()
50+
51+
# Test with no termination.
52+
test1 = TEST_GUID_1.bytes_le + b'\x00\x00' + b'\x00A\x00B'
53+
with self.assertRaises(Exception):
54+
test_vpl.decode(test1)
55+
56+
# Test with an unaligned termination.
57+
test2 = TEST_GUID_1.bytes_le + b'\x00\x00' + b'A\x00B\x00' + b'C' + b'\x00\x00'
58+
with self.assertRaises(Exception):
59+
test_vpl.decode(test2)
60+
61+
def test_to_string(self):
62+
test_vpl = VariableLockOnVarStatePolicy()
63+
test_buffer = TEST_GUID_2.bytes_le + b'\x00\x00' + b'A\x00B\x00C\x00\x00\x00'
64+
65+
test_vpl.decode(test_buffer)
66+
67+
self.assertEqual(test_vpl.Name, "ABC")
68+
69+
70+
class TestVariablePolicyEntry(unittest.TestCase):
71+
def test_create_and_to_string(self):
72+
test_vp = VariablePolicyEntry()
73+
to_string = str(test_vp)
74+
75+
# Check for the LockType string.
76+
self.assertIn("NONE", to_string)
77+
78+
test_vp.LockPolicyType = VariablePolicyEntry.TYPE_LOCK_ON_CREATE
79+
to_string = str(test_vp)
80+
81+
# Check for the new LockType string.
82+
self.assertIn("CREATE", to_string)
83+
84+
def test_csv_formatting(self):
85+
header_row = VariablePolicyEntry.csv_header()
86+
self.assertIn("Namespace", header_row)
87+
self.assertIn("LockPolicyType", header_row)
88+
89+
test_vp = VariablePolicyEntry()
90+
test_vp.LockPolicyType = VariablePolicyEntry.TYPE_LOCK_ON_CREATE
91+
csv_row = test_vp.csv_row()
92+
self.assertEqual(len(header_row), len(csv_row))
93+
self.assertIn("ON_CREATE", csv_row)
94+
95+
def test_decoding(self):
96+
test_vp = VariablePolicyEntry()
97+
test_vp.decode(TEST_POLICY_ENTRY)
98+
99+
self.assertEqual(test_vp.Namespace, TEST_POLICY_ENTRY_GUID)
100+
self.assertEqual(test_vp.LockPolicyType, VariablePolicyEntry.TYPE_LOCK_ON_VAR_STATE)
101+
self.assertEqual(test_vp.Name, "LastAttemptStatus")
102+
self.assertEqual(test_vp.LockPolicy.Name, "EOD")
103+
104+
to_string = str(test_vp)
105+
self.assertIn("VAR_STATE", to_string)
106+
self.assertIn("EOD", to_string)
107+
self.assertIn("LastAttemptStatus", to_string)
108+
109+
def test_decoding_errors(self):
110+
test_vp = VariablePolicyEntry()
111+
112+
with self.assertRaises(ValueError):
113+
test_vp.decode(TEST_POLICY_ENTRY_BAD_VERSION)
114+
with self.assertRaises(ValueError):
115+
test_vp.decode(TEST_POLICY_ENTRY_BAD_LOCK_TYPE)

edk2toollib/uefi/uefi_multi_phase.py

+28
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,38 @@
66
# SPDX-License-Identifier: BSD-2-Clause-Patent
77
##
88

9+
import struct
10+
911
EFI_VARIABLE_NON_VOLATILE = 0x00000001
1012
EFI_VARIABLE_BOOTSERVICE_ACCESS = 0x00000002
1113
EFI_VARIABLE_RUNTIME_ACCESS = 0x00000004
1214
EFI_VARIABLE_HARDWARE_ERROR_RECORD = 0x00000008
1315
EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS = 0x00000010
1416
EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS = 0x00000020
1517
EFI_VARIABLE_APPEND_WRITE = 0x00000040
18+
19+
20+
class EfiVariableAttributes(object):
21+
# UINT32
22+
_StructFormat = "<I"
23+
_StructSize = struct.calcsize(_StructFormat)
24+
25+
STRING_MAP = {
26+
EFI_VARIABLE_APPEND_WRITE: "EFI_VARIABLE_APPEND_WRITE",
27+
EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS: "EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS",
28+
EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS: "EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS",
29+
EFI_VARIABLE_HARDWARE_ERROR_RECORD: "EFI_VARIABLE_HARDWARE_ERROR_RECORD",
30+
EFI_VARIABLE_RUNTIME_ACCESS: "EFI_VARIABLE_RUNTIME_ACCESS",
31+
EFI_VARIABLE_BOOTSERVICE_ACCESS: "EFI_VARIABLE_BOOTSERVICE_ACCESS",
32+
EFI_VARIABLE_NON_VOLATILE: "EFI_VARIABLE_NON_VOLATILE",
33+
}
34+
35+
def __init__(self, attributes=0x0000_0000):
36+
self.Attributes = attributes
37+
38+
def __str__(self):
39+
result = []
40+
for key in EfiVariableAttributes.STRING_MAP:
41+
if self.Attributes & key:
42+
result.append(EfiVariableAttributes.STRING_MAP[key])
43+
return ",".join(result)
+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# @file
2+
# Code to test UEFI MultiPhase module
3+
#
4+
# Copyright (c) Microsoft Corporation
5+
#
6+
# SPDX-License-Identifier: BSD-2-Clause-Patent
7+
##
8+
import unittest
9+
from edk2toollib.uefi.uefi_multi_phase import (EfiVariableAttributes,
10+
EFI_VARIABLE_NON_VOLATILE, EFI_VARIABLE_RUNTIME_ACCESS,
11+
EFI_VARIABLE_BOOTSERVICE_ACCESS)
12+
13+
14+
class TestUefiMultiphase (unittest.TestCase):
15+
16+
def test_StringConversion(self):
17+
attr = EfiVariableAttributes(EFI_VARIABLE_NON_VOLATILE
18+
| EFI_VARIABLE_RUNTIME_ACCESS | EFI_VARIABLE_BOOTSERVICE_ACCESS)
19+
string = str(attr)
20+
21+
self.assertTrue("EFI_VARIABLE_RUNTIME_ACCESS" in string)
22+
self.assertTrue("EFI_VARIABLE_NON_VOLATILE" in string)
23+
self.assertTrue("EFI_VARIABLE_BOOTSERVICE_ACCESS" in string)

0 commit comments

Comments
 (0)