1 /*
2 * Copyright 2017 Frank Hunleth
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "uboot_env.h"
18 #include "util.h"
19 #include "crc32.h"
20 #include "block_cache.h"
21
22 #include <inttypes.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <errno.h>
26
27 // Licensing note: U-boot is licensed under the GPL which is incompatible with
28 // fwup's Apache 2.0 license. Please don't copy code from
29 // U-boot especially since the U-boot environment data
30 // structure is simple enough to reverse engineer by playing
31 // with mkenvimage.
32
uboot_env_verify_cfg(cfg_t * cfg)33 int uboot_env_verify_cfg(cfg_t *cfg)
34 {
35 int block_offset = cfg_getint(cfg, "block-offset");
36 if (block_offset < 0)
37 ERR_RETURN("block-offset must be specified and less than 2^31 - 1");
38
39 int block_count = cfg_getint(cfg, "block-count");
40 if (block_count <= 0 || block_count >= UINT16_MAX)
41 ERR_RETURN("block-count must be specified, greater than 0 and less than 2^16 - 1");
42
43 int block_offset_redund = cfg_getint(cfg, "block-offset-redund");
44 if (block_offset_redund >= 0) {
45 if (block_offset_redund >= INT32_MAX)
46 ERR_RETURN("block-offset-redund must be less than 2^31 - 1");
47
48 int redundant_left = block_offset_redund;
49 int redundant_right = block_offset_redund + block_count;
50 if ((redundant_left >= block_offset && redundant_left < block_offset + block_count) ||
51 (redundant_right > block_offset && redundant_right < block_offset + block_count))
52 ERR_RETURN("block-offset-redund can't overlap primary U-Boot environment");
53 }
54
55 return 0;
56 }
57
uboot_env_create_cfg(cfg_t * cfg,struct uboot_env * output)58 int uboot_env_create_cfg(cfg_t *cfg, struct uboot_env *output)
59 {
60 memset(output, 0, sizeof(struct uboot_env));
61
62 output->block_offset = cfg_getint(cfg, "block-offset");
63 output->block_count = cfg_getint(cfg, "block-count");
64 output->env_size = output->block_count * FWUP_BLOCK_SIZE;
65
66 int redundant_offset = cfg_getint(cfg, "block-offset-redund");
67 if (redundant_offset >= 0) {
68 output->use_redundant = true;
69 output->redundant_block_offset = redundant_offset;
70 output->write_primary = true;
71 output->write_secondary = true;
72 output->flags = 0;
73 } else {
74 output->use_redundant = false;
75 output->redundant_block_offset = output->block_offset;
76 output->write_primary = true;
77 }
78
79 output->vars = NULL;
80
81 // This condition should only be hit if the .fw file was manually
82 // modified, since the block-count should have been validated at
83 // .fw creation time.
84 if (output->block_count == 0 || output->block_count >= UINT16_MAX)
85 ERR_RETURN("invalid u-boot environment block count");
86
87 return 0;
88 }
89
uboot_env_decode(struct uboot_env * env,const char * buffer)90 static int uboot_env_decode(struct uboot_env *env, const char *buffer)
91 {
92 uboot_env_free(env);
93
94 size_t data_offset = (env->use_redundant ? 5 : 4);
95 uint32_t expected_crc32 = ((uint8_t) buffer[0] | ((uint8_t) buffer[1] << 8) | ((uint8_t) buffer[2] << 16) | ((uint8_t) buffer[3] << 24));
96 uint32_t actual_crc32 = crc32buf(buffer + data_offset, env->env_size - data_offset);
97 if (expected_crc32 != actual_crc32)
98 ERR_RETURN("U-boot environment (block %" PRIu64 ") CRC32 mismatch (expected 0x%08x; got 0x%08x)", env->block_offset, expected_crc32, actual_crc32);
99
100 const char *end = buffer + env->env_size;
101 const char *name = buffer + data_offset;
102 while (name != end && *name != '\0') {
103 const char *endname = name + 1;
104 for (;;) {
105 if (endname == end || *endname == '\0')
106 ERR_RETURN("Invalid U-boot environment");
107
108 if (*endname == '=')
109 break;
110
111 endname++;
112 }
113
114 const char *value = endname + 1;
115 const char *endvalue = value;
116 for (;;) {
117 if (endvalue == end)
118 ERR_RETURN("Invalid U-boot environment");
119
120 if (*endvalue == '\0')
121 break;
122
123 endvalue++;
124 }
125
126 struct uboot_name_value *pair = malloc(sizeof(struct uboot_name_value));
127 pair->name = strndup(name, endname - name);
128 pair->value = strndup(value, value - endvalue);
129 pair->next = env->vars;
130 env->vars = pair;
131
132 name = endvalue + 1;
133 }
134
135 return 0;
136 }
137
uboot_env_setenv(struct uboot_env * env,const char * name,const char * value)138 int uboot_env_setenv(struct uboot_env *env, const char *name, const char *value)
139 {
140 struct uboot_name_value *pair;
141 for (pair = env->vars; pair != NULL; pair = pair->next) {
142 if (strcmp(pair->name, name) == 0) {
143 free(pair->value);
144 pair->value = strdup(value);
145 return 0;
146 }
147 }
148 pair = malloc(sizeof(*pair));
149 pair->name = strdup(name);
150 pair->value = strdup(value);
151 pair->next = env->vars;
152 env->vars = pair;
153 return 0;
154 }
155
uboot_env_unsetenv(struct uboot_env * env,const char * name)156 int uboot_env_unsetenv(struct uboot_env *env, const char *name)
157 {
158 struct uboot_name_value *prev = NULL;
159 struct uboot_name_value *pair;
160 for (pair = env->vars;
161 pair != NULL;
162 prev = pair, pair = pair->next) {
163 if (strcmp(pair->name, name) == 0) {
164 if (prev)
165 prev->next = pair->next;
166 else
167 env->vars = pair->next;
168 free(pair->name);
169 free(pair->value);
170 free(pair);
171 break;
172 }
173 }
174 return 0;
175 }
176
uboot_env_getenv(struct uboot_env * env,const char * name,char ** value)177 int uboot_env_getenv(struct uboot_env *env, const char *name, char **value)
178 {
179 struct uboot_name_value *pair;
180 for (pair = env->vars; pair != NULL; pair = pair->next) {
181 if (strcmp(pair->name, name) == 0) {
182 *value = strdup(pair->value);
183 return 0;
184 }
185 }
186
187 *value = NULL;
188 ERR_RETURN("variable '%s' not found", name);
189 }
190
env_name_compare(const void * a,const void * b)191 static int env_name_compare(const void *a, const void *b)
192 {
193 struct uboot_name_value **apair = (struct uboot_name_value **) a;
194 struct uboot_name_value **bpair = (struct uboot_name_value **) b;
195
196 return strcmp((*apair)->name, (*bpair)->name);
197 }
198
uboot_env_sort(struct uboot_env * env)199 static void uboot_env_sort(struct uboot_env *env)
200 {
201 int count = 0;
202 struct uboot_name_value *pair;
203 for (pair = env->vars; pair != NULL; pair = pair->next)
204 count++;
205
206 if (count == 0)
207 return;
208
209 struct uboot_name_value **pairarray =
210 (struct uboot_name_value **) malloc(count * sizeof(struct uboot_name_value *));
211 int i;
212 for (pair = env->vars, i = 0; pair != NULL; pair = pair->next, i++)
213 pairarray[i] = pair;
214
215 qsort(pairarray, count, sizeof(struct uboot_name_value *), env_name_compare);
216
217 for (i = 0; i < count - 1; i++)
218 pairarray[i]->next = pairarray[i + 1];
219 pairarray[count - 1]->next = NULL;
220 env->vars = pairarray[0];
221
222 free(pairarray);
223 }
224
uboot_env_encode(struct uboot_env * env,char * buffer)225 static int uboot_env_encode(struct uboot_env *env, char *buffer)
226 {
227 // U-boot environment blocks are filled by 0xff by default
228 memset(buffer, 0xff, env->env_size);
229
230 // Skip over the CRC until the end.
231 size_t data_offset = (env->use_redundant ? 5 : 4);
232 char *p = buffer + data_offset;
233 char *end = buffer + env->env_size - 2;
234
235 // Sort the name/value pairs so that their ordering is
236 // deterministic.
237 uboot_env_sort(env);
238
239 // Add all of the name/value pairs.
240 struct uboot_name_value *pair;
241 for (pair = env->vars; pair != NULL; pair = pair->next) {
242 size_t namelen = strlen(pair->name);
243 size_t valuelen = strlen(pair->value);
244 if (p + namelen + 1 + valuelen >= end)
245 ERR_RETURN("Not enough room in U-boot environment");
246
247 memcpy(p, pair->name, namelen);
248 p += namelen;
249 *p = '=';
250 p++;
251 memcpy(p, pair->value, valuelen);
252 p += valuelen;
253 *p = 0;
254 p++;
255 }
256
257 // Add the extra NULL byte on the end.
258 *p = 0;
259
260 // Calculate and add the CRC-32
261 uint32_t crc32 = crc32buf(buffer + data_offset, env->env_size - data_offset);
262 buffer[0] = crc32 & 0xff;
263 buffer[1] = (crc32 >> 8) & 0xff;
264 buffer[2] = (crc32 >> 16) & 0xff;
265 buffer[3] = crc32 >> 24;
266
267 return 0;
268 }
269
uboot_env_free(struct uboot_env * env)270 void uboot_env_free(struct uboot_env *env)
271 {
272 struct uboot_name_value *pair = env->vars;
273 while (pair != NULL) {
274 free(pair->name);
275 free(pair->value);
276
277 struct uboot_name_value *next = pair->next;
278 free(pair);
279 pair = next;
280 }
281
282 env->vars = NULL;
283 }
284
uboot_env_read_non_redundant(struct block_cache * bc,struct uboot_env * env)285 static int uboot_env_read_non_redundant(struct block_cache *bc, struct uboot_env *env)
286 {
287 int rc;
288 char *buffer = (char *) malloc(env->env_size);
289
290 // Initialize data that's only used for redundant environment support
291 env->write_primary = true;
292 env->write_secondary = false;
293 env->flags = 0;
294
295 OK_OR_CLEANUP_MSG(block_cache_pread(bc, buffer, env->env_size, env->block_offset * FWUP_BLOCK_SIZE),
296 "unexpected error reading uboot environment: %s", strerror(errno));
297
298 rc = uboot_env_decode(env, buffer);
299 cleanup:
300 free(buffer);
301 return rc;
302 }
303
uboot_env_read_redundant(struct block_cache * bc,struct uboot_env * env)304 static int uboot_env_read_redundant(struct block_cache *bc, struct uboot_env *env)
305 {
306 int rc;
307 char *buffer1 = (char *) malloc(env->env_size);
308 char *buffer2 = (char *) malloc(env->env_size);
309
310 // Reset which environment to write until we see which one was read.
311 env->write_primary = false;
312 env->write_secondary = false;
313 env->flags = 0;
314
315 OK_OR_CLEANUP_MSG(block_cache_pread(bc, buffer1, env->env_size, env->block_offset * FWUP_BLOCK_SIZE),
316 "unexpected error reading primary uboot environment: %s", strerror(errno));
317
318 OK_OR_CLEANUP_MSG(block_cache_pread(bc, buffer2, env->env_size, env->redundant_block_offset * FWUP_BLOCK_SIZE),
319 "unexpected error reading redundant uboot environment: %s", strerror(errno));
320
321 // The flags really are a counter. The bigger one (accounting for wrap) determines
322 // which was written last and should be tried first.
323 int8_t flag1 = buffer1[4];
324 int8_t flag2 = buffer2[4];
325
326 if (flag1 - flag2 >= 0) {
327 // Check the first environment first
328 rc = uboot_env_decode(env, buffer1);
329 if (rc == 0) {
330 env->flags = flag1;
331 env->write_secondary = true;
332 } else {
333 env->flags = flag2;
334 env->write_primary = true;
335 rc = uboot_env_decode(env, buffer2);
336 if (rc < 0)
337 env->write_secondary = true;
338 }
339 } else {
340 // Check the redundant environment first
341 rc = uboot_env_decode(env, buffer2);
342 if (rc == 0) {
343 env->flags = flag2;
344 env->write_primary = true;
345 } else {
346 env->flags = flag1;
347 env->write_secondary = true;
348 rc = uboot_env_decode(env, buffer1);
349 if (rc < 0)
350 env->write_primary = true;
351 }
352 }
353
354 cleanup:
355 free(buffer2);
356 free(buffer1);
357 return rc;
358 }
359
uboot_env_read(struct block_cache * bc,struct uboot_env * env)360 int uboot_env_read(struct block_cache *bc, struct uboot_env *env)
361 {
362 if (env->use_redundant)
363 return uboot_env_read_redundant(bc, env);
364 else
365 return uboot_env_read_non_redundant(bc, env);
366 }
367
uboot_env_write(struct block_cache * bc,struct uboot_env * env)368 int uboot_env_write(struct block_cache *bc, struct uboot_env *env)
369 {
370 int rc = 0;
371 char *buffer = (char *) malloc(env->env_size);
372
373 OK_OR_CLEANUP(uboot_env_encode(env, buffer));
374
375 if (env->use_redundant)
376 buffer[4] = env->flags + 1;
377
378 if (env->write_primary) {
379 OK_OR_CLEANUP_MSG(block_cache_pwrite(bc, buffer, env->env_size, env->block_offset * FWUP_BLOCK_SIZE, false),
380 "unexpected error writing uboot environment: %s", strerror(errno));
381 }
382
383 if (env->write_secondary) {
384 OK_OR_CLEANUP_MSG(block_cache_pwrite(bc, buffer, env->env_size, env->redundant_block_offset * FWUP_BLOCK_SIZE, false),
385 "unexpected error writing redundant uboot environment: %s", strerror(errno));
386 }
387 cleanup:
388 free(buffer);
389 return rc;
390 }
391