summaryrefslogtreecommitdiff
path: root/updater/firmware.c
blob: 4672e18ffc8209349c0a64c1fc046a03f1790217 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
/*
 * Copyright (C) 2008 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "bootloader.h"
#include "common.h"
#include "firmware.h"
#include "mtdutils/mtdutils.h"
#include "mincrypt/sha.h"

#include <errno.h>
#include <string.h>
#include <sys/reboot.h>

/* Bootloader / Recovery Flow
 *
 * On every boot, the bootloader will read the bootloader_message
 * from flash and check the command field.  The bootloader should
 * deal with the command field not having a 0 terminator correctly
 * (so as to not crash if the block is invalid or corrupt).
 *
 * The bootloader will have to publish the partition that contains
 * the bootloader_message to the linux kernel so it can update it.
 *
 * if command == "boot-recovery" -> boot recovery.img
 * else if command == "update-radio" -> update radio image (below)
 * else if command == "update-hboot" -> update hboot image (below)
 * else -> boot boot.img (normal boot)
 *
 * Radio/Hboot Update Flow
 * 1. the bootloader will attempt to load and validate the header
 * 2. if the header is invalid, status="invalid-update", goto #8
 * 3. display the busy image on-screen
 * 4. if the update image is invalid, status="invalid-radio-image", goto #8
 * 5. attempt to update the firmware (depending on the command)
 * 6. if successful, status="okay", goto #8
 * 7. if failed, and the old image can still boot, status="failed-update"
 * 8. write the bootloader_message, leaving the recovery field
 *    unchanged, updating status, and setting command to
 *    "boot-recovery"
 * 9. reboot
 *
 * The bootloader will not modify or erase the cache partition.
 * It is recovery's responsibility to clean up the mess afterwards.
 */

#undef LOGE
#define LOGE(...) fprintf(stderr, "E:" __VA_ARGS__)


int verify_image(const uint8_t* expected_sha1) {
    MtdPartition* part = mtd_find_partition_by_name(CACHE_NAME);
    if (part == NULL) {
        printf("verify image: failed to find cache partition\n");
        return -1;
    }

    size_t block_size;
    if (mtd_partition_info(part, NULL, &block_size, NULL) != 0) {
        printf("verify image: failed to get cache partition block size\n");
        return -1;
    }
    printf("block size is 0x%x\n", block_size);

    char* buffer = malloc(block_size);
    if (buffer == NULL) {
        printf("verify image: failed to allocate memory\n");
        return -1;
    }

    MtdReadContext* ctx = mtd_read_partition(part);
    if (ctx == NULL) {
        printf("verify image: failed to init read context\n");
        return -1;
    }

    size_t pos = 0;
    if (mtd_read_data(ctx, buffer, block_size) != block_size) {
        printf("verify image: failed to read header\n");
        return -1;
    }
    pos += block_size;

    if (strncmp(buffer, "MSM-RADIO-UPDATE", 16) != 0) {
        printf("verify image: header missing magic\n");
        return -1;
    }

    unsigned image_offset = *(unsigned*)(buffer+24);
    unsigned image_length = *(unsigned*)(buffer+28);
    printf("image offset 0x%x length 0x%x\n", image_offset, image_length);

    while (pos < image_offset) {
        size_t to_read = image_offset - pos;
        if (to_read > block_size) to_read = block_size;
        ssize_t read = mtd_read_data(ctx, buffer, to_read);
        if (read < 0) {
            printf("verify image: failed to skip to image start\n");
            return -1;
        }
        pos += read;
    }

    SHA_CTX sha_ctx;
    SHA_init(&sha_ctx);

    size_t total = 0;
    while (total < image_length) {
        size_t to_read = image_length - total;
        if (to_read > block_size) to_read = block_size;
        ssize_t read = mtd_read_data(ctx, buffer, to_read);
        if (read < 0) {
            printf("verify image: failed reading image (read 0x%x so far)\n",
                   total);
            return -1;
        }
        SHA_update(&sha_ctx, buffer, read);
        total += read;
    }

    free(buffer);

    const uint8_t* sha1 = SHA_final(&sha_ctx);
    if (memcmp(sha1, expected_sha1, SHA_DIGEST_SIZE) != 0) {
        printf("verify image: sha1 doesn't match\n");
        return -1;
    }

    printf("verify image: verification succeeded\n");

    return 0;
}

int install_firmware_update(const char *update_type,
                            const char *update_data,
                            size_t update_length,
                            int width, int height, int bpp,
                            const char* busy_image,
                            const char* fail_image,
                            const char *log_filename,
                            const uint8_t* expected_sha1) {
    if (update_data == NULL || update_length == 0) return 0;

    mtd_scan_partitions();

    /* We destroy the cache partition to pass the update image to the
     * bootloader, so all we can really do afterwards is wipe cache and reboot.
     * Set up this instruction now, in case we're interrupted while writing.
     */

    struct bootloader_message boot;
    memset(&boot, 0, sizeof(boot));
    strlcpy(boot.command, "boot-recovery", sizeof(boot.command));
    strlcpy(boot.recovery, "recovery\n--wipe_cache\n", sizeof(boot.command));
    if (set_bootloader_message(&boot)) return -1;

    if (write_update_for_bootloader(
            update_data, update_length,
            width, height, bpp, busy_image, fail_image, log_filename)) {
        LOGE("Can't write %s image\n(%s)\n", update_type, strerror(errno));
        return -1;
    }

    if (verify_image(expected_sha1) == 0) {
        /* The update image is fully written, so now we can instruct
         * the bootloader to install it.  (After doing so, it will
         * come back here, and we will wipe the cache and reboot into
         * the system.)
         */
        snprintf(boot.command, sizeof(boot.command), "update-%s", update_type);
        if (set_bootloader_message(&boot)) {
            return -1;
        }

        reboot(RB_AUTOBOOT);

        // Can't reboot?  WTF?
        LOGE("Can't reboot\n");
    }
    return -1;
}