博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

recovery升级--uncrypt分析

Posted on 2019-07-19 14:22  不上班行不行  阅读(1499)  评论(0编辑  收藏  举报

随着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操作。