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