/src/fwupd/libfwupdplugin/fu-efi-vss2-variable-store.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 | | #define G_LOG_DOMAIN "FuEfiVss2VariableStore" |
8 | | |
9 | | #include "config.h" |
10 | | |
11 | | #include "fu-byte-array.h" |
12 | | #include "fu-common.h" |
13 | | #include "fu-efi-struct.h" |
14 | | #include "fu-efi-vss-auth-variable.h" |
15 | | #include "fu-efi-vss2-variable-store.h" |
16 | | #include "fu-input-stream.h" |
17 | | #include "fu-string.h" |
18 | | |
19 | | /** |
20 | | * FuEfiVss2VariableStore: |
21 | | * |
22 | | * A NVRAM variable store. |
23 | | * |
24 | | * See also: [class@FuFirmware] |
25 | | */ |
26 | | |
27 | | struct _FuEfiVss2VariableStore { |
28 | | FuFirmware parent_instance; |
29 | | }; |
30 | | |
31 | 115k | G_DEFINE_TYPE(FuEfiVss2VariableStore, fu_efi_vss2_variable_store, FU_TYPE_FIRMWARE) |
32 | 115k | |
33 | 115k | static gboolean |
34 | 115k | fu_efi_vss2_variable_store_validate(FuFirmware *firmware, |
35 | 115k | FuInputStream *stream, |
36 | 115k | gsize offset, |
37 | 115k | GError **error) |
38 | 115k | { |
39 | 115k | return fu_struct_efi_vss2_variable_store_header_validate_stream(stream, offset, error); |
40 | 115k | } |
41 | | |
42 | | static gboolean |
43 | | fu_efi_vss2_variable_store_parse(FuFirmware *firmware, |
44 | | FuInputStream *stream, |
45 | | FuFirmwareParseFlags flags, |
46 | | GError **error) |
47 | 2.84k | { |
48 | 2.84k | gsize offset = 0x0; |
49 | 2.84k | g_autoptr(FuStructEfiVss2VariableStoreHeader) st = NULL; |
50 | | |
51 | 2.84k | st = fu_struct_efi_vss2_variable_store_header_parse_stream(stream, offset, error); |
52 | 2.84k | if (st == NULL) |
53 | 0 | return FALSE; |
54 | | |
55 | | /* sanity check */ |
56 | 2.84k | if (fu_struct_efi_vss2_variable_store_header_get_size(st) > |
57 | 2.84k | fu_firmware_get_size_max(firmware)) { |
58 | 80 | g_set_error(error, |
59 | 80 | FWUPD_ERROR, |
60 | 80 | FWUPD_ERROR_INTERNAL, |
61 | 80 | "VSS store larger than max size: 0x%x > 0x%x", |
62 | 80 | (guint)fu_struct_efi_vss2_variable_store_header_get_size(st), |
63 | 80 | (guint)fu_firmware_get_size_max(firmware)); |
64 | 80 | return FALSE; |
65 | 80 | } |
66 | | |
67 | | /* parse each attr */ |
68 | 2.76k | if (!fu_size_checked_inc(&offset, st->buf->len, error)) { |
69 | 0 | g_prefix_error_literal(error, "VSS2 header offset overflow: "); |
70 | 0 | return FALSE; |
71 | 0 | } |
72 | | |
73 | 18.8k | while (offset < fu_struct_efi_vss2_variable_store_header_get_size(st)) { |
74 | 18.1k | g_autoptr(FuFirmware) img = fu_efi_vss_auth_variable_new(); |
75 | | |
76 | 18.1k | if (!fu_firmware_parse_stream(img, stream, offset, flags, error)) { |
77 | 1.70k | g_prefix_error(error, "offset @0x%x: ", (guint)offset); |
78 | 1.70k | return FALSE; |
79 | 1.70k | } |
80 | 16.4k | if (fu_firmware_has_flag(img, FU_FIRMWARE_FLAG_IS_LAST_IMAGE)) |
81 | 236 | break; |
82 | 16.1k | if (fu_firmware_get_size(img) == 0) { |
83 | 0 | g_set_error_literal(error, |
84 | 0 | FWUPD_ERROR, |
85 | 0 | FWUPD_ERROR_INTERNAL, |
86 | 0 | "VSS2 store entry has zero size"); |
87 | 0 | return FALSE; |
88 | 0 | } |
89 | 16.1k | if (fu_efi_vss_auth_variable_get_state(FU_EFI_VSS_AUTH_VARIABLE(img)) == |
90 | 16.1k | FU_EFI_VARIABLE_STATE_VARIABLE_ADDED) { |
91 | 15.5k | fu_firmware_set_offset(img, offset); |
92 | 15.5k | if (!fu_firmware_add_image(firmware, img, error)) |
93 | 105 | return FALSE; |
94 | 15.5k | } |
95 | 16.0k | if (!fu_size_checked_inc(&offset, fu_firmware_get_size(img), error)) |
96 | 0 | return FALSE; |
97 | 16.0k | offset = fu_common_align_up(offset, FU_FIRMWARE_ALIGNMENT_4); |
98 | 16.0k | } |
99 | | |
100 | | /* success */ |
101 | 954 | fu_firmware_set_size(firmware, fu_struct_efi_vss2_variable_store_header_get_size(st)); |
102 | 954 | return TRUE; |
103 | 2.76k | } |
104 | | |
105 | | static GByteArray * |
106 | | fu_efi_vss2_variable_store_write(FuFirmware *firmware, GError **error) |
107 | 552 | { |
108 | 552 | g_autoptr(FuStructEfiVss2VariableStoreHeader) st = |
109 | 552 | fu_struct_efi_vss2_variable_store_header_new(); |
110 | 552 | g_autoptr(GPtrArray) imgs = fu_firmware_get_images(firmware); |
111 | | |
112 | | /* sanity check */ |
113 | 552 | if (fu_firmware_get_size(firmware) < st->buf->len) { |
114 | 10 | g_set_error_literal(error, |
115 | 10 | FWUPD_ERROR, |
116 | 10 | FWUPD_ERROR_INTERNAL, |
117 | 10 | "VSS2 variable store has zero size"); |
118 | 10 | return NULL; |
119 | 10 | } |
120 | | |
121 | | /* each attr */ |
122 | 2.55k | for (guint i = 0; i < imgs->len; i++) { |
123 | 2.01k | FuFirmware *img = g_ptr_array_index(imgs, i); |
124 | 2.01k | g_autoptr(GBytes) fw = NULL; |
125 | | |
126 | 2.01k | fw = fu_firmware_write(img, error); |
127 | 2.01k | if (fw == NULL) |
128 | 0 | return NULL; |
129 | 2.01k | fu_byte_array_append_bytes(st->buf, fw); |
130 | 2.01k | fu_byte_array_align_up(st->buf, FU_FIRMWARE_ALIGNMENT_4, 0xFF); |
131 | 2.01k | } |
132 | | |
133 | | /* sanity check */ |
134 | 542 | if (st->buf->len > fu_firmware_get_size(firmware)) { |
135 | 110 | g_set_error(error, |
136 | 110 | FWUPD_ERROR, |
137 | 110 | FWUPD_ERROR_INTERNAL, |
138 | 110 | "VSS2 store is too small, needed 0x%x but defined as 0x%x", |
139 | 110 | st->buf->len, |
140 | 110 | (guint)(fu_firmware_get_size(firmware))); |
141 | 110 | return NULL; |
142 | 110 | } |
143 | | |
144 | | /* fix up header and attrs */ |
145 | 432 | fu_byte_array_set_size(st->buf, fu_firmware_get_size(firmware), 0xFF); |
146 | 432 | fu_struct_efi_vss2_variable_store_header_set_size(st, fu_firmware_get_size(firmware)); |
147 | | |
148 | | /* success */ |
149 | 432 | return g_steal_pointer(&st->buf); |
150 | 542 | } |
151 | | |
152 | | static void |
153 | | fu_efi_vss2_variable_store_init(FuEfiVss2VariableStore *self) |
154 | 115k | { |
155 | 115k | fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_DEDUPE_ID); |
156 | 115k | fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_STORED_SIZE); |
157 | 115k | } |
158 | | |
159 | | static void |
160 | | fu_efi_vss2_variable_store_class_init(FuEfiVss2VariableStoreClass *klass) |
161 | 3 | { |
162 | 3 | FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass); |
163 | 3 | firmware_class->validate = fu_efi_vss2_variable_store_validate; |
164 | 3 | firmware_class->parse = fu_efi_vss2_variable_store_parse; |
165 | 3 | firmware_class->write = fu_efi_vss2_variable_store_write; |
166 | 3 | fu_firmware_add_image_gtype(firmware_class, FU_TYPE_EFI_VSS_AUTH_VARIABLE); |
167 | 3 | #ifdef HAVE_FUZZER |
168 | 3 | fu_firmware_set_size_max(firmware_class, 4 * FU_KB); |
169 | 3 | fu_firmware_set_images_max(firmware_class, 10); |
170 | | #else |
171 | | fu_firmware_set_size_max(firmware_class, 16 * FU_MB); |
172 | | fu_firmware_set_images_max(firmware_class, 10000); |
173 | | #endif |
174 | 3 | } |
175 | | |
176 | | /** |
177 | | * fu_efi_vss2_variable_store_new: |
178 | | * |
179 | | * Creates an empty VSS variable store. |
180 | | * |
181 | | * Returns: a #FuFirmware |
182 | | * |
183 | | * Since: 2.0.17 |
184 | | **/ |
185 | | FuFirmware * |
186 | | fu_efi_vss2_variable_store_new(void) |
187 | 0 | { |
188 | 0 | return g_object_new(FU_TYPE_EFI_VSS2_VARIABLE_STORE, NULL); |
189 | 0 | } |