/src/fwupd/libfwupdplugin/fu-efi-vss-auth-variable.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright 2025 Richard Hughes <richard@hughsie.com> |
3 | | * |
4 | | * SPDX-License-Identifier: LGPL-2.1-or-later |
5 | | */ |
6 | | |
7 | 4.68k | #define G_LOG_DOMAIN "FuEfiVssAuthVariable" |
8 | | |
9 | | #include "config.h" |
10 | | |
11 | | #include "fu-byte-array.h" |
12 | | #include "fu-common.h" |
13 | | #include "fu-efi-common.h" |
14 | | #include "fu-efi-signature-list.h" |
15 | | #include "fu-efi-struct.h" |
16 | | #include "fu-efi-vss-auth-variable.h" |
17 | | #include "fu-efivars.h" |
18 | | #include "fu-partial-input-stream.h" |
19 | | #include "fu-string.h" |
20 | | |
21 | | /** |
22 | | * FuEfiVssAuthVariable: |
23 | | * |
24 | | * A NVRAM authenticated variable |
25 | | * |
26 | | * See also: [class@FuFirmware] |
27 | | */ |
28 | | |
29 | | struct _FuEfiVssAuthVariable { |
30 | | FuFirmware parent_instance; |
31 | | gchar *vendor_guid; |
32 | | FuEfiVariableAttrs attributes; |
33 | | FuEfiVariableState state; |
34 | | FuStructEfiTime *timestamp; /* nullable */ |
35 | | }; |
36 | | |
37 | 89.3k | G_DEFINE_TYPE(FuEfiVssAuthVariable, fu_efi_vss_auth_variable, FU_TYPE_FIRMWARE) |
38 | 89.3k | |
39 | 89.3k | /** |
40 | 89.3k | * fu_efi_vss_auth_variable_get_state: |
41 | 89.3k | * @self: #FuEfiVssAuthVariable |
42 | 89.3k | * |
43 | 89.3k | * Gets the VSS variable state. |
44 | 89.3k | * |
45 | 89.3k | * Returns: a #FuEfiVariableState, e.g. %FU_EFI_VARIABLE_STATE_VARIABLE_ADDED |
46 | 89.3k | * |
47 | 89.3k | * Since: 2.0.17 |
48 | 89.3k | **/ |
49 | 89.3k | FuEfiVariableState |
50 | 89.3k | fu_efi_vss_auth_variable_get_state(FuEfiVssAuthVariable *self) |
51 | 89.3k | { |
52 | 4.37k | g_return_val_if_fail(FU_IS_FIRMWARE(self), FU_EFI_VARIABLE_STATE_UNSET); |
53 | 4.37k | return self->state; |
54 | 4.37k | } |
55 | | |
56 | | static void |
57 | | fu_efi_vss_auth_variable_export(FuFirmware *firmware, |
58 | | FuFirmwareExportFlags flags, |
59 | | XbBuilderNode *bn) |
60 | 0 | { |
61 | 0 | FuEfiVssAuthVariable *self = FU_EFI_VSS_AUTH_VARIABLE(firmware); |
62 | 0 | fu_xmlb_builder_insert_kv(bn, "vendor_guid", self->vendor_guid); |
63 | 0 | if (self->state != FU_EFI_VARIABLE_STATE_UNSET) { |
64 | 0 | const gchar *str = fu_efi_variable_state_to_string(self->state); |
65 | 0 | fu_xmlb_builder_insert_kv(bn, "state", str); |
66 | 0 | } |
67 | 0 | if (self->attributes != FU_EFI_VARIABLE_ATTR_NONE) { |
68 | 0 | g_autofree gchar *str = fu_efi_variable_attrs_to_string(self->attributes); |
69 | 0 | fu_xmlb_builder_insert_kv(bn, "attributes", str); |
70 | 0 | } |
71 | 0 | if (self->timestamp != NULL) { |
72 | 0 | g_autoptr(XbBuilderNode) bc = xb_builder_node_insert(bn, "timestamp", NULL); |
73 | 0 | fu_efi_timestamp_export(self->timestamp, bc); |
74 | 0 | } |
75 | 0 | } |
76 | | |
77 | | static GType |
78 | | fu_efi_vss_auth_variable_lookup_image_gtype(FuEfiVssAuthVariable *self) |
79 | 4.68k | { |
80 | 4.68k | struct { |
81 | 4.68k | const gchar *guid; |
82 | 4.68k | const gchar *name; |
83 | 4.68k | GType gtype; |
84 | 4.68k | } gtypes[] = { |
85 | 4.68k | {FU_EFIVARS_GUID_EFI_GLOBAL, "PK", FU_TYPE_EFI_SIGNATURE_LIST}, |
86 | 4.68k | {FU_EFIVARS_GUID_EFI_GLOBAL, "KEK", FU_TYPE_EFI_SIGNATURE_LIST}, |
87 | 4.68k | {FU_EFIVARS_GUID_SECURITY_DATABASE, "db", FU_TYPE_EFI_SIGNATURE_LIST}, |
88 | 4.68k | {FU_EFIVARS_GUID_SECURITY_DATABASE, "dbx", FU_TYPE_EFI_SIGNATURE_LIST}, |
89 | 4.68k | }; |
90 | 23.4k | for (guint i = 0; i < G_N_ELEMENTS(gtypes); i++) { |
91 | 18.7k | if (g_strcmp0(gtypes[i].guid, self->vendor_guid) == 0 && |
92 | 0 | g_strcmp0(gtypes[i].name, fu_firmware_get_id(FU_FIRMWARE(self))) == 0) |
93 | 0 | return gtypes[i].gtype; |
94 | 18.7k | } |
95 | 4.68k | return G_TYPE_INVALID; |
96 | 4.68k | } |
97 | | |
98 | | static gboolean |
99 | | fu_efi_vss_auth_variable_parse(FuFirmware *firmware, |
100 | | GInputStream *stream, |
101 | | FuFirmwareParseFlags flags, |
102 | | GError **error) |
103 | 5.33k | { |
104 | 5.33k | FuEfiVssAuthVariable *self = FU_EFI_VSS_AUTH_VARIABLE(firmware); |
105 | 5.33k | gsize offset = 0x0; |
106 | 5.33k | GType img_gtype; |
107 | 5.33k | g_autoptr(FuStructEfiVssAuthVariableHeader) st = NULL; |
108 | 5.33k | g_autoptr(GByteArray) buf_name = NULL; |
109 | 5.33k | g_autofree gchar *name = NULL; |
110 | | |
111 | 5.33k | st = fu_struct_efi_vss_auth_variable_header_parse_stream(stream, offset, error); |
112 | 5.33k | if (st == NULL) |
113 | 109 | return FALSE; |
114 | 5.22k | if (fu_struct_efi_vss_auth_variable_header_get_start_id(st) == 0xFFFF) { |
115 | 176 | fu_firmware_add_flag(firmware, FU_FIRMWARE_FLAG_IS_LAST_IMAGE); |
116 | 176 | return TRUE; |
117 | 176 | } |
118 | 5.04k | if (fu_struct_efi_vss_auth_variable_header_get_start_id(st) != |
119 | 5.04k | FU_STRUCT_EFI_VSS_AUTH_VARIABLE_HEADER_DEFAULT_START_ID) { |
120 | 233 | g_set_error(error, |
121 | 233 | FWUPD_ERROR, |
122 | 233 | FWUPD_ERROR_INTERNAL, |
123 | 233 | "invalid VSS variable start ID, expected 0x%x and got 0x%x", |
124 | 233 | (guint)FU_STRUCT_EFI_VSS_AUTH_VARIABLE_HEADER_DEFAULT_START_ID, |
125 | 233 | fu_struct_efi_vss_auth_variable_header_get_start_id(st)); |
126 | 233 | return FALSE; |
127 | 233 | } |
128 | | |
129 | | /* attributes we care about */ |
130 | 4.81k | self->vendor_guid = |
131 | 4.81k | fwupd_guid_to_string(fu_struct_efi_vss_auth_variable_header_get_vendor_guid(st), |
132 | 4.81k | FWUPD_GUID_FLAG_MIXED_ENDIAN); |
133 | 4.81k | self->attributes = fu_struct_efi_vss_auth_variable_header_get_attributes(st); |
134 | 4.81k | self->state = fu_struct_efi_vss_auth_variable_header_get_state(st); |
135 | 4.81k | self->timestamp = fu_struct_efi_vss_auth_variable_header_get_timestamp(st); |
136 | | |
137 | | /* read name */ |
138 | 4.81k | offset += st->buf->len; |
139 | 4.81k | buf_name = fu_input_stream_read_byte_array( |
140 | 4.81k | stream, |
141 | 4.81k | offset, |
142 | 4.81k | fu_struct_efi_vss_auth_variable_header_get_name_size(st), |
143 | 4.81k | NULL, |
144 | 4.81k | error); |
145 | 4.81k | if (buf_name == NULL) |
146 | 42 | return FALSE; |
147 | 4.77k | name = fu_utf16_to_utf8_byte_array(buf_name, G_LITTLE_ENDIAN, error); |
148 | 4.77k | if (name == NULL) |
149 | 90 | return FALSE; |
150 | 4.68k | fu_firmware_set_id(firmware, name); |
151 | 4.68k | g_debug("added %s: %s", self->vendor_guid, name); |
152 | | |
153 | | /* read data */ |
154 | 4.68k | offset += fu_struct_efi_vss_auth_variable_header_get_name_size(st); |
155 | | |
156 | | /* if this is a well known key then parse it as a child type */ |
157 | 4.68k | img_gtype = fu_efi_vss_auth_variable_lookup_image_gtype(self); |
158 | 4.68k | if (img_gtype != G_TYPE_INVALID) { |
159 | 0 | g_autoptr(FuFirmware) img = g_object_new(img_gtype, NULL); |
160 | 0 | g_autoptr(GInputStream) partial_stream = NULL; |
161 | 0 | partial_stream = fu_partial_input_stream_new( |
162 | 0 | stream, |
163 | 0 | offset, |
164 | 0 | fu_struct_efi_vss_auth_variable_header_get_data_size(st), |
165 | 0 | error); |
166 | 0 | if (partial_stream == NULL) |
167 | 0 | return FALSE; |
168 | 0 | if (!fu_firmware_parse_stream(img, partial_stream, 0x0, flags, error)) |
169 | 0 | return FALSE; |
170 | 0 | if (!fu_firmware_add_image(firmware, img, error)) |
171 | 0 | return FALSE; |
172 | 4.68k | } else { |
173 | 4.68k | g_autoptr(GBytes) data = NULL; |
174 | 4.68k | data = fu_input_stream_read_bytes( |
175 | 4.68k | stream, |
176 | 4.68k | offset, |
177 | 4.68k | fu_struct_efi_vss_auth_variable_header_get_data_size(st), |
178 | 4.68k | NULL, |
179 | 4.68k | error); |
180 | 4.68k | if (data == NULL) |
181 | 304 | return FALSE; |
182 | 4.37k | fu_firmware_set_bytes(firmware, data); |
183 | 4.37k | } |
184 | | |
185 | | /* next header */ |
186 | 4.37k | offset += fu_struct_efi_vss_auth_variable_header_get_data_size(st); |
187 | | |
188 | | /* success */ |
189 | 4.37k | fu_firmware_set_size(firmware, offset); |
190 | 4.37k | return TRUE; |
191 | 4.68k | } |
192 | | |
193 | | static GByteArray * |
194 | | fu_efi_vss_auth_variable_write(FuFirmware *firmware, GError **error) |
195 | 525 | { |
196 | 525 | FuEfiVssAuthVariable *self = FU_EFI_VSS_AUTH_VARIABLE(firmware); |
197 | 525 | g_autoptr(FuStructEfiVssAuthVariableHeader) st = |
198 | 525 | fu_struct_efi_vss_auth_variable_header_new(); |
199 | 525 | g_autoptr(GBytes) name = NULL; |
200 | 525 | g_autoptr(GBytes) blob = NULL; |
201 | | |
202 | | /* attrs */ |
203 | 525 | fu_struct_efi_vss_auth_variable_header_set_attributes(st, self->attributes); |
204 | 525 | fu_struct_efi_vss_auth_variable_header_set_state(st, self->state); |
205 | 525 | if (self->timestamp != NULL) { |
206 | 525 | if (!fu_struct_efi_vss_auth_variable_header_set_timestamp(st, |
207 | 525 | self->timestamp, |
208 | 525 | error)) |
209 | 0 | return NULL; |
210 | 525 | } |
211 | | |
212 | | /* name */ |
213 | 525 | if (fu_firmware_get_id(firmware) == NULL) { |
214 | 0 | g_set_error_literal(error, |
215 | 0 | FWUPD_ERROR, |
216 | 0 | FWUPD_ERROR_INVALID_DATA, |
217 | 0 | "firmware ID is required"); |
218 | 0 | return NULL; |
219 | 0 | } |
220 | 525 | name = fu_utf8_to_utf16_bytes(fu_firmware_get_id(firmware), |
221 | 525 | G_LITTLE_ENDIAN, |
222 | 525 | FU_UTF_CONVERT_FLAG_APPEND_NUL, |
223 | 525 | error); |
224 | 525 | if (name == NULL) |
225 | 0 | return NULL; |
226 | 525 | fu_struct_efi_vss_auth_variable_header_set_name_size(st, g_bytes_get_size(name)); |
227 | | |
228 | | /* data */ |
229 | 525 | blob = fu_firmware_get_image_by_id_bytes(firmware, NULL, NULL); |
230 | 525 | if (blob == NULL) { |
231 | 525 | blob = fu_firmware_get_bytes(firmware, error); |
232 | 525 | if (blob == NULL) |
233 | 0 | return NULL; |
234 | 525 | } |
235 | 525 | fu_struct_efi_vss_auth_variable_header_set_data_size(st, g_bytes_get_size(blob)); |
236 | | |
237 | | /* guid */ |
238 | 525 | if (self->vendor_guid != NULL) { |
239 | 525 | fwupd_guid_t guid = {0}; |
240 | 525 | if (!fwupd_guid_from_string(self->vendor_guid, |
241 | 525 | &guid, |
242 | 525 | FWUPD_GUID_FLAG_MIXED_ENDIAN, |
243 | 525 | error)) |
244 | 0 | return NULL; |
245 | 525 | fu_struct_efi_vss_auth_variable_header_set_vendor_guid(st, &guid); |
246 | 525 | } |
247 | | |
248 | | /* concat */ |
249 | 525 | fu_byte_array_append_bytes(st->buf, name); |
250 | 525 | fu_byte_array_append_bytes(st->buf, blob); |
251 | | |
252 | | /* success */ |
253 | 525 | return g_steal_pointer(&st->buf); |
254 | 525 | } |
255 | | |
256 | | static gboolean |
257 | | fu_efi_vss_auth_variable_build(FuFirmware *firmware, XbNode *n, GError **error) |
258 | 0 | { |
259 | 0 | FuEfiVssAuthVariable *self = FU_EFI_VSS_AUTH_VARIABLE(firmware); |
260 | 0 | const gchar *tmp; |
261 | 0 | g_autoptr(XbNode) n_timestamp = NULL; |
262 | | |
263 | | /* simple properties */ |
264 | 0 | tmp = xb_node_query_text(n, "vendor_guid", NULL); |
265 | 0 | if (tmp != NULL) |
266 | 0 | self->vendor_guid = g_strdup(tmp); |
267 | 0 | tmp = xb_node_query_text(n, "attributes", NULL); |
268 | 0 | if (tmp != NULL) |
269 | 0 | self->attributes = fu_efi_variable_attrs_from_string(tmp); |
270 | 0 | tmp = xb_node_query_text(n, "state", NULL); |
271 | 0 | if (tmp != NULL) |
272 | 0 | self->state = fu_efi_variable_state_from_string(tmp); |
273 | | |
274 | | /* EFI_TIME */ |
275 | 0 | n_timestamp = xb_node_query_first(n, "timestamp", NULL); |
276 | 0 | if (n_timestamp != NULL) { |
277 | 0 | self->timestamp = fu_struct_efi_time_new(); |
278 | 0 | fu_efi_timestamp_build(self->timestamp, n_timestamp); |
279 | 0 | } |
280 | | |
281 | | /* success */ |
282 | 0 | return TRUE; |
283 | 0 | } |
284 | | |
285 | | static void |
286 | | fu_efi_vss_auth_variable_init(FuEfiVssAuthVariable *self) |
287 | 5.42k | { |
288 | 5.42k | fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_STORED_SIZE); |
289 | 5.42k | fu_firmware_add_image_gtype(FU_FIRMWARE(self), FU_TYPE_EFI_SIGNATURE_LIST); |
290 | 5.42k | } |
291 | | |
292 | | static void |
293 | | fu_efi_vss_auth_variable_finalize(GObject *obj) |
294 | 5.42k | { |
295 | 5.42k | FuEfiVssAuthVariable *self = FU_EFI_VSS_AUTH_VARIABLE(obj); |
296 | 5.42k | if (self->timestamp != NULL) |
297 | 4.81k | fu_struct_efi_time_unref(self->timestamp); |
298 | 5.42k | g_free(self->vendor_guid); |
299 | 5.42k | G_OBJECT_CLASS(fu_efi_vss_auth_variable_parent_class)->finalize(obj); |
300 | 5.42k | } |
301 | | |
302 | | static void |
303 | | fu_efi_vss_auth_variable_class_init(FuEfiVssAuthVariableClass *klass) |
304 | 2 | { |
305 | 2 | FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass); |
306 | 2 | GObjectClass *object_class = G_OBJECT_CLASS(klass); |
307 | 2 | object_class->finalize = fu_efi_vss_auth_variable_finalize; |
308 | 2 | firmware_class->parse = fu_efi_vss_auth_variable_parse; |
309 | 2 | firmware_class->export = fu_efi_vss_auth_variable_export; |
310 | 2 | firmware_class->write = fu_efi_vss_auth_variable_write; |
311 | 2 | firmware_class->build = fu_efi_vss_auth_variable_build; |
312 | 2 | } |
313 | | |
314 | | /** |
315 | | * fu_efi_vss_auth_variable_new: |
316 | | * |
317 | | * Creates an empty VSS variable store. |
318 | | * |
319 | | * Returns: a #FuFirmware |
320 | | * |
321 | | * Since: 2.0.17 |
322 | | **/ |
323 | | FuFirmware * |
324 | | fu_efi_vss_auth_variable_new(void) |
325 | 5.42k | { |
326 | 5.42k | return g_object_new(FU_TYPE_EFI_VSS_AUTH_VARIABLE, NULL); |
327 | 5.42k | } |