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