075ad800c5
Implement a new method of sideloading over ADB that does not require the entire package to be held in RAM (useful for low-RAM devices and devices using block OTA where we'd rather have more RAM available for binary patching). We communicate with the host using a new adb service called "sideload-host", which makes the host act as a server, sending us different parts of the package file on request. We create a FUSE filesystem that creates a virtual file "/sideload/package.zip" that is backed by the ADB connection -- users see a normal file, but when they read from the file we're actually fetching the data from the adb host. This file is then passed to the verification and installation systems like any other. To prevent a malicious adb host implementation from serving different data to the verification and installation phases of sideloading, the FUSE filesystem verifies that the contents of the file don't change between reads -- every time we fetch a block from the host we compare its hash to the previous hash for that block (if it was read before) and cause the read to fail if it changes. One necessary change is that the minadbd started by recovery in sideload mode no longer drops its root privileges (they're needed to mount the FUSE filesystem). We rely on SELinux enforcement to restrict the set of things that can be accessed. Change-Id: Ida7dbd3b04c1d4e27a2779d88c1da0c7c81fb114
278 lines
8.7 KiB
C++
278 lines
8.7 KiB
C++
/*
|
|
* 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 <ctype.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <limits.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/wait.h>
|
|
#include <unistd.h>
|
|
|
|
#include "common.h"
|
|
#include "install.h"
|
|
#include "mincrypt/rsa.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"
|
|
|
|
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;
|
|
|
|
// 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 <frac> <secs>
|
|
// fill up the next <frac> part of of the progress bar
|
|
// over <secs> seconds. If <secs> is zero, use
|
|
// set_progress commands to manually control the
|
|
// progress of this segment of the bar
|
|
//
|
|
// set_progress <frac>
|
|
// <frac> 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"> <filename>
|
|
// arrange to install the contents of <filename> in the
|
|
// given partition on reboot.
|
|
//
|
|
// (API v2: <filename> may start with "PACKAGE:" to
|
|
// indicate taking a file from the OTA package.)
|
|
//
|
|
// (API v3: this command no longer exists.)
|
|
//
|
|
// ui_print <string>
|
|
// display <string> 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) {
|
|
umask(022);
|
|
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");
|
|
}
|
|
fflush(stdout);
|
|
} else if (strcmp(command, "wipe_cache") == 0) {
|
|
*wipe_cache = 1;
|
|
} else if (strcmp(command, "clear_display") == 0) {
|
|
ui->SetBackground(RecoveryUI::NONE);
|
|
} else if (strcmp(command, "enable_reboot") == 0) {
|
|
// packages can explicitly request that they want the user
|
|
// to be able to reboot during installation (useful for
|
|
// debugging packages that don't exit).
|
|
ui->SetEnableReboot(true);
|
|
} 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;
|
|
}
|
|
|
|
static int
|
|
really_install_package(const char *path, int* wipe_cache, bool needs_mount)
|
|
{
|
|
ui->SetBackground(RecoveryUI::INSTALLING_UPDATE);
|
|
ui->Print("Finding update package...\n");
|
|
// Give verification half the progress bar...
|
|
ui->SetProgressType(RecoveryUI::DETERMINATE);
|
|
ui->ShowProgress(VERIFICATION_PROGRESS_FRACTION, VERIFICATION_PROGRESS_TIME);
|
|
LOGI("Update location: %s\n", path);
|
|
|
|
// Map the update package into memory.
|
|
ui->Print("Opening update package...\n");
|
|
|
|
if (path && needs_mount) {
|
|
if (path[0] == '@') {
|
|
ensure_path_mounted(path+1);
|
|
} else {
|
|
ensure_path_mounted(path);
|
|
}
|
|
}
|
|
|
|
MemMapping map;
|
|
if (sysMapFile(path, &map) != 0) {
|
|
LOGE("failed to map file\n");
|
|
return INSTALL_CORRUPT;
|
|
}
|
|
|
|
int numKeys;
|
|
Certificate* loadedKeys = load_keys(PUBLIC_KEYS_FILE, &numKeys);
|
|
if (loadedKeys == NULL) {
|
|
LOGE("Failed to load keys\n");
|
|
return INSTALL_CORRUPT;
|
|
}
|
|
LOGI("%d key(s) loaded from %s\n", numKeys, PUBLIC_KEYS_FILE);
|
|
|
|
ui->Print("Verifying update package...\n");
|
|
|
|
int err;
|
|
err = verify_file(map.addr, map.length, loadedKeys, numKeys);
|
|
free(loadedKeys);
|
|
LOGI("verify_file returned %d\n", err);
|
|
if (err != VERIFY_SUCCESS) {
|
|
LOGE("signature verification failed\n");
|
|
sysReleaseMap(&map);
|
|
return INSTALL_CORRUPT;
|
|
}
|
|
|
|
/* Try to open the package.
|
|
*/
|
|
ZipArchive zip;
|
|
err = mzOpenZipArchive(map.addr, map.length, &zip);
|
|
if (err != 0) {
|
|
LOGE("Can't open %s\n(%s)\n", path, err != -1 ? strerror(err) : "bad");
|
|
sysReleaseMap(&map);
|
|
return INSTALL_CORRUPT;
|
|
}
|
|
|
|
/* Verify and install the contents of the package.
|
|
*/
|
|
ui->Print("Installing update...\n");
|
|
ui->SetEnableReboot(false);
|
|
int result = try_update_binary(path, &zip, wipe_cache);
|
|
ui->SetEnableReboot(true);
|
|
ui->Print("\n");
|
|
|
|
sysReleaseMap(&map);
|
|
|
|
return result;
|
|
}
|
|
|
|
int
|
|
install_package(const char* path, int* wipe_cache, const char* install_file,
|
|
bool needs_mount)
|
|
{
|
|
FILE* install_log = fopen_path(install_file, "w");
|
|
if (install_log) {
|
|
fputs(path, install_log);
|
|
fputc('\n', install_log);
|
|
} else {
|
|
LOGE("failed to open last_install: %s\n", strerror(errno));
|
|
}
|
|
int result;
|
|
if (setup_install_mounts() != 0) {
|
|
LOGE("failed to set up expected mounts for install; aborting\n");
|
|
result = INSTALL_ERROR;
|
|
} else {
|
|
result = really_install_package(path, wipe_cache, needs_mount);
|
|
}
|
|
if (install_log) {
|
|
fputc(result == INSTALL_SUCCESS ? '1' : '0', install_log);
|
|
fputc('\n', install_log);
|
|
fclose(install_log);
|
|
}
|
|
return result;
|
|
}
|