/* * Copyright (C) 2007 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 #include #include #include #include #include #include #include #include #include "common.h" #include "twmincrypt/twrsa.h" #include "twmincrypt/twsha.h" #include "minui/minui.h" #include "minzip/SysUtil.h" #include "minzip/Zip.h" #include "mtdutils/mounts.h" #include "mtdutils/mtdutils.h" #include "roots.h" #include "verifier.h" #include "ui.h" #include "variables.h" #include "data.hpp" #include "partitions.hpp" extern "C" { #include "extra-functions.h" int __system(const char *command); }; extern RecoveryUI* ui; #define ASSUMED_UPDATE_BINARY_NAME "META-INF/com/google/android/update-binary" #define PUBLIC_KEYS_FILE "/res/keys" // Default allocation of progress bar segments to operations static const int VERIFICATION_PROGRESS_TIME = 60; static const float VERIFICATION_PROGRESS_FRACTION = 0.25; static const float DEFAULT_FILES_PROGRESS_FRACTION = 0.4; static const float DEFAULT_IMAGE_PROGRESS_FRACTION = 0.1; enum { INSTALL_SUCCESS, INSTALL_ERROR, INSTALL_CORRUPT }; // Look for an RSA signature embedded in the .ZIP file comment given // the path to the zip. Verify it matches one of the given public // keys. // // Return VERIFY_SUCCESS, VERIFY_FAILURE (if any error is encountered // or no key matches the signature). int TWverify_file(const char* path, const RSAPublicKey *pKeys, unsigned int numKeys) { ui->SetProgress(0.0); FILE* f = fopen(path, "rb"); if (f == NULL) { LOGE("failed to open %s (%s)\n", path, strerror(errno)); return VERIFY_FAILURE; } // An archive with a whole-file signature will end in six bytes: // // (2-byte signature start) $ff $ff (2-byte comment size) // // (As far as the ZIP format is concerned, these are part of the // archive comment.) We start by reading this footer, this tells // us how far back from the end we have to start reading to find // the whole comment. #define FOOTER_SIZE 6 if (fseek(f, -FOOTER_SIZE, SEEK_END) != 0) { LOGE("failed to seek in %s (%s)\n", path, strerror(errno)); fclose(f); return VERIFY_FAILURE; } unsigned char footer[FOOTER_SIZE]; if (fread(footer, 1, FOOTER_SIZE, f) != FOOTER_SIZE) { LOGE("failed to read footer from %s (%s)\n", path, strerror(errno)); fclose(f); return VERIFY_FAILURE; } if (footer[2] != 0xff || footer[3] != 0xff) { fclose(f); return VERIFY_FAILURE; } size_t comment_size = footer[4] + (footer[5] << 8); size_t signature_start = footer[0] + (footer[1] << 8); LOGI("comment is %d bytes; signature %d bytes from end\n", comment_size, signature_start); if (signature_start - FOOTER_SIZE < RSANUMBYTES) { // "signature" block isn't big enough to contain an RSA block. LOGE("signature is too short\n"); fclose(f); return VERIFY_FAILURE; } #define EOCD_HEADER_SIZE 22 // The end-of-central-directory record is 22 bytes plus any // comment length. size_t eocd_size = comment_size + EOCD_HEADER_SIZE; if (fseek(f, -eocd_size, SEEK_END) != 0) { LOGE("failed to seek in %s (%s)\n", path, strerror(errno)); fclose(f); return VERIFY_FAILURE; } // Determine how much of the file is covered by the signature. // This is everything except the signature data and length, which // includes all of the EOCD except for the comment length field (2 // bytes) and the comment data. size_t signed_len = ftell(f) + EOCD_HEADER_SIZE - 2; unsigned char* eocd = (unsigned char*)malloc(eocd_size); if (eocd == NULL) { LOGE("malloc for EOCD record failed\n"); fclose(f); return VERIFY_FAILURE; } if (fread(eocd, 1, eocd_size, f) != eocd_size) { LOGE("failed to read eocd from %s (%s)\n", path, strerror(errno)); fclose(f); return VERIFY_FAILURE; } // If this is really is the EOCD record, it will begin with the // magic number $50 $4b $05 $06. if (eocd[0] != 0x50 || eocd[1] != 0x4b || eocd[2] != 0x05 || eocd[3] != 0x06) { LOGE("signature length doesn't match EOCD marker\n"); fclose(f); return VERIFY_FAILURE; } size_t i; for (i = 4; i < eocd_size-3; ++i) { if (eocd[i ] == 0x50 && eocd[i+1] == 0x4b && eocd[i+2] == 0x05 && eocd[i+3] == 0x06) { // if the sequence $50 $4b $05 $06 appears anywhere after // the real one, minzip will find the later (wrong) one, // which could be exploitable. Fail verification if // this sequence occurs anywhere after the real one. LOGE("EOCD marker occurs after start of EOCD\n"); fclose(f); return VERIFY_FAILURE; } } #define BUFFER_SIZE 4096 SHA_CTX ctx; SHA_init(&ctx); unsigned char* buffer = (unsigned char*)malloc(BUFFER_SIZE); if (buffer == NULL) { LOGE("failed to alloc memory for sha1 buffer\n"); fclose(f); return VERIFY_FAILURE; } double frac = -1.0; size_t so_far = 0; fseek(f, 0, SEEK_SET); while (so_far < signed_len) { size_t size = BUFFER_SIZE; if (signed_len - so_far < size) size = signed_len - so_far; if (fread(buffer, 1, size, f) != size) { LOGE("failed to read data from %s (%s)\n", path, strerror(errno)); fclose(f); return VERIFY_FAILURE; } SHA_update(&ctx, buffer, size); so_far += size; double f = so_far / (double)signed_len; if (f > frac + 0.02 || size == so_far) { ui->SetProgress(f); frac = f; } } fclose(f); free(buffer); const uint8_t* sha1 = SHA_final(&ctx); for (i = 0; i < numKeys; ++i) { // The 6 bytes is the "(signature_start) $ff $ff (comment_size)" that // the signing tool appends after the signature itself. if (RSA_verify(pKeys+i, eocd + eocd_size - 6 - RSANUMBYTES, RSANUMBYTES, sha1)) { LOGI("whole-file signature verified against key %d\n", i); free(eocd); return VERIFY_SUCCESS; } } free(eocd); LOGE("failed to verify whole-file signature\n"); return VERIFY_FAILURE; } // If the package contains an update binary, extract it and run it. static int try_update_binary(const char *path, ZipArchive *zip, int* wipe_cache) { const ZipEntry* binary_entry = mzFindZipEntry(zip, ASSUMED_UPDATE_BINARY_NAME); if (binary_entry == NULL) { mzCloseZipArchive(zip); return INSTALL_CORRUPT; } const char* binary = "/tmp/update_binary"; unlink(binary); int fd = creat(binary, 0755); if (fd < 0) { mzCloseZipArchive(zip); LOGE("Can't make %s\n", binary); return INSTALL_ERROR; } bool ok = mzExtractZipEntryToFile(zip, binary_entry, fd); close(fd); mzCloseZipArchive(zip); if (!ok) { LOGE("Can't copy %s\n", ASSUMED_UPDATE_BINARY_NAME); return INSTALL_ERROR; } int pipefd[2]; pipe(pipefd); // When executing the update binary contained in the package, the // arguments passed are: // // - the version number for this interface // // - an fd to which the program can write in order to update the // progress bar. The program can write single-line commands: // // progress // fill up the next part of of the progress bar // over seconds. If is zero, use // set_progress commands to manually control the // progress of this segment of the bar // // set_progress // should be between 0.0 and 1.0; sets the // progress bar within the segment defined by the most // recent progress command. // // firmware <"hboot"|"radio"> // arrange to install the contents of in the // given partition on reboot. // // (API v2: may start with "PACKAGE:" to // indicate taking a file from the OTA package.) // // (API v3: this command no longer exists.) // // ui_print // display on the screen. // // - the name of the package zip file. // const char** args = (const char**)malloc(sizeof(char*) * 5); args[0] = binary; args[1] = EXPAND(RECOVERY_API_VERSION); // defined in Android.mk char* temp = (char*)malloc(10); sprintf(temp, "%d", pipefd[1]); args[2] = temp; args[3] = (char*)path; args[4] = NULL; pid_t pid = fork(); if (pid == 0) { close(pipefd[0]); execv(binary, (char* const*)args); fprintf(stdout, "E:Can't run %s (%s)\n", binary, strerror(errno)); _exit(-1); } close(pipefd[1]); *wipe_cache = 0; char buffer[1024]; FILE* from_child = fdopen(pipefd[0], "r"); while (fgets(buffer, sizeof(buffer), from_child) != NULL) { char* command = strtok(buffer, " \n"); if (command == NULL) { continue; } else if (strcmp(command, "progress") == 0) { char* fraction_s = strtok(NULL, " \n"); char* seconds_s = strtok(NULL, " \n"); float fraction = strtof(fraction_s, NULL); int seconds = strtol(seconds_s, NULL, 10); ui->ShowProgress(fraction * (1-VERIFICATION_PROGRESS_FRACTION), seconds); } else if (strcmp(command, "set_progress") == 0) { char* fraction_s = strtok(NULL, " \n"); float fraction = strtof(fraction_s, NULL); ui->SetProgress(fraction); } else if (strcmp(command, "ui_print") == 0) { char* str = strtok(NULL, "\n"); if (str) { ui->Print("%s", str); } else { ui->Print("\n"); } } else if (strcmp(command, "wipe_cache") == 0) { *wipe_cache = 1; } else if (strcmp(command, "clear_display") == 0) { //ui->SetBackground(RecoveryUI::NONE); } else { LOGE("unknown command [%s]\n", command); } } fclose(from_child); int status; waitpid(pid, &status, 0); if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { LOGE("Error in %s\n(Status %d)\n", path, WEXITSTATUS(status)); return INSTALL_ERROR; } return INSTALL_SUCCESS; } // Reads a file containing one or more public keys as produced by // DumpPublicKey: this is an RSAPublicKey struct as it would appear // as a C source literal, eg: // // "{64,0xc926ad21,{1795090719,...,-695002876},{-857949815,...,1175080310}}" // // (Note that the braces and commas in this example are actual // characters the parser expects to find in the file; the ellipses // indicate more numbers omitted from this example.) // // The file may contain multiple keys in this format, separated by // commas. The last key must not be followed by a comma. // // Returns NULL if the file failed to parse, or if it contain zero keys. static RSAPublicKey* load_keys(const char* filename, int* numKeys) { RSAPublicKey* out = NULL; *numKeys = 0; FILE* f = fopen(filename, "r"); if (f == NULL) { LOGE("opening %s: %s\n", filename, strerror(errno)); goto exit; } { int i; bool done = false; while (!done) { ++*numKeys; out = (RSAPublicKey*)realloc(out, *numKeys * sizeof(RSAPublicKey)); RSAPublicKey* key = out + (*numKeys - 1); if (fscanf(f, " { %i , 0x%x , { %u", &(key->len), &(key->n0inv), &(key->n[0])) != 3) { goto exit; } if (key->len != RSANUMWORDS) { LOGE("key length (%d) does not match expected size\n", key->len); goto exit; } for (i = 1; i < key->len; ++i) { if (fscanf(f, " , %u", &(key->n[i])) != 1) goto exit; } if (fscanf(f, " } , { %u", &(key->rr[0])) != 1) goto exit; for (i = 1; i < key->len; ++i) { if (fscanf(f, " , %u", &(key->rr[i])) != 1) goto exit; } fscanf(f, " } } "); // if the line ends in a comma, this file has more keys. switch (fgetc(f)) { case ',': // more keys to come. break; case EOF: done = true; break; default: LOGE("unexpected character between keys\n"); goto exit; } } } fclose(f); return out; exit: if (f) fclose(f); free(out); *numKeys = 0; return NULL; } char* get_path (char* path) { char *s; /* Go to the end of the string. */ s = path + strlen(path) - 1; /* Strip off trailing /s (unless it is also the leading /). */ while (path < s && s[0] == '/') s--; /* Strip the last component. */ while (path <= s && s[0] != '/') s--; while (path < s && s[0] == '/') s--; if (s < path) return (char*)("."); s[1] = '\0'; return path; } /* Checks md5 for a path Return values: -1 : MD5 does not exist 0 : Failed 1 : Success */ int check_md5(const char* path) { FILE* fp; char command[255], line[512], actual_md5[512], md5[512]; char md5file[PATH_MAX + 40]; char *ptr; unsigned int line_len, index = 0; struct stat st; // Check to see if the filename.zip.md5 file exists strcpy(md5file, path); strcat(md5file, ".md5"); if (stat(md5file, &st) != 0) return -1; // no MD5 file found // Dump the md5 of the zip to a text file for reading sprintf(command, "md5sum '%s' > /tmp/md5output.txt", path); __system(command); fp = fopen("/tmp/md5output.txt", "rt"); if (fp == NULL) { LOGI("Unable to open /tmp/md5output.txt.\n"); return false; } while (fgets(line, sizeof(line), fp) != NULL) { line_len = strlen(line); for (index = 0; index < line_len; index++) { if (line[index] <= 32) line[index] = '\0'; } strcpy(actual_md5, line); break; } fclose(fp); // Read the filename.zip.md5 file fp = fopen(md5file, "rt"); if (fp == NULL) { LOGI("Unable to open '%s'.\n", md5file); return false; } while (fgets(line, sizeof(line), fp) != NULL) { line_len = strlen(line); for (index = 0; index < line_len; index++) { if (line[index] <= 32) line[index] = '\0'; } strcpy(md5, line); break; } fclose(fp); // Comare the 2 MD5 values if (strcmp(actual_md5, md5) == 0) return 1; LOGI("MD5 did not match: '%s' != '%s'\n", actual_md5, md5); return 0; } extern "C" int TWinstall_zip(const char* path, int* wipe_cache) { int err, zip_verify, md5_return, md5_verify; ui_print("Installing '%s'...\n", path); if (!PartitionManager.Mount_By_Path(path, 0)) { LOGE("Failed to mount '%s'\n", path); return -1; } ui_print("Checking for MD5 file...\n"); md5_return = check_md5(path); if (md5_return == 0) { // MD5 did not match. LOGE("Zip MD5 does not match.\nUnable to install zip.\n"); return INSTALL_CORRUPT; } else if (md5_return == -1) { DataManager::GetValue(TW_FORCE_MD5_CHECK_VAR, md5_verify); if (md5_verify == 1) { // Forced MD5 checking is on and no MD5 file found. LOGE("No MD5 file found for '%s'.\nDisable force MD5 check to avoid this error.\n", path); return INSTALL_CORRUPT; } else ui_print("No MD5 file found, this is not an error.\n"); } else if (md5_return == 1) ui_print("Zip MD5 matched.\n"); // MD5 found and matched. DataManager::GetValue(TW_SIGNED_ZIP_VERIFY_VAR, zip_verify); if (zip_verify) { ui_print("Verifying zip signature...\n"); int numKeys; RSAPublicKey* loadedKeys = load_keys(PUBLIC_KEYS_FILE, &numKeys); if (loadedKeys == NULL) { LOGE("Failed to load keys\n"); return -1; } LOGI("%d key(s) loaded from %s\n", numKeys, PUBLIC_KEYS_FILE); // Give verification half the progress bar... ui->Print("Verifying update package...\n"); ui->SetProgressType(RecoveryUI::DETERMINATE); ui->ShowProgress(VERIFICATION_PROGRESS_FRACTION, VERIFICATION_PROGRESS_TIME); err = TWverify_file(path, loadedKeys, numKeys); free(loadedKeys); LOGI("verify_file returned %d\n", err); if (err != VERIFY_SUCCESS) { LOGE("signature verification failed\n"); return -1; } } /* Try to open the package. */ ZipArchive zip; err = mzOpenZipArchive(path, &zip); if (err != 0) { LOGE("Can't open %s\n(%s)\n", path, err != -1 ? strerror(err) : "bad"); return INSTALL_CORRUPT; } /* Verify and install the contents of the package. */ return try_update_binary(path, &zip, wipe_cache); }