From a43c44f31f873d7e39d3c2872f0b9531b1584f11 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Queru Date: Thu, 12 Nov 2009 18:45:15 -0800 Subject: [PATCH 1/2] eclair snapshot --- Android.mk | 14 +- common.h | 2 +- default_recovery_ui.c | 65 ++++ install.c | 34 +- minui/graphics.c | 1 + minui/minui.h | 16 - recovery.c | 242 +++++++++------ recovery_ui.h | 76 +++++ res/images/icon_error.png | Bin 9616 -> 10398 bytes res/images/icon_firmware_install.png | Bin 11986 -> 12717 bytes res/images/icon_installing.png | Bin 10138 -> 11493 bytes roots.c | 1 + ui.c | 17 +- updater/Android.mk | 38 ++- updater/install.c | 73 ++++- updater/updater.c | 6 + verifier.c | 443 ++++++++------------------- verifier.h | 12 +- 18 files changed, 582 insertions(+), 458 deletions(-) create mode 100644 default_recovery_ui.c create mode 100644 recovery_ui.h mode change 100644 => 100755 res/images/icon_error.png mode change 100644 => 100755 res/images/icon_firmware_install.png mode change 100644 => 100755 res/images/icon_installing.png diff --git a/Android.mk b/Android.mk index ecf2640..deec80a 100644 --- a/Android.mk +++ b/Android.mk @@ -1,11 +1,11 @@ +ifneq ($(TARGET_SIMULATOR),true) +ifeq ($(TARGET_ARCH),arm) + LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) commands_recovery_local_path := $(LOCAL_PATH) -ifneq ($(TARGET_SIMULATOR),true) -ifeq ($(TARGET_ARCH),arm) - LOCAL_SRC_FILES := \ recovery.c \ bootloader.c \ @@ -31,7 +31,13 @@ LOCAL_CFLAGS += -DRECOVERY_API_VERSION=$(RECOVERY_API_VERSION) LOCAL_MODULE_TAGS := eng -LOCAL_STATIC_LIBRARIES := libminzip libunz libmtdutils libmincrypt +LOCAL_STATIC_LIBRARIES := +ifeq ($(TARGET_RECOVERY_UI_LIB),) + LOCAL_SRC_FILES += default_recovery_ui.c +else + LOCAL_STATIC_LIBRARIES += $(TARGET_RECOVERY_UI_LIB) +endif +LOCAL_STATIC_LIBRARIES += libminzip libunz libmtdutils libmincrypt LOCAL_STATIC_LIBRARIES += libminui libpixelflinger_static libpng libcutils LOCAL_STATIC_LIBRARIES += libstdc++ libc diff --git a/common.h b/common.h index 6761159..d962a0a 100644 --- a/common.h +++ b/common.h @@ -68,7 +68,7 @@ void ui_set_progress(float fraction); // 0.0 - 1.0 within the defined scope // Default allocation of progress bar segments to operations static const int VERIFICATION_PROGRESS_TIME = 60; -static const float VERIFICATION_PROGRESS_FRACTION = 0.5; +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; diff --git a/default_recovery_ui.c b/default_recovery_ui.c new file mode 100644 index 0000000..d4e6204 --- /dev/null +++ b/default_recovery_ui.c @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2009 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 "recovery_ui.h" +#include "common.h" + +char* MENU_HEADERS[] = { "Android system recovery utility", + "", + NULL }; + +char* MENU_ITEMS[] = { "reboot system now", + "apply sdcard:update.zip", + "wipe data/factory reset", + "wipe cache partition", + NULL }; + +int device_toggle_display(volatile char* key_pressed, int key_code) { + return key_code == KEY_HOME; +} + +int device_reboot_now(volatile char* key_pressed, int key_code) { + return 0; +} + +int device_handle_key(int key_code, int visible) { + if (visible) { + switch (key_code) { + case KEY_DOWN: + case KEY_VOLUMEDOWN: + return HIGHLIGHT_DOWN; + + case KEY_UP: + case KEY_VOLUMEUP: + return HIGHLIGHT_UP; + + case KEY_ENTER: + return SELECT_ITEM; + } + } + + return NO_ACTION; +} + +int device_perform_action(int which) { + return which; +} + +int device_wipe_data() { + return 0; +} diff --git a/install.c b/install.c index 2c557ea..7710cec 100644 --- a/install.c +++ b/install.c @@ -234,20 +234,8 @@ try_update_binary(const char *path, ZipArchive *zip) { } static int -handle_update_package(const char *path, ZipArchive *zip, - const RSAPublicKey *keys, int numKeys) +handle_update_package(const char *path, ZipArchive *zip) { - // Give verification half the progress bar... - ui_print("Verifying update package...\n"); - ui_show_progress( - VERIFICATION_PROGRESS_FRACTION, - VERIFICATION_PROGRESS_TIME); - - if (!verify_jar_signature(zip, keys, numKeys)) { - LOGE("Verification failed\n"); - return INSTALL_CORRUPT; - } - // Update should take the rest of the progress bar. ui_print("Installing update...\n"); @@ -360,10 +348,25 @@ install_package(const char *root_path) } 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_show_progress( + VERIFICATION_PROGRESS_FRACTION, + VERIFICATION_PROGRESS_TIME); + + int err; + err = verify_file(path, loadedKeys, numKeys); + free(loadedKeys); + LOGI("verify_file returned %d\n", err); + if (err != VERIFY_SUCCESS) { + LOGE("signature verification failed\n"); + return INSTALL_CORRUPT; + } + /* Try to open the package. */ ZipArchive zip; - int err = mzOpenZipArchive(path, &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; @@ -371,8 +374,7 @@ install_package(const char *root_path) /* Verify and install the contents of the package. */ - int status = handle_update_package(path, &zip, loadedKeys, numKeys); + int status = handle_update_package(path, &zip); mzCloseZipArchive(&zip); - free(loadedKeys); return status; } diff --git a/minui/graphics.c b/minui/graphics.c index 06c5fdf..adbfc09 100644 --- a/minui/graphics.c +++ b/minui/graphics.c @@ -115,6 +115,7 @@ static void set_active_framebuffer(unsigned n) if (n > 1) return; vi.yres_virtual = vi.yres * 2; vi.yoffset = n * vi.yres; + vi.bits_per_pixel = 16; if (ioctl(gr_fb_fd, FBIOPUT_VSCREENINFO, &vi) < 0) { perror("active fb swap failed"); } diff --git a/minui/minui.h b/minui/minui.h index 80b47a4..567d421 100644 --- a/minui/minui.h +++ b/minui/minui.h @@ -41,22 +41,6 @@ unsigned int gr_get_height(gr_surface surface); // see http://www.mjmwired.net/kernel/Documentation/input/ for info. struct input_event; -// Dream-specific key codes -#define KEY_DREAM_HOME 102 // = KEY_HOME -#define KEY_DREAM_RED 107 // = KEY_END -#define KEY_DREAM_VOLUMEDOWN 114 // = KEY_VOLUMEDOWN -#define KEY_DREAM_VOLUMEUP 115 // = KEY_VOLUMEUP -#define KEY_DREAM_SYM 127 // = KEY_COMPOSE -#define KEY_DREAM_MENU 139 // = KEY_MENU -#define KEY_DREAM_BACK 158 // = KEY_BACK -#define KEY_DREAM_FOCUS 211 // = KEY_HP (light touch on camera) -#define KEY_DREAM_CAMERA 212 // = KEY_CAMERA -#define KEY_DREAM_AT 215 // = KEY_EMAIL -#define KEY_DREAM_GREEN 231 -#define KEY_DREAM_FATTOUCH 258 // = BTN_2 ??? -#define KEY_DREAM_BALL 272 // = BTN_MOUSE -#define KEY_DREAM_TOUCH 330 // = BTN_TOUCH - int ev_init(void); void ev_exit(void); int ev_get(struct input_event *ev, unsigned dont_wait); diff --git a/recovery.c b/recovery.c index 0408abf..5b3f6e5 100644 --- a/recovery.c +++ b/recovery.c @@ -36,12 +36,14 @@ #include "minui/minui.h" #include "minzip/DirUtil.h" #include "roots.h" +#include "recovery_ui.h" static const struct option OPTIONS[] = { { "send_intent", required_argument, NULL, 's' }, { "update_package", required_argument, NULL, 'u' }, { "wipe_data", no_argument, NULL, 'w' }, { "wipe_cache", no_argument, NULL, 'c' }, + { NULL, 0, NULL, 0 }, }; static const char *COMMAND_FILE = "CACHE:recovery/command"; @@ -207,6 +209,15 @@ get_args(int *argc, char ***argv) { set_bootloader_message(&boot); } +static void +set_sdcard_update_bootloader_message() +{ + struct bootloader_message boot; + memset(&boot, 0, sizeof(boot)); + strlcpy(boot.command, "boot-recovery", sizeof(boot.command)); + strlcpy(boot.recovery, "recovery\n", sizeof(boot.recovery)); + set_bootloader_message(&boot); +} // clear the recovery command and prepare to boot a (hopefully working) system, // copy our log file to cache as well (for the system to read), and @@ -266,116 +277,159 @@ erase_root(const char *root) return format_root_device(root); } -static void -prompt_and_wait() -{ - char* headers[] = { "Android system recovery <" +static char** +prepend_title(char** headers) { + char* title[] = { "Android system recovery <" EXPAND(RECOVERY_API_VERSION) "e>", - "", - "Use trackball to highlight;", - "click to select.", - "", - NULL }; - - // these constants correspond to elements of the items[] list. -#define ITEM_REBOOT 0 -#define ITEM_APPLY_SDCARD 1 -#define ITEM_WIPE_DATA 2 -#define ITEM_WIPE_CACHE 3 - char* items[] = { "reboot system now [Home+Back]", - "apply sdcard:update.zip [Alt+S]", - "wipe data/factory reset [Alt+W]", - "wipe cache partition", + "", NULL }; + // count the number of lines in our title, plus the + // caller-provided headers. + int count = 0; + char** p; + for (p = title; *p; ++p, ++count); + for (p = headers; *p; ++p, ++count); + + char** new_headers = malloc((count+1) * sizeof(char*)); + char** h = new_headers; + for (p = title; *p; ++p, ++h) *h = *p; + for (p = headers; *p; ++p, ++h) *h = *p; + *h = NULL; + + return new_headers; +} + +static int +get_menu_selection(char** headers, char** items, int menu_only) { + // throw away keys pressed previously, so user doesn't + // accidentally trigger menu items. + ui_clear_key_queue(); + ui_start_menu(headers, items); int selected = 0; int chosen_item = -1; - finish_recovery(NULL); - ui_reset_progress(); - for (;;) { + while (chosen_item < 0) { int key = ui_wait_key(); - int alt = ui_key_pressed(KEY_LEFTALT) || ui_key_pressed(KEY_RIGHTALT); int visible = ui_text_visible(); - if (key == KEY_DREAM_BACK && ui_key_pressed(KEY_DREAM_HOME)) { - // Wait for the keys to be released, to avoid triggering - // special boot modes (like coming back into recovery!). - while (ui_key_pressed(KEY_DREAM_BACK) || - ui_key_pressed(KEY_DREAM_HOME)) { - usleep(1000); + int action = device_handle_key(key, visible); + + if (action < 0) { + switch (action) { + case HIGHLIGHT_UP: + --selected; + selected = ui_menu_select(selected); + break; + case HIGHLIGHT_DOWN: + ++selected; + selected = ui_menu_select(selected); + break; + case SELECT_ITEM: + chosen_item = selected; + break; + case NO_ACTION: + break; } - chosen_item = ITEM_REBOOT; - } else if (alt && key == KEY_W) { - chosen_item = ITEM_WIPE_DATA; - } else if (alt && key == KEY_S) { - chosen_item = ITEM_APPLY_SDCARD; - } else if ((key == KEY_DOWN || key == KEY_VOLUMEDOWN) && visible) { - ++selected; - selected = ui_menu_select(selected); - } else if ((key == KEY_UP || key == KEY_VOLUMEUP) && visible) { - --selected; - selected = ui_menu_select(selected); - } else if (key == BTN_MOUSE && visible) { - chosen_item = selected; + } else if (!menu_only) { + chosen_item = action; + } + } + + ui_end_menu(); + return chosen_item; +} + +static void +wipe_data(int confirm) { + if (confirm) { + static char** title_headers = NULL; + + if (title_headers == NULL) { + char* headers[] = { "Confirm wipe of all user data?", + " THIS CAN NOT BE UNDONE.", + "", + NULL }; + title_headers = prepend_title(headers); } - if (chosen_item >= 0) { - // turn off the menu, letting ui_print() to scroll output - // on the screen. - ui_end_menu(); + char* items[] = { " No", + " No", + " No", + " No", + " No", + " No", + " No", + " Yes -- delete all user data", // [7] + " No", + " No", + " No", + NULL }; - switch (chosen_item) { - case ITEM_REBOOT: - return; + int chosen_item = get_menu_selection(title_headers, items, 1); + if (chosen_item != 7) { + return; + } + } - case ITEM_WIPE_DATA: - ui_print("\n-- Wiping data...\n"); - erase_root("DATA:"); - erase_root("CACHE:"); - ui_print("Data wipe complete.\n"); - if (!ui_text_visible()) return; - break; + ui_print("\n-- Wiping data...\n"); + device_wipe_data(); + erase_root("DATA:"); + erase_root("CACHE:"); + ui_print("Data wipe complete.\n"); +} - case ITEM_WIPE_CACHE: - ui_print("\n-- Wiping cache...\n"); - erase_root("CACHE:"); - ui_print("Cache wipe complete.\n"); - if (!ui_text_visible()) return; - break; +static void +prompt_and_wait() +{ + char** headers = prepend_title(MENU_HEADERS); - case ITEM_APPLY_SDCARD: - ui_print("\n-- Install from sdcard...\n"); - int status = install_package(SDCARD_PACKAGE_FILE); - if (status != INSTALL_SUCCESS) { - ui_set_background(BACKGROUND_ICON_ERROR); - ui_print("Installation aborted.\n"); - } else if (!ui_text_visible()) { - return; // reboot if logs aren't visible + for (;;) { + finish_recovery(NULL); + ui_reset_progress(); + + int chosen_item = get_menu_selection(headers, MENU_ITEMS, 0); + + // device-specific code may take some action here. It may + // return one of the core actions handled in the switch + // statement below. + chosen_item = device_perform_action(chosen_item); + + switch (chosen_item) { + case ITEM_REBOOT: + return; + + case ITEM_WIPE_DATA: + wipe_data(ui_text_visible()); + if (!ui_text_visible()) return; + break; + + case ITEM_WIPE_CACHE: + ui_print("\n-- Wiping cache...\n"); + erase_root("CACHE:"); + ui_print("Cache wipe complete.\n"); + if (!ui_text_visible()) return; + break; + + case ITEM_APPLY_SDCARD: + ui_print("\n-- Install from sdcard...\n"); + set_sdcard_update_bootloader_message(); + int status = install_package(SDCARD_PACKAGE_FILE); + if (status != INSTALL_SUCCESS) { + ui_set_background(BACKGROUND_ICON_ERROR); + ui_print("Installation aborted.\n"); + } else if (!ui_text_visible()) { + return; // reboot if logs aren't visible + } else { + if (firmware_update_pending()) { + ui_print("\nReboot via menu to complete\n" + "installation.\n"); } else { - if (firmware_update_pending()) { - ui_print("\nReboot via home+back or menu\n" - "to complete installation.\n"); - } else { ui_print("\nInstall from sdcard complete.\n"); - } } - break; - } - - // if we didn't return from this function to reboot, show - // the menu again. - ui_start_menu(headers, items); - selected = 0; - chosen_item = -1; - - finish_recovery(NULL); - ui_reset_progress(); - - // throw away keys pressed while the command was running, - // so user doesn't accidentally trigger menu items. - ui_clear_key_queue(); + } + break; } } } @@ -432,10 +486,14 @@ main(int argc, char **argv) if (update_package != NULL) { status = install_package(update_package); if (status != INSTALL_SUCCESS) ui_print("Installation aborted.\n"); - } else if (wipe_data || wipe_cache) { - if (wipe_data && erase_root("DATA:")) status = INSTALL_ERROR; + } else if (wipe_data) { + if (device_wipe_data()) status = INSTALL_ERROR; + if (erase_root("DATA:")) status = INSTALL_ERROR; if (wipe_cache && erase_root("CACHE:")) status = INSTALL_ERROR; if (status != INSTALL_SUCCESS) ui_print("Data wipe failed.\n"); + } else if (wipe_cache) { + if (wipe_cache && erase_root("CACHE:")) status = INSTALL_ERROR; + if (status != INSTALL_SUCCESS) ui_print("Cache wipe failed.\n"); } else { status = INSTALL_ERROR; // No command specified } diff --git a/recovery_ui.h b/recovery_ui.h new file mode 100644 index 0000000..8818ef3 --- /dev/null +++ b/recovery_ui.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2009 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. + */ + +#ifndef _RECOVERY_UI_H +#define _RECOVERY_UI_H + +// Called in the input thread when a new key (key_code) is pressed. +// *key_pressed is an array of KEY_MAX+1 bytes indicating which other +// keys are already pressed. Return true if the text display should +// be toggled. +extern int device_toggle_display(volatile char* key_pressed, int key_code); + +// Called in the input thread when a new key (key_code) is pressed. +// *key_pressed is an array of KEY_MAX+1 bytes indicating which other +// keys are already pressed. Return true if the device should reboot +// immediately. +extern int device_reboot_now(volatile char* key_pressed, int key_code); + +// Called from the main thread when recovery is waiting for input and +// a key is pressed. key is the code of the key pressed; visible is +// true if the recovery menu is being shown. Implementations can call +// ui_key_pressed() to discover if other keys are being held down. +// Return one of the defined constants below in order to: +// +// - move the menu highlight (HIGHLIGHT_*) +// - invoke the highlighted item (SELECT_ITEM) +// - do nothing (NO_ACTION) +// - invoke a specific action (a menu position: any non-negative number) +extern int device_handle_key(int key, int visible); + +// Perform a recovery action selected from the menu. 'which' will be +// the item number of the selected menu item, or a non-negative number +// returned from device_handle_key(). The menu will be hidden when +// this is called; implementations can call ui_print() to print +// information to the screen. +extern int device_perform_action(int which); + +// Called when we do a wipe data/factory reset operation (either via a +// reboot from the main system with the --wipe_data flag, or when the +// user boots into recovery manually and selects the option from the +// menu.) Can perform whatever device-specific wiping actions are +// needed. Return 0 on success. The userdata and cache partitions +// are erased after this returns (whether it returns success or not). +int device_wipe_data(); + +#define NO_ACTION -1 + +#define HIGHLIGHT_UP -2 +#define HIGHLIGHT_DOWN -3 +#define SELECT_ITEM -4 + +#define ITEM_REBOOT 0 +#define ITEM_APPLY_SDCARD 1 +#define ITEM_WIPE_DATA 2 +#define ITEM_WIPE_CACHE 3 + +// Header text to display above the main menu. +extern char* MENU_HEADERS[]; + +// Text of menu items. +extern char* MENU_ITEMS[]; + +#endif diff --git a/res/images/icon_error.png b/res/images/icon_error.png old mode 100644 new mode 100755 index 7064c2e23e0deb59e7b366f4dc7cb40902763c76..90c8d878ad7576ae1a44cd34df4f2d5f80615d95 GIT binary patch literal 10398 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 IM6N<$f);A2s{jB1 literal 9616 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 z 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 diff --git a/res/images/icon_installing.png b/res/images/icon_installing.png old mode 100644 new mode 100755 index f24f2e33f12c7b8af3a2384720aea87436719725..d428f57d10235461521dcfef63cd20cdc96e3f31 GIT binary patch literal 11493 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 LNkvXXu0mjf8I%5Y literal 10138 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 0) || - (key_pressed[KEY_HOME] && ev.code == KEY_END && ev.value > 0)) { + if (ev.value > 0 && device_toggle_display(key_pressed, ev.code)) { pthread_mutex_lock(&gUpdateMutex); show_text = !show_text; update_screen_locked(); pthread_mutex_unlock(&gUpdateMutex); } - // Green+Menu+Red: reboot immediately - if (ev.code == KEY_DREAM_RED && - key_pressed[KEY_DREAM_MENU] && - key_pressed[KEY_DREAM_GREEN]) { + if (ev.value > 0 && device_reboot_now(key_pressed, ev.code)) { reboot(RB_AUTOBOOT); } } @@ -344,7 +339,11 @@ void ui_init(void) for (i = 0; BITMAPS[i].name != NULL; ++i) { int result = res_create_surface(BITMAPS[i].name, BITMAPS[i].surface); if (result < 0) { - LOGE("Missing bitmap %s\n(Code %d)\n", BITMAPS[i].name, result); + if (result == -2) { + LOGI("Bitmap %s missing header\n", BITMAPS[i].name); + } else { + LOGE("Missing bitmap %s\n(Code %d)\n", BITMAPS[i].name, result); + } *BITMAPS[i].surface = NULL; } } diff --git a/updater/Android.mk b/updater/Android.mk index 897b9d7..d4a4e33 100644 --- a/updater/Android.mk +++ b/updater/Android.mk @@ -18,11 +18,47 @@ LOCAL_MODULE_TAGS := eng LOCAL_SRC_FILES := $(updater_src_files) -LOCAL_STATIC_LIBRARIES := libapplypatch libedify libmtdutils libminzip libz +LOCAL_STATIC_LIBRARIES := $(TARGET_RECOVERY_UPDATER_LIBS) $(TARGET_RECOVERY_UPDATER_EXTRA_LIBS) +LOCAL_STATIC_LIBRARIES += libapplypatch libedify libmtdutils libminzip libz LOCAL_STATIC_LIBRARIES += libmincrypt libbz LOCAL_STATIC_LIBRARIES += libcutils libstdc++ libc LOCAL_C_INCLUDES += $(LOCAL_PATH)/.. +# Each library in TARGET_RECOVERY_UPDATER_LIBS should have a function +# named "Register_()". Here we emit a little C function that +# gets #included by updater.c. It calls all those registration +# functions. + +# Devices can also add libraries to TARGET_RECOVERY_UPDATER_EXTRA_LIBS. +# These libs are also linked in with updater, but we don't try to call +# any sort of registration function for these. Use this variable for +# any subsidiary static libraries required for your registered +# extension libs. + +inc := $(call intermediates-dir-for,PACKAGING,updater_extensions)/register.inc + +# During the first pass of reading the makefiles, we dump the list of +# extension libs to a temp file, then copy that to the ".list" file if +# it is different than the existing .list (if any). The register.inc +# file then uses the .list as a prerequisite, so it is only rebuilt +# (and updater.o recompiled) when the list of extension libs changes. + +junk := $(shell mkdir -p $(dir $(inc));\ + echo $(TARGET_RECOVERY_UPDATER_LIBS) > $(inc).temp;\ + diff -q $(inc).temp $(inc).list || cp -f $(inc).temp $(inc).list) + +$(inc) : libs := $(TARGET_RECOVERY_UPDATER_LIBS) +$(inc) : $(inc).list + $(hide) mkdir -p $(dir $@) + $(hide) echo "" > $@ + $(hide) $(foreach lib,$(libs),echo "extern void Register_$(lib)(void);" >> $@) + $(hide) echo "void RegisterDeviceExtensions() {" >> $@ + $(hide) $(foreach lib,$(libs),echo " Register_$(lib)();" >> $@) + $(hide) echo "}" >> $@ + +$(call intermediates-dir-for,EXECUTABLES,updater)/updater.o : $(inc) +LOCAL_C_INCLUDES += $(dir $(inc)) + LOCAL_MODULE := updater LOCAL_FORCE_STATIC_EXECUTABLE := true diff --git a/updater/install.c b/updater/install.c index c4f5e03..aa80d75 100644 --- a/updater/install.c +++ b/updater/install.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include "cutils/misc.h" @@ -85,6 +86,8 @@ char* MountFn(const char* name, State* state, int argc, Expr* argv[]) { } else { if (mount(location, mount_point, type, MS_NOATIME | MS_NODEV | MS_NODIRATIME, "") < 0) { + fprintf(stderr, "%s: failed to mount %s at %s: %s\n", + name, location, mount_point, strerror(errno)); result = strdup(""); } else { result = mount_point; @@ -347,6 +350,7 @@ char* PackageExtractFileFn(const char* name, State* state, // symlink target src1 src2 ... +// unlinks any previously existing src1, src2, etc before creating symlinks. char* SymlinkFn(const char* name, State* state, int argc, Expr* argv[]) { if (argc == 0) { return ErrorAbort(state, "%s() expects 1+ args, got %d", name, argc); @@ -363,7 +367,16 @@ char* SymlinkFn(const char* name, State* state, int argc, Expr* argv[]) { int i; for (i = 0; i < argc-1; ++i) { - symlink(target, srcs[i]); + if (unlink(srcs[i]) < 0) { + if (errno != ENOENT) { + fprintf(stderr, "%s: failed to remove %s: %s\n", + name, srcs[i], strerror(errno)); + } + } + if (symlink(target, srcs[i]) < 0) { + fprintf(stderr, "%s: failed to symlink %s to %s: %s\n", + name, srcs[i], target, strerror(errno)); + } free(srcs[i]); } free(srcs); @@ -423,8 +436,14 @@ char* SetPermFn(const char* name, State* state, int argc, Expr* argv[]) { } for (i = 3; i < argc; ++i) { - chown(args[i], uid, gid); - chmod(args[i], mode); + if (chown(args[i], uid, gid) < 0) { + fprintf(stderr, "%s: chown of %s to %d %d failed: %s\n", + name, args[i], uid, gid, strerror(errno)); + } + if (chmod(args[i], mode) < 0) { + fprintf(stderr, "%s: chmod of %s to %o failed: %s\n", + name, args[i], mode, strerror(errno)); + } } } result = strdup(""); @@ -759,6 +778,52 @@ char* UIPrintFn(const char* name, State* state, int argc, Expr* argv[]) { return buffer; } +char* RunProgramFn(const char* name, State* state, int argc, Expr* argv[]) { + if (argc < 1) { + return ErrorAbort(state, "%s() expects at least 1 arg", name); + } + char** args = ReadVarArgs(state, argc, argv); + if (args == NULL) { + return NULL; + } + + char** args2 = malloc(sizeof(char*) * (argc+1)); + memcpy(args2, args, sizeof(char*) * argc); + args2[argc] = NULL; + + fprintf(stderr, "about to run program [%s] with %d args\n", args2[0], argc); + + pid_t child = fork(); + if (child == 0) { + execv(args2[0], args2); + fprintf(stderr, "run_program: execv failed: %s\n", strerror(errno)); + _exit(1); + } + int status; + waitpid(child, &status, 0); + if (WIFEXITED(status)) { + if (WEXITSTATUS(status) != 0) { + fprintf(stderr, "run_program: child exited with status %d\n", + WEXITSTATUS(status)); + } + } else if (WIFSIGNALED(status)) { + fprintf(stderr, "run_program: child terminated by signal %d\n", + WTERMSIG(status)); + } + + int i; + for (i = 0; i < argc; ++i) { + free(args[i]); + } + free(args); + free(args2); + + char buffer[20]; + sprintf(buffer, "%d", status); + + return strdup(buffer); +} + void RegisterInstallFunctions() { RegisterFunction("mount", MountFn); @@ -785,4 +850,6 @@ void RegisterInstallFunctions() { RegisterFunction("apply_patch_space", ApplyPatchFn); RegisterFunction("ui_print", UIPrintFn); + + RegisterFunction("run_program", RunProgramFn); } diff --git a/updater/updater.c b/updater/updater.c index 31d93ae..1aa277c 100644 --- a/updater/updater.c +++ b/updater/updater.c @@ -23,6 +23,11 @@ #include "install.h" #include "minzip/Zip.h" +// Generated by the makefile, this function defines the +// RegisterDeviceExtensions() function, which calls all the +// registration functions for device-specific extensions. +#include "register.inc" + // Where in the package we expect to find the edify script to execute. // (Note it's "updateR-script", not the older "update-script".) #define SCRIPT_NAME "META-INF/com/google/android/updater-script" @@ -76,6 +81,7 @@ int main(int argc, char** argv) { RegisterBuiltins(); RegisterInstallFunctions(); + RegisterDeviceExtensions(); FinishRegistration(); // Parse the script. diff --git a/verifier.c b/verifier.c index 1180ae8..f2491a1 100644 --- a/verifier.c +++ b/verifier.c @@ -17,345 +17,168 @@ #include "common.h" #include "verifier.h" -#include "minzip/Zip.h" #include "mincrypt/rsa.h" #include "mincrypt/sha.h" -#include /* required for resolv.h */ -#include /* for base64 codec */ #include +#include +#include -/* Return an allocated buffer with the contents of a zip file entry. */ -static char *slurpEntry(const ZipArchive *pArchive, const ZipEntry *pEntry) { - if (!mzIsZipEntryIntact(pArchive, pEntry)) { - UnterminatedString fn = mzGetZipEntryFileName(pEntry); - LOGE("Invalid %.*s\n", fn.len, fn.str); - return NULL; +// 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 verify_file(const char* path, const RSAPublicKey *pKeys, unsigned int numKeys) { + ui_set_progress(0.0); + + FILE* f = fopen(path, "rb"); + if (f == NULL) { + LOGE("failed to open %s (%s)\n", path, strerror(errno)); + return VERIFY_FAILURE; } - int len = mzGetZipEntryUncompLen(pEntry); - char *buf = malloc(len + 1); - if (buf == NULL) { - UnterminatedString fn = mzGetZipEntryFileName(pEntry); - LOGE("Can't allocate %d bytes for %.*s\n", len, fn.len, fn.str); - return NULL; + // An archive with a whole-file signature will end in six bytes: + // + // $ff $ff (2-byte comment size) (2-byte signature start) + // + // (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; } - if (!mzReadZipEntry(pArchive, pEntry, buf, len)) { - UnterminatedString fn = mzGetZipEntryFileName(pEntry); - LOGE("Can't read %.*s\n", fn.len, fn.str); - free(buf); - return NULL; + 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; } - buf[len] = '\0'; - return buf; -} - - -struct DigestContext { - SHA_CTX digest; - unsigned *doneBytes; - unsigned totalBytes; -}; - - -/* mzProcessZipEntryContents callback to update an SHA-1 hash context. */ -static bool updateHash(const unsigned char *data, int dataLen, void *cookie) { - struct DigestContext *context = (struct DigestContext *) cookie; - SHA_update(&context->digest, data, dataLen); - if (context->doneBytes != NULL) { - *context->doneBytes += dataLen; - if (context->totalBytes > 0) { - ui_set_progress(*context->doneBytes * 1.0 / context->totalBytes); - } - } - return true; -} - - -/* Get the SHA-1 digest of a zip file entry. */ -static bool digestEntry(const ZipArchive *pArchive, const ZipEntry *pEntry, - unsigned *doneBytes, unsigned totalBytes, - uint8_t digest[SHA_DIGEST_SIZE]) { - struct DigestContext context; - SHA_init(&context.digest); - context.doneBytes = doneBytes; - context.totalBytes = totalBytes; - if (!mzProcessZipEntryContents(pArchive, pEntry, updateHash, &context)) { - UnterminatedString fn = mzGetZipEntryFileName(pEntry); - LOGE("Can't digest %.*s\n", fn.len, fn.str); - return false; + if (footer[2] != 0xff || footer[3] != 0xff) { + fclose(f); + return VERIFY_FAILURE; } - memcpy(digest, SHA_final(&context.digest), SHA_DIGEST_SIZE); + int comment_size = footer[4] + (footer[5] << 8); + int signature_start = footer[0] + (footer[1] << 8); + LOGI("comment is %d bytes; signature %d bytes from end\n", + comment_size, signature_start); -#ifdef LOG_VERBOSE - UnterminatedString fn = mzGetZipEntryFileName(pEntry); - char base64[SHA_DIGEST_SIZE * 3]; - b64_ntop(digest, SHA_DIGEST_SIZE, base64, sizeof(base64)); - LOGV("sha1(%.*s) = %s\n", fn.len, fn.str, base64); -#endif + 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; + } - return true; -} +#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; -/* Find a /META-INF/xxx.SF signature file signed by a matching xxx.RSA file. */ -static const ZipEntry *verifySignature(const ZipArchive *pArchive, - const RSAPublicKey *pKeys, unsigned int numKeys) { - static const char prefix[] = "META-INF/"; - static const char rsa[] = ".RSA", sf[] = ".SF"; + if (fseek(f, -eocd_size, SEEK_END) != 0) { + LOGE("failed to seek in %s (%s)\n", path, strerror(errno)); + fclose(f); + return VERIFY_FAILURE; + } - unsigned int i, j; - for (i = 0; i < mzZipEntryCount(pArchive); ++i) { - const ZipEntry *rsaEntry = mzGetZipEntryAt(pArchive, i); - UnterminatedString rsaName = mzGetZipEntryFileName(rsaEntry); - int rsaLen = mzGetZipEntryUncompLen(rsaEntry); - if (rsaLen >= RSANUMBYTES && rsaName.len > sizeof(prefix) && - !strncmp(rsaName.str, prefix, sizeof(prefix) - 1) && - !strncmp(rsaName.str + rsaName.len - sizeof(rsa) + 1, - rsa, sizeof(rsa) - 1)) { - char *sfName = malloc(rsaName.len - sizeof(rsa) + sizeof(sf) + 1); - if (sfName == NULL) { - LOGE("Can't allocate %d bytes for filename\n", rsaName.len); - continue; - } + // 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; - /* Replace .RSA with .SF */ - strncpy(sfName, rsaName.str, rsaName.len - sizeof(rsa) + 1); - strcpy(sfName + rsaName.len - sizeof(rsa) + 1, sf); - const ZipEntry *sfEntry = mzFindZipEntry(pArchive, sfName); + unsigned char* eocd = 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 (sfEntry == NULL) { - LOGW("Missing signature file %s\n", sfName); - free(sfName); - continue; - } + // 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; + } - free(sfName); - - uint8_t sfDigest[SHA_DIGEST_SIZE]; - if (!digestEntry(pArchive, sfEntry, NULL, 0, sfDigest)) continue; - - char *rsaBuf = slurpEntry(pArchive, rsaEntry); - if (rsaBuf == NULL) continue; - - /* Try to verify the signature with all the keys. */ - uint8_t *sig = (uint8_t *) rsaBuf + rsaLen - RSANUMBYTES; - for (j = 0; j < numKeys; ++j) { - if (RSA_verify(&pKeys[j], sig, RSANUMBYTES, sfDigest)) { - free(rsaBuf); - LOGI("Verified %.*s\n", rsaName.len, rsaName.str); - return sfEntry; - } - } - - free(rsaBuf); - LOGW("Can't verify %.*s\n", rsaName.len, rsaName.str); + int i; + for (i = 4; i < eocd_size-3; ++i) { + if (eocd[i ] == 0x50 && eocd[i+1] == 0x4b && + eocd[i+2] == 0x05 && eocd[i+1] == 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; } } - LOGE("No signature (%d files)\n", mzZipEntryCount(pArchive)); - return NULL; -} +#define BUFFER_SIZE 4096 + SHA_CTX ctx; + SHA_init(&ctx); + unsigned char* buffer = malloc(BUFFER_SIZE); + if (buffer == NULL) { + LOGE("failed to alloc memory for sha1 buffer\n"); + fclose(f); + return VERIFY_FAILURE; + } -/* Verify /META-INF/MANIFEST.MF against the digest in a signature file. */ -static const ZipEntry *verifyManifest(const ZipArchive *pArchive, - const ZipEntry *sfEntry) { - static const char prefix[] = "SHA1-Digest-Manifest: ", eol[] = "\r\n"; - uint8_t expected[SHA_DIGEST_SIZE + 3], actual[SHA_DIGEST_SIZE]; - - char *sfBuf = slurpEntry(pArchive, sfEntry); - if (sfBuf == NULL) return NULL; - - char *line, *save; - for (line = strtok_r(sfBuf, eol, &save); line != NULL; - line = strtok_r(NULL, eol, &save)) { - if (!strncasecmp(prefix, line, sizeof(prefix) - 1)) { - UnterminatedString fn = mzGetZipEntryFileName(sfEntry); - const char *digest = line + sizeof(prefix) - 1; - int n = b64_pton(digest, expected, sizeof(expected)); - if (n != SHA_DIGEST_SIZE) { - LOGE("Invalid base64 in %.*s: %s (%d)\n", - fn.len, fn.str, digest, n); - line = NULL; - } - break; + double frac = -1.0; + size_t so_far = 0; + fseek(f, 0, SEEK_SET); + while (so_far < signed_len) { + int 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_set_progress(f); + frac = f; } } + fclose(f); + free(buffer); - free(sfBuf); - - if (line == NULL) { - LOGE("No digest manifest in signature file\n"); - return false; - } - - const char *mfName = "META-INF/MANIFEST.MF"; - const ZipEntry *mfEntry = mzFindZipEntry(pArchive, mfName); - if (mfEntry == NULL) { - LOGE("No manifest file %s\n", mfName); - return NULL; - } - - if (!digestEntry(pArchive, mfEntry, NULL, 0, actual)) return NULL; - if (memcmp(expected, actual, SHA_DIGEST_SIZE)) { - UnterminatedString fn = mzGetZipEntryFileName(sfEntry); - LOGE("Wrong digest for %s in %.*s\n", mfName, fn.len, fn.str); - return NULL; - } - - LOGI("Verified %s\n", mfName); - return mfEntry; -} - - -/* Verify all the files in a Zip archive against the manifest. */ -static bool verifyArchive(const ZipArchive *pArchive, const ZipEntry *mfEntry) { - static const char namePrefix[] = "Name: "; - static const char contPrefix[] = " "; // Continuation of the filename - static const char digestPrefix[] = "SHA1-Digest: "; - static const char eol[] = "\r\n"; - - char *mfBuf = slurpEntry(pArchive, mfEntry); - if (mfBuf == NULL) return false; - - /* we're using calloc() here, so the initial state of the array is false */ - bool *unverified = (bool *) calloc(mzZipEntryCount(pArchive), sizeof(bool)); - if (unverified == NULL) { - LOGE("Can't allocate valid flags\n"); - free(mfBuf); - return false; - } - - /* Mark all the files in the archive that need to be verified. - * As we scan the manifest and check signatures, we'll unset these flags. - * At the end, we'll make sure that all the flags are unset. - */ - - unsigned i, totalBytes = 0; - for (i = 0; i < mzZipEntryCount(pArchive); ++i) { - const ZipEntry *entry = mzGetZipEntryAt(pArchive, i); - UnterminatedString fn = mzGetZipEntryFileName(entry); - int len = mzGetZipEntryUncompLen(entry); - - // Don't validate: directories, the manifest, *.RSA, and *.SF. - - if (entry == mfEntry) { - LOGV("Skipping manifest %.*s\n", fn.len, fn.str); - } else if (fn.len > 0 && fn.str[fn.len-1] == '/' && len == 0) { - LOGV("Skipping directory %.*s\n", fn.len, fn.str); - } else if (!strncasecmp(fn.str, "META-INF/", 9) && ( - !strncasecmp(fn.str + fn.len - 4, ".RSA", 4) || - !strncasecmp(fn.str + fn.len - 3, ".SF", 3))) { - LOGV("Skipping signature %.*s\n", fn.len, fn.str); - } else { - unverified[i] = true; - totalBytes += len; + const uint8_t* sha1 = SHA_final(&ctx); + for (i = 0; i < numKeys; ++i) { + // The 6 bytes is the "$ff $ff (signature_start) (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\n"); + free(eocd); + return VERIFY_SUCCESS; } } - - unsigned doneBytes = 0; - char *line, *save, *name = NULL; - for (line = strtok_r(mfBuf, eol, &save); line != NULL; - line = strtok_r(NULL, eol, &save)) { - if (!strncasecmp(line, namePrefix, sizeof(namePrefix) - 1)) { - // "Name:" introducing a new stanza - if (name != NULL) { - LOGE("No digest:\n %s\n", name); - break; - } - - name = strdup(line + sizeof(namePrefix) - 1); - if (name == NULL) { - LOGE("Can't copy filename in %s\n", line); - break; - } - } else if (!strncasecmp(line, contPrefix, sizeof(contPrefix) - 1)) { - // Continuing a long name (nothing else should be continued) - const char *tail = line + sizeof(contPrefix) - 1; - if (name == NULL) { - LOGE("Unexpected continuation:\n %s\n", tail); - } - - char *concat; - if (asprintf(&concat, "%s%s", name, tail) < 0) { - LOGE("Can't append continuation %s\n", tail); - break; - } - free(name); - name = concat; - } else if (!strncasecmp(line, digestPrefix, sizeof(digestPrefix) - 1)) { - // "Digest:" supplying a hash code for the current stanza - const char *base64 = line + sizeof(digestPrefix) - 1; - if (name == NULL) { - LOGE("Unexpected digest:\n %s\n", base64); - break; - } - - const ZipEntry *entry = mzFindZipEntry(pArchive, name); - if (entry == NULL) { - LOGE("Missing file:\n %s\n", name); - break; - } - if (!mzIsZipEntryIntact(pArchive, entry)) { - LOGE("Corrupt file:\n %s\n", name); - break; - } - if (!unverified[mzGetZipEntryIndex(pArchive, entry)]) { - LOGE("Unexpected file:\n %s\n", name); - break; - } - - uint8_t expected[SHA_DIGEST_SIZE + 3], actual[SHA_DIGEST_SIZE]; - int n = b64_pton(base64, expected, sizeof(expected)); - if (n != SHA_DIGEST_SIZE) { - LOGE("Invalid base64:\n %s\n %s\n", name, base64); - break; - } - - if (!digestEntry(pArchive, entry, &doneBytes, totalBytes, actual) || - memcmp(expected, actual, SHA_DIGEST_SIZE) != 0) { - LOGE("Wrong digest:\n %s\n", name); - break; - } - - LOGI("Verified %s\n", name); - unverified[mzGetZipEntryIndex(pArchive, entry)] = false; - free(name); - name = NULL; - } - } - - if (name != NULL) free(name); - free(mfBuf); - - for (i = 0; i < mzZipEntryCount(pArchive) && !unverified[i]; ++i) ; - free(unverified); - - // This means we didn't get to the end of the manifest successfully. - if (line != NULL) return false; - - if (i < mzZipEntryCount(pArchive)) { - const ZipEntry *entry = mzGetZipEntryAt(pArchive, i); - UnterminatedString fn = mzGetZipEntryFileName(entry); - LOGE("No digest for %.*s\n", fn.len, fn.str); - return false; - } - - return true; -} - - -bool verify_jar_signature(const ZipArchive *pArchive, - const RSAPublicKey *pKeys, int numKeys) { - const ZipEntry *sfEntry = verifySignature(pArchive, pKeys, numKeys); - if (sfEntry == NULL) return false; - - const ZipEntry *mfEntry = verifyManifest(pArchive, sfEntry); - if (mfEntry == NULL) return false; - - return verifyArchive(pArchive, mfEntry); + free(eocd); + LOGE("failed to verify whole-file signature\n"); + return VERIFY_FAILURE; } diff --git a/verifier.h b/verifier.h index d784dce..1bdfca6 100644 --- a/verifier.h +++ b/verifier.h @@ -17,14 +17,14 @@ #ifndef _RECOVERY_VERIFIER_H #define _RECOVERY_VERIFIER_H -#include "minzip/Zip.h" #include "mincrypt/rsa.h" -/* - * Check the digital signature (as applied by jarsigner) on a Zip archive. - * Every file in the archive must be signed by one of the supplied RSA keys. +/* Look in the file for a signature footer, and verify that it + * matches one of the given keys. Return one of the constants below. */ -bool verify_jar_signature(const ZipArchive *pArchive, - const RSAPublicKey *pKeys, int numKeys); +int verify_file(const char* path, const RSAPublicKey *pKeys, unsigned int numKeys); + +#define VERIFY_SUCCESS 0 +#define VERIFY_FAILURE 1 #endif /* _RECOVERY_VERIFIER_H */ From b765729081399b6826c9d45d77c9c58ca2f32816 Mon Sep 17 00:00:00 2001 From: The Android Open Source Project Date: Tue, 12 Jan 2010 15:18:06 -0800 Subject: [PATCH 2/2] android-2.1_r1 snapshot --- mtdutils/mtdutils.c | 17 ++++++++++++----- verifier.c | 2 +- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/mtdutils/mtdutils.c b/mtdutils/mtdutils.c index fc06766..8d32520 100644 --- a/mtdutils/mtdutils.c +++ b/mtdutils/mtdutils.c @@ -283,19 +283,26 @@ static int read_block(const MtdPartition *partition, int fd, char *data) return -1; } - off_t pos = lseek(fd, 0, SEEK_CUR); + loff_t pos = lseek64(fd, 0, SEEK_CUR); + ssize_t size = partition->erase_size; + int mgbb; + while (pos + size <= (int) partition->size) { - if (lseek(fd, pos, SEEK_SET) != pos || read(fd, data, size) != size) { - fprintf(stderr, "mtd: read error at 0x%08lx (%s)\n", + if (lseek64(fd, pos, SEEK_SET) != pos || read(fd, data, size) != size) { + fprintf(stderr, "mtd: read error at 0x%08llx (%s)\n", pos, strerror(errno)); } else if (ioctl(fd, ECCGETSTATS, &after)) { fprintf(stderr, "mtd: ECCGETSTATS error (%s)\n", strerror(errno)); return -1; } else if (after.failed != before.failed) { - fprintf(stderr, "mtd: ECC errors (%d soft, %d hard) at 0x%08lx\n", + fprintf(stderr, "mtd: ECC errors (%d soft, %d hard) at 0x%08llx\n", after.corrected - before.corrected, after.failed - before.failed, pos); + } else if ((mgbb = ioctl(fd, MEMGETBADBLOCK, &pos))) { + fprintf(stderr, + "mtd: MEMGETBADBLOCK returned %d at 0x%08llx (errno=%d)\n", + mgbb, pos, errno); } else { int i; for (i = 0; i < size; ++i) { @@ -303,7 +310,7 @@ static int read_block(const MtdPartition *partition, int fd, char *data) return 0; // Success! } } - fprintf(stderr, "mtd: read all-zero block at 0x%08lx; skipping\n", + fprintf(stderr, "mtd: read all-zero block at 0x%08llx; skipping\n", pos); } diff --git a/verifier.c b/verifier.c index f2491a1..164fb4a 100644 --- a/verifier.c +++ b/verifier.c @@ -123,7 +123,7 @@ int verify_file(const char* path, const RSAPublicKey *pKeys, unsigned int numKey int i; for (i = 4; i < eocd_size-3; ++i) { if (eocd[i ] == 0x50 && eocd[i+1] == 0x4b && - eocd[i+2] == 0x05 && eocd[i+1] == 0x06) { + 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