master
c 262 lines 6.17 KB
Raw
1 /*
2 * SPDX-License-Identifier: GPL-2.0-or-later
3 *
4 * uefi vars device - serialize non-volatile varstore from/to json,
5 * using qapi
6 *
7 * tools which can read/write these json files:
8 * - https://gitlab.com/kraxel/virt-firmware
9 * - https://github.com/awslabs/python-uefivars
10 */
11 #include "qemu/osdep.h"
12 #include "qemu/cutils.h"
13 #include "qemu/error-report.h"
14 #include "system/dma.h"
15
16 #include "hw/uefi/var-service.h"
17
18 #include "qobject/qobject.h"
19 #include "qobject/qjson.h"
20
21 #include "qapi/error.h"
22 #include "qapi/dealloc-visitor.h"
23 #include "qapi/qobject-input-visitor.h"
24 #include "qapi/qobject-output-visitor.h"
25 #include "qapi/qapi-types-uefi.h"
26 #include "qapi/qapi-visit-uefi.h"
27
28 static char *generate_hexstr(void *data, size_t len)
29 {
30 static const char hex[] = {
31 '0', '1', '2', '3', '4', '5', '6', '7',
32 '8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
33 };
34 uint8_t *src = data;
35 char *dest;
36 size_t i;
37
38 dest = g_malloc(len * 2 + 1);
39 for (i = 0; i < len * 2;) {
40 dest[i++] = hex[*src >> 4];
41 dest[i++] = hex[*src & 15];
42 src++;
43 }
44 dest[i++] = 0;
45
46 return dest;
47 }
48
49 static UefiVarStore *uefi_vars_to_qapi(uefi_vars_state *uv)
50 {
51 UefiVarStore *vs;
52 UefiVariableList **tail;
53 UefiVariable *v;
54 QemuUUID be;
55 uefi_variable *var;
56
57 vs = g_new0(UefiVarStore, 1);
58 vs->version = 2;
59 tail = &vs->variables;
60
61 QTAILQ_FOREACH(var, &uv->variables, next) {
62 if (!(var->attributes & EFI_VARIABLE_NON_VOLATILE)) {
63 continue;
64 }
65
66 v = g_new0(UefiVariable, 1);
67 be = qemu_uuid_bswap(var->guid);
68 v->guid = qemu_uuid_unparse_strdup(&be);
69 v->name = uefi_ucs2_to_ascii(var->name, var->name_size);
70 v->attr = var->attributes;
71
72 v->data = generate_hexstr(var->data, var->data_size);
73
74 if (var->attributes &
75 EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS) {
76 v->time = generate_hexstr(&var->time, sizeof(var->time));
77 if (var->digest && var->digest_size) {
78 v->digest = generate_hexstr(var->digest, var->digest_size);
79 }
80 }
81
82 QAPI_LIST_APPEND(tail, v);
83 }
84 return vs;
85 }
86
87 static unsigned parse_hexchar(char c)
88 {
89 switch (c) {
90 case '0' ... '9': return c - '0';
91 case 'a' ... 'f': return c - 'a' + 0xa;
92 case 'A' ... 'F': return c - 'A' + 0xA;
93 default: return 0;
94 }
95 }
96
97 static void parse_hexstr(void *dest, char *src, int len)
98 {
99 uint8_t *data = dest;
100 size_t i;
101
102 for (i = 0; i + 1 < len; i += 2) {
103 *(data++) =
104 parse_hexchar(src[i]) << 4 |
105 parse_hexchar(src[i + 1]);
106 }
107 }
108
109 static void uefi_vars_from_qapi(uefi_vars_state *uv, UefiVarStore *vs)
110 {
111 UefiVariableList *item;
112 UefiVariable *v;
113 QemuUUID be;
114 uefi_variable *var;
115 uint8_t *data;
116 size_t i, len;
117
118 for (item = vs->variables; item != NULL; item = item->next) {
119 v = item->value;
120
121 var = g_new0(uefi_variable, 1);
122 var->attributes = v->attr;
123 qemu_uuid_parse(v->guid, &be);
124 var->guid = qemu_uuid_bswap(be);
125
126 len = strlen(v->name);
127 var->name_size = len * 2 + 2;
128 var->name = g_malloc(var->name_size);
129 for (i = 0; i <= len; i++) {
130 var->name[i] = v->name[i];
131 }
132
133 len = strlen(v->data);
134 var->data_size = len / 2;
135 var->data = data = g_malloc(var->data_size);
136 parse_hexstr(var->data, v->data, len);
137
138 if (v->time && strlen(v->time) == 32) {
139 parse_hexstr(&var->time, v->time, 32);
140 }
141
142 if (v->digest) {
143 len = strlen(v->digest);
144 var->digest_size = len / 2;
145 var->digest = g_malloc(var->digest_size);
146 parse_hexstr(var->digest, v->digest, len);
147 }
148
149 QTAILQ_INSERT_TAIL(&uv->variables, var, next);
150 }
151 }
152
153 static GString *uefi_vars_to_json(uefi_vars_state *uv)
154 {
155 UefiVarStore *vs = uefi_vars_to_qapi(uv);
156 QObject *qobj = NULL;
157 Visitor *v;
158 GString *gstr;
159
160 v = qobject_output_visitor_new(&qobj);
161 if (visit_type_UefiVarStore(v, NULL, &vs, NULL)) {
162 visit_complete(v, &qobj);
163 }
164 visit_free(v);
165 qapi_free_UefiVarStore(vs);
166
167 gstr = qobject_to_json_pretty(qobj, true);
168 qobject_unref(qobj);
169
170 return gstr;
171 }
172
173 void uefi_vars_json_init(uefi_vars_state *uv, Error **errp)
174 {
175 if (uv->jsonfile) {
176 uv->jsonfd = qemu_create(uv->jsonfile, O_RDWR | O_BINARY, 0666, errp);
177 }
178 }
179
180 void uefi_vars_json_save(uefi_vars_state *uv)
181 {
182 g_autoptr(GString) gstr = NULL;
183 int rc;
184
185 if (uv->jsonfd == -1) {
186 return;
187 }
188
189 gstr = uefi_vars_to_json(uv);
190
191 rc = lseek(uv->jsonfd, 0, SEEK_SET);
192 if (rc < 0) {
193 warn_report("%s: lseek error", __func__);
194 return;
195 }
196
197 rc = ftruncate(uv->jsonfd, 0);
198 if (rc != 0) {
199 warn_report("%s: ftruncate error", __func__);
200 return;
201 }
202
203 rc = write(uv->jsonfd, gstr->str, gstr->len);
204 if (rc != gstr->len) {
205 warn_report("%s: write error", __func__);
206 return;
207 }
208
209 fsync(uv->jsonfd);
210 }
211
212 void uefi_vars_json_load(uefi_vars_state *uv, Error **errp)
213 {
214 UefiVarStore *vs;
215 QObject *qobj;
216 Visitor *v;
217 char *str;
218 ssize_t len;
219 int rc;
220
221 if (uv->jsonfd == -1) {
222 return;
223 }
224
225 len = lseek(uv->jsonfd, 0, SEEK_END);
226 if (len < 0) {
227 warn_report("%s: lseek error", __func__);
228 return;
229 }
230 if (len == 0) {
231 /* empty file */
232 return;
233 }
234
235 str = g_malloc(len + 1);
236 lseek(uv->jsonfd, 0, SEEK_SET);
237 rc = read(uv->jsonfd, str, len);
238 if (rc != len) {
239 warn_report("%s: read error", __func__);
240 g_free(str);
241 return;
242 }
243 str[len] = 0;
244
245 qobj = qobject_from_json(str, errp);
246 v = qobject_input_visitor_new(qobj);
247 visit_type_UefiVarStore(v, NULL, &vs, errp);
248 visit_free(v);
249
250 if (!(*errp)) {
251 uefi_vars_from_qapi(uv, vs);
252 uefi_vars_update_storage(uv);
253 if (uv->used_storage > uv->max_storage) {
254 error_setg(errp, "out of variable memory (%" PRId64 " > %" PRId64 ")",
255 uv->used_storage, uv->max_storage);
256 }
257 }
258
259 qapi_free_UefiVarStore(vs);
260 qobject_unref(qobj);
261 g_free(str);
262 }