随着android系统的更新,在android7.0中,recovery模式中已经不在挂载data分区,而且data分区可以设置加密,这样更促进了在recovery分区中不挂载data分区,加强了用户的安全性。但是这样就会有问题,当升级包较大时cache分区是放不下的,增大cache分区只会浪费资源,最好的办法还是把它放在data分区下。但是因为加密和不挂载的原因导致在recovery模式下是无法使用升级包的。而uncrypt机制就是解决这个问题的。它的基本原理就是,在正常模式下记录升级包所在的磁盘区块号,而且如果加密了就通过读进行解密,再绕过加密写到存储磁盘上。当记录好了磁盘区块号后,启动到recovery模式后就可以不挂载data分区,直接根据区块号读取升级包中的数据进行升级。下面记录代码的分析流程。
frameworks/base/core/java/android/os/RecoverySystem.java中
public static void processPackage(Context context,
File packageFile,
final ProgressListener listener,
final Handler handler)
throws IOException {
String filename = packageFile.getCanonicalPath();
if (!filename.startsWith("/data/")) {
return;
}
RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE);
IRecoverySystemProgressListener progressListener = null;
if (listener != null) {
final Handler progressHandler;
if (handler != null) {
progressHandler = handler;
} else {
progressHandler = new Handler(context.getMainLooper());
}
progressListener = new IRecoverySystemProgressListener.Stub() {
int lastProgress = 0;
long lastPublishTime = System.currentTimeMillis();
@Override
public void onProgress(final int progress) {
final long now = System.currentTimeMillis();
progressHandler.post(new Runnable() {
@Override
public void run() {
if (progress > lastProgress &&
now - lastPublishTime > PUBLISH_PROGRESS_INTERVAL_MS) {
lastProgress = progress;
lastPublishTime = now;
listener.onProgress(progress);
}
}
});
}
};
}
if (!rs.uncrypt(filename, progressListener)) {
throw new IOException("process package failed");
}
}
首先判断升级包是否是在data分区下,如果不是则不需要进行uncrypt进行处理,否则使用uncrypt进行处理。最后调用rs.uncrypt(filename,progressListener)。
bootable/recovery/uncrypt/uncrypt.cpp中
static constexpr int WINDOW_SIZE = 5; static constexpr int FIBMAP_RETRY_LIMIT = 3; static const std::string CACHE_BLOCK_MAP = "/cache/recovery/block.map"; static const std::string UNCRYPT_PATH_FILE = "/cache/recovery/uncrypt_file"; static const std::string UNCRYPT_STATUS = "/cache/recovery/uncrypt_status"; static const std::string UNCRYPT_SOCKET = "uncrypt"; static bool uncrypt_wrapper(const char* input_path, const char* map_file, const int socket) { // Initialize the uncrypt error to kUncryptErrorPlaceholder. log_uncrypt_error_code(kUncryptErrorPlaceholder); std::string package; if (input_path == nullptr) { if (!find_uncrypt_package(UNCRYPT_PATH_FILE, &package)) { write_status_to_socket(-1, socket); // Overwrite the error message. log_uncrypt_error_code(kUncryptPackageMissingError); return false; } input_path = package.c_str(); } CHECK(map_file != nullptr); auto start = std::chrono::system_clock::now(); int status = uncrypt(input_path, map_file, socket); std::chrono::duration<double> duration = std::chrono::system_clock::now() - start; int count = static_cast<int>(duration.count()); std::string uncrypt_message = android::base::StringPrintf("uncrypt_time: %d\n", count); if (status != 0) { // Log the time cost and error code if uncrypt fails. uncrypt_message += android::base::StringPrintf("uncrypt_error: %d\n", status); if (!android::base::WriteStringToFile(uncrypt_message, UNCRYPT_STATUS)) { PLOG(WARNING) << "failed to write to " << UNCRYPT_STATUS; } write_status_to_socket(-1, socket); return false; } if (!android::base::WriteStringToFile(uncrypt_message, UNCRYPT_STATUS)) { PLOG(WARNING) << "failed to write to " << UNCRYPT_STATUS; } write_status_to_socket(100, socket); return true; }
通过调用该方法进行uncrypt的处理。
1.判断升级包路径是否为NULL,如果为NULL则从UNCRYPT_PATH_FILE中读取升级包的路径名,并且检查map文件路径
static bool find_uncrypt_package(const std::string& uncrypt_path_file, std::string* package_name) {
CHECK(package_name != nullptr);
std::string uncrypt_path;
if (!android::base::ReadFileToString(uncrypt_path_file, &uncrypt_path)) {
PLOG(ERROR) << "failed to open \"" << uncrypt_path_file << "\"";
return false;
}
// Remove the trailing '\n' if present.
*package_name = android::base::Trim(uncrypt_path);
return true;
}
2.调用uncrypt(input_path, map_file, socket),并且记录该方法的执行时间。
3.将更新时间和升级后的状态记录到UNCRYPT_STATUS所指向的文件
接着继续分析uncrypt函数
static int uncrypt(const char* input_path, const char* map_file, const int socket) {
LOG(INFO) << "update package is \"" << input_path << "\"";
// Turn the name of the file we're supposed to convert into an
// absolute path, so we can find what filesystem it's on.
char path[PATH_MAX+1];
if (realpath(input_path, path) == NULL) {
PLOG(ERROR) << "failed to convert \"" << input_path << "\" to absolute path";
return 1;
}
bool encryptable;
bool encrypted;
const char* blk_dev = find_block_device(path, &encryptable, &encrypted);
if (blk_dev == NULL) {
LOG(ERROR) << "failed to find block device for " << path;
return 1;
}
// If the filesystem it's on isn't encrypted, we only produce the
// block map, we don't rewrite the file contents (it would be
// pointless to do so).
LOG(INFO) << "encryptable: " << (encryptable ? "yes" : "no");
LOG(INFO) << " encrypted: " << (encrypted ? "yes" : "no");
// Recovery supports installing packages from 3 paths: /cache,
// /data, and /sdcard. (On a particular device, other locations
// may work, but those are three we actually expect.)
//
// On /data we want to convert the file to a block map so that we
// can read the package without mounting the partition. On /cache
// and /sdcard we leave the file alone.
if (strncmp(path, "/data/", 6) == 0) {
LOG(INFO) << "writing block map " << map_file;
return produce_block_map(path, map_file, blk_dev, encrypted, socket);
}
return 0;
}
1.将升级包的路径转换为绝对路径
2.寻找文件所在的分区(块设备)同时记录该分区是否进行了加密处理
static const char* find_block_device(const char* path, bool* encryptable, bool* encrypted) { // Look for a volume whose mount point is the prefix of path and // return its block device. Set encrypted if it's currently // encrypted. for (int i = 0; i < fstab->num_entries; ++i) { struct fstab_rec* v = &fstab->recs[i]; if (!v->mount_point) { continue; } int len = strlen(v->mount_point); if (strncmp(path, v->mount_point, len) == 0 && (path[len] == '/' || path[len] == 0)) { *encrypted = false; *encryptable = false; if (fs_mgr_is_encryptable(v) || fs_mgr_is_file_encrypted(v)) { *encryptable = true; if (android::base::GetProperty("ro.crypto.state", "") == "encrypted") { *encrypted = true; } } return v->blk_device; } } return NULL; }
3.判断升级包是否在data分区下,如果是在data分区下则调用produce_block_map方法,生成升级包对应的block文件
继续分析produce_block_map方法
static int produce_block_map(const char* path, const char* map_file, const char* blk_dev, bool encrypted, int socket) { std::string err; if (!android::base::RemoveFileIfExists(map_file, &err)) { LOG(ERROR) << "failed to remove the existing map file " << map_file << ": " << err; return kUncryptFileRemoveError; } std::string tmp_map_file = std::string(map_file) + ".tmp"; android::base::unique_fd mapfd(open(tmp_map_file.c_str(), O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR)); if (mapfd == -1) { PLOG(ERROR) << "failed to open " << tmp_map_file; return kUncryptFileOpenError; } // Make sure we can write to the socket. if (!write_status_to_socket(0, socket)) { LOG(ERROR) << "failed to write to socket " << socket; return kUncryptSocketWriteError; } struct stat sb; if (stat(path, &sb) != 0) { LOG(ERROR) << "failed to stat " << path; return kUncryptFileStatError; } LOG(INFO) << " block size: " << sb.st_blksize << " bytes"; int blocks = ((sb.st_size-1) / sb.st_blksize) + 1; LOG(INFO) << " file size: " << sb.st_size << " bytes, " << blocks << " blocks"; std::vector<int> ranges; std::string s = android::base::StringPrintf("%s\n%" PRId64 " %" PRId64 "\n", blk_dev, static_cast<int64_t>(sb.st_size), static_cast<int64_t>(sb.st_blksize)); if (!android::base::WriteStringToFd(s, mapfd)) { PLOG(ERROR) << "failed to write " << tmp_map_file; return kUncryptWriteError; } std::vector<std::vector<unsigned char>> buffers; if (encrypted) { buffers.resize(WINDOW_SIZE, std::vector<unsigned char>(sb.st_blksize)); } int head_block = 0; int head = 0, tail = 0; android::base::unique_fd fd(open(path, O_RDONLY)); if (fd == -1) { PLOG(ERROR) << "failed to open " << path << " for reading"; return kUncryptFileOpenError; } android::base::unique_fd wfd; if (encrypted) { wfd.reset(open(blk_dev, O_WRONLY)); if (wfd == -1) { PLOG(ERROR) << "failed to open " << blk_dev << " for writing"; return kUncryptBlockOpenError; } } off64_t pos = 0; int last_progress = 0; while (pos < sb.st_size) { // Update the status file, progress must be between [0, 99]. int progress = static_cast<int>(100 * (double(pos) / double(sb.st_size))); if (progress > last_progress) { last_progress = progress; write_status_to_socket(progress, socket); } if ((tail+1) % WINDOW_SIZE == head) { // write out head buffer int block = head_block; if (ioctl(fd, FIBMAP, &block) != 0) { PLOG(ERROR) << "failed to find block " << head_block; return kUncryptIoctlError; } if (block == 0) { LOG(ERROR) << "failed to find block " << head_block << ", retrying"; int error = retry_fibmap(fd, path, &block, head_block); if (error != kUncryptNoError) { return error; } } printf("head==%d,tail==%d,head_block==%d,pos=%ld\n",head,tail,head_block,pos); printf("uncrypt:block=%d\n",block); add_block_to_ranges(ranges, block); if (encrypted) { if (write_at_offset(buffers[head].data(), sb.st_blksize, wfd, static_cast<off64_t>(sb.st_blksize) * block) != 0) { return kUncryptWriteError; } } head = (head + 1) % WINDOW_SIZE; ++head_block; } // read next block to tail if (encrypted) { size_t to_read = static_cast<size_t>( std::min(static_cast<off64_t>(sb.st_blksize), sb.st_size - pos)); if (!android::base::ReadFully(fd, buffers[tail].data(), to_read)) { PLOG(ERROR) << "failed to read " << path; return kUncryptReadError; } pos += to_read; } else { // If we're not encrypting; we don't need to actually read // anything, just skip pos forward as if we'd read a // block. pos += sb.st_blksize; } tail = (tail+1) % WINDOW_SIZE; } while (head != tail) { // write out head buffer int block = head_block; if (ioctl(fd, FIBMAP, &block) != 0) { PLOG(ERROR) << "failed to find block " << head_block; return kUncryptIoctlError; } if (block == 0) { LOG(ERROR) << "failed to find block " << head_block << ", retrying"; int error = retry_fibmap(fd, path, &block, head_block); if (error != kUncryptNoError) { return error; } } add_block_to_ranges(ranges, block); if (encrypted) { if (write_at_offset(buffers[head].data(), sb.st_blksize, wfd, static_cast<off64_t>(sb.st_blksize) * block) != 0) { return kUncryptWriteError; } } head = (head + 1) % WINDOW_SIZE; ++head_block; } if (!android::base::WriteStringToFd( android::base::StringPrintf("%zu\n", ranges.size() / 2), mapfd)) { PLOG(ERROR) << "failed to write " << tmp_map_file; return kUncryptWriteError; } for (size_t i = 0; i < ranges.size(); i += 2) { if (!android::base::WriteStringToFd( android::base::StringPrintf("%d %d\n", ranges[i], ranges[i+1]), mapfd)) { PLOG(ERROR) << "failed to write " << tmp_map_file; return kUncryptWriteError; } } if (fsync(mapfd) == -1) { PLOG(ERROR) << "failed to fsync \"" << tmp_map_file << "\""; return kUncryptFileSyncError; } if (close(mapfd.release()) == -1) { PLOG(ERROR) << "failed to close " << tmp_map_file; return kUncryptFileCloseError; } if (encrypted) { if (fsync(wfd) == -1) { PLOG(ERROR) << "failed to fsync \"" << blk_dev << "\""; return kUncryptFileSyncError; } if (close(wfd.release()) == -1) { PLOG(ERROR) << "failed to close " << blk_dev; return kUncryptFileCloseError; } } if (rename(tmp_map_file.c_str(), map_file) == -1) { PLOG(ERROR) << "failed to rename " << tmp_map_file << " to " << map_file; return kUncryptFileRenameError; } // Sync dir to make rename() result written to disk. std::string file_name = map_file; std::string dir_name = dirname(&file_name[0]); android::base::unique_fd dfd(open(dir_name.c_str(), O_RDONLY | O_DIRECTORY)); if (dfd == -1) { PLOG(ERROR) << "failed to open dir " << dir_name; return kUncryptFileOpenError; } if (fsync(dfd) == -1) { PLOG(ERROR) << "failed to fsync " << dir_name; return kUncryptFileSyncError; } if (close(dfd.release()) == -1) { PLOG(ERROR) << "failed to close " << dir_name; return kUncryptFileCloseError; } return 0; }
该方法是生成map文件的主要方法,也是uncrypt的核心方法
1.如果map_file已经存在则进行删除
2.创建并且打卡map_file的临时文件
3.确保socket是可写的,定义ranges保存块的区间,buffers用来保存数据,进行解密。
4.获取升级文件的详细信息,包括块的大小,文件的大小,计算出块数。
5.将块设备路径,文件的大小,块的大小写入到map_file.tmp文件中
6.以只读的方式打开升级包,以写的方式打开块设备
7.在第一个while循环中,首先输出进度,然后(如果进行了加密)填充窗口大小的数据(WINDOW_SIZE)指定。填充好后,获取物理块号,保存块号,如果加密了就进行解密。据逻辑块号获取物理块号,核心方法是ioctl(fd, FIBMAP, &block) ,block传入的时候是逻辑块号,方法调用成功后将物理块号回写入到block中。例如你传入的是0获取的就是第0块的物理块号,传入的是1则获取的就是第一块的物理块号。方法调用成功返回0,如果block获取到的值为0说明没有获取到物理块号,head_block获取已经得到的块数。注意使用了数据窗口机制获取数据,并且add_block_to_ranges也很巧妙的记录了物理块号的范围。
add_block_to_ranges
static void add_block_to_ranges(std::vector<int>& ranges, int new_block) { if (!ranges.empty() && new_block == ranges.back()) { // If the new block comes immediately after the current range, // all we have to do is extend the current range. ++ranges.back(); } else { // We need to start a new range. ranges.push_back(new_block); ranges.push_back(new_block + 1); } }
这个函数利用了磁盘保存数据时往往都是在连续的块中存储的特性。
8.跳出第一个循环,执行到第二个循环,将最后范围内的物理块好进行记录,同时如果进行了加密就解密。
9.在tmp_map_file中记录range数,range的范围。重命名tmp_map_file等做一些资源关闭释放的工作
总结:uncrypt思路不复杂,关键是要知道可以绕过文件系统直接对块进行I/O操作。