├── .gitignore ├── Android.bp ├── LICENSE ├── README.md ├── hashtreepatcher.hpp ├── main.cpp └── version.hpp /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | CMakeLists.txt 3 | cmake-build-debug/ -------------------------------------------------------------------------------- /Android.bp: -------------------------------------------------------------------------------- 1 | cc_binary { 2 | name: "httools", 3 | srcs: [ 4 | "main.cpp", 5 | ], 6 | shared_libs: [ 7 | "libcrypto", 8 | "libfec", 9 | "libfs_mgr", 10 | "libjsoncpp", 11 | "libbase", 12 | "libcutils", 13 | "liblog", 14 | ], 15 | static_libs: [ 16 | "libavb", 17 | "libfs_avb", 18 | "libfstab", 19 | ], 20 | cflags: [ 21 | "-Wall", 22 | "-Werror", 23 | "-O3", 24 | ], 25 | } 26 | 27 | cc_binary { 28 | name: "httools_static", 29 | device_supported: true, 30 | static_executable: true, 31 | srcs: [ 32 | "main.cpp", 33 | ], 34 | static_libs: [ 35 | "libcrypto_static", 36 | "libfec", 37 | "libavb", 38 | "libfs_avb", 39 | "libfstab", 40 | "libfs_mgr", 41 | "libjsoncpp", 42 | "libbase", 43 | "libcutils", 44 | "liblog", 45 | ], 46 | cflags: [ 47 | "-Wall", 48 | "-Werror", 49 | "-O3", 50 | ], 51 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2022 capntrips 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hashtree Patcher 2 | 3 | Hashtree Patcher is an Android command-line tool that patches `vendor_dlkm.img` and `vbmeta.img` for booting without disabling verity. 4 | It also has tools for checking if verity and verification are disabled in the top-level `vbmeta` and if a given partition has an `avb` fs option. 5 | 6 | ## Usage 7 | 8 | ```bash 9 | httools patch 10 | ``` 11 | 12 | A hashtree footer is appended to the partition image, and the specified hashtree descriptor in the `vbmeta` image is patched 13 | with the relevant values. Both images are patched in place. 14 | 15 | ### FEC 16 | 17 | If the `fec` binary is present in the working directory, it will be used to generate FEC data. Prebuilt binaries are available 18 | [here](https://github.com/capntrips/vendor_fec/releases/tag/v12.0.0_r12). 19 | 20 | ```bash 21 | httools avb 22 | ``` 23 | 24 | Checks if the partition has an `avb` fs option, the value of which is printed, if so. 25 | 26 | ```bash 27 | httools disable-flags 28 | ``` 29 | 30 | Checks if verity or verification are disabled in the top-level `vbmeta`. 31 | 32 | ```bash 33 | httools mount 34 | ``` 35 | 36 | Mounts the partition, with a hashtree if verity and verification are enabled. 37 | 38 | ```bash 39 | httools umount 40 | ``` 41 | 42 | Unmounts the partition and tears down its hashtree if verity and verification are enabled. 43 | 44 | ```bash 45 | httools --version 46 | ``` 47 | 48 | Prints the current version and exits. -------------------------------------------------------------------------------- /hashtreepatcher.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | using android::fs_mgr::FstabEntry; 6 | 7 | bool are_flags_disabled(); 8 | FstabEntry find_fstab_entry(char* partition_name); 9 | static bool IsMountPointMounted(const std::string& mount_point); 10 | 11 | // https://android.googlesource.com/platform/external/avb/+/refs/tags/android-12.0.0_r12/test/avb_unittest_util.h#35 12 | // Encodes |len| bytes of |data| as a lower-case hex-string. 13 | std::string mem_to_hexstring(const uint8_t* data, size_t len); 14 | 15 | // https://android.googlesource.com/platform/system/core/+/refs/tags/android-12.0.0_r12/fs_mgr/libfs_avb/util.h#55 16 | bool NibbleValue(const char& c, uint8_t* value); 17 | 18 | // https://android.googlesource.com/platform/system/core/+/refs/tags/android-12.0.0_r12/fs_mgr/libfs_avb/util.h#57 19 | bool HexToBytes(uint8_t* bytes, size_t bytes_len, const std::string& hex); 20 | 21 | // https://android.googlesource.com/platform/external/avb/+/refs/tags/android-12.0.0_r12/libavb/avb_descriptor.h#67 22 | /* Copies |src| to |dest|, byte-unswapping fields in the 23 | * process if needed. 24 | * 25 | * Data following the struct is not copied. 26 | */ 27 | void avb_descriptor_byteunswap(const AvbDescriptor* src, AvbDescriptor* dest); 28 | 29 | // https://android.googlesource.com/platform/external/avb/+/refs/tags/android-12.0.0_r12/libavb/avb_hashtree_descriptor.h#85 30 | /* Copies |src| to |dest|, byte-unswapping fields in the 31 | * process if needed. 32 | * 33 | * Data following the struct is not copied. 34 | */ 35 | void avb_hashtree_descriptor_byteunswap(const AvbHashtreeDescriptor* src, AvbHashtreeDescriptor* dest); 36 | 37 | // https://stackoverflow.com/a/29389440/434343 38 | uint8_t bit_length(uint32_t x); 39 | 40 | // https://android.googlesource.com/platform/external/avb/+/refs/tags/android-12.0.0_r12/avbtool.py#220 41 | // Rounds a number up to the next power of 2. 42 | // If |number| is already a power of 2 then |number| is 43 | // returned. Otherwise the smallest power of 2 greater than |number| 44 | // is returned. 45 | uint8_t round_to_pow2(uint8_t number); 46 | 47 | // https://android.googlesource.com/platform/external/avb/+/refs/tags/android-12.0.0_r12/avbtool.py#203 48 | // Rounds a number up to nearest multiple of another number. 49 | // If |number| is a multiple of |size|, returns |number|, otherwise 50 | // returns |number| + |size|. 51 | uint32_t round_to_multiple(uint32_t number, uint16_t size); 52 | 53 | // https://android.googlesource.com/platform/external/avb/+/refs/tags/android-12.0.0_r12/avbtool.py#3956 54 | // Calculate the offsets of all the hash-levels in a Merkle-tree. 55 | // Returns an array of offsets and the size of the tree, in bytes. 56 | std::pair, uint32_t> calc_hash_level_offsets(uint32_t image_size, uint16_t block_size, uint8_t digest_size); 57 | 58 | // https://android.googlesource.com/platform/external/avb/+/refs/tags/android-12.0.0_r12/avbtool.py#4055 59 | // Generates a Merkle-tree for a file. 60 | // Returns the top-level hash and hash-tree as bytes. 61 | std::pair, std::vector> generate_hash_tree(uint8_t *image, uint32_t image_size, uint16_t block_size, uint8_t digest_size, const uint8_t *salt, uint8_t salt_size, uint16_t digest_padding, std::vector hash_level_offsets, uint32_t tree_size); -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include "hashtreepatcher.hpp" 25 | #include "version.hpp" 26 | 27 | using android::base::StartsWith; 28 | using android::base::unique_fd; 29 | using android::fs_mgr::AvbHandle; 30 | using android::fs_mgr::AvbHandleStatus; 31 | using android::fs_mgr::AvbHashtreeResult; 32 | using android::fs_mgr::AvbUniquePtr; 33 | using android::fs_mgr::Fstab; 34 | using android::fs_mgr::FstabEntry; 35 | using android::fs_mgr::HashAlgorithm; 36 | 37 | int main(int argc, char **argv) { 38 | char *command_name = argv[0]; 39 | 40 | if (argc <= 1) { 41 | fprintf(stderr, "%s [-v|--version] [exists|dump|avb|disable-flags|patch|mount|umount]\n", command_name); 42 | exit(EXIT_SUCCESS); 43 | } else if (argc == 2 && (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-v") == 0)) { 44 | fprintf(stderr, "%s %s\n", command_name, version); 45 | exit(EXIT_SUCCESS); 46 | } 47 | 48 | if (strcmp(argv[1], "exists") == 0) { 49 | if (argc != 3) { 50 | fprintf(stderr, "%s avb \n", command_name); 51 | exit(EXIT_FAILURE); 52 | } 53 | auto partition_name = argv[2]; 54 | find_fstab_entry(partition_name); 55 | exit(EXIT_SUCCESS); 56 | } else if (strcmp(argv[1], "dump") == 0) { 57 | if (argc != 3) { 58 | fprintf(stderr, "%s avb \n", command_name); 59 | exit(EXIT_FAILURE); 60 | } 61 | auto partition_name = argv[2]; 62 | auto fstab_entry = find_fstab_entry(partition_name); 63 | 64 | // https://cs.android.com/android/platform/superproject/+/android-12.1.0_r8:system/core/fs_mgr/fs_mgr.cpp;l=1432-1437 65 | if (fstab_entry.fs_mgr_flags.logical) { 66 | fs_mgr_update_logical_partition(&fstab_entry); 67 | } 68 | 69 | Json::Value root; 70 | Json::Value fsMgrFlags; 71 | 72 | // https://cs.android.com/android/platform/superproject/+/android-12.1.0_r8:system/core/fs_mgr/fs_mgr_fstab.cpp;l=469-485 73 | root["blkDevice"] = fstab_entry.blk_device; 74 | root["mountPoint"] = fstab_entry.mount_point; 75 | root["fsType"] = fstab_entry.fs_type; 76 | 77 | // https://cs.android.com/android/platform/superproject/+/android-12.1.0_r8:system/core/fs_mgr/fs_mgr_fstab.cpp;l=176 78 | // https://cs.android.com/android/platform/superproject/+/android-12.1.0_r8:system/core/fs_mgr/fs_mgr_fstab.cpp;l=504-506 79 | if (fstab_entry.fs_mgr_flags.logical) { 80 | fsMgrFlags["logical"] = true; 81 | root["logicalPartitionName"] = fstab_entry.logical_partition_name; 82 | } 83 | 84 | // https://cs.android.com/android/platform/superproject/+/android-12.1.0_r8:system/core/fs_mgr/fs_mgr_fstab.cpp;l=175 85 | // https://cs.android.com/android/platform/superproject/+/android-12.1.0_r8:system/core/fs_mgr/fs_mgr_fstab.cpp;l=284-288 86 | if (fstab_entry.fs_mgr_flags.avb) { 87 | root["avb"] = true; 88 | root["vbmetaPartition"] = fstab_entry.vbmeta_partition; 89 | } else if (!fstab_entry.avb_keys.empty()) { 90 | root["avbKeys"] = fstab_entry.avb_keys; 91 | } 92 | 93 | root["fsMgrFlags"] = fsMgrFlags; 94 | 95 | Json::StreamWriterBuilder wbuilder; 96 | wbuilder["indentation"] = ""; 97 | std::string document = Json::writeString(wbuilder, root); 98 | printf("%s\n", document.c_str()); 99 | exit(EXIT_SUCCESS); 100 | } else if (strcmp(argv[1], "avb") == 0) { 101 | if (argc != 3) { 102 | fprintf(stderr, "%s avb \n", command_name); 103 | exit(EXIT_FAILURE); 104 | } 105 | auto partition_name = argv[2]; 106 | auto fstab_entry = find_fstab_entry(partition_name); 107 | 108 | // https://android.googlesource.com/platform/system/core/+/refs/tags/android-12.0.0_r12/init/first_stage_mount.cpp#800 109 | // https://android.googlesource.com/platform/system/core/+/refs/tags/android-12.0.0_r12/fs_mgr/fs_mgr_fstab.cpp#286 110 | if (fstab_entry.fs_mgr_flags.avb) { 111 | printf("%s\n", fstab_entry.vbmeta_partition.c_str()); 112 | } 113 | exit(EXIT_SUCCESS); 114 | } else if (strcmp(argv[1], "disable-flags") == 0) { 115 | auto is_disabled = are_flags_disabled(); 116 | if (is_disabled) { 117 | printf("disabled\n"); 118 | } else { 119 | printf("enabled\n"); 120 | } 121 | exit(EXIT_SUCCESS); 122 | } else if (strcmp(argv[1], "patch") == 0) { 123 | if (argc != 5) { 124 | fprintf(stderr, "%s patch \n", command_name); 125 | exit(EXIT_FAILURE); 126 | } 127 | 128 | int fd_partition; 129 | int fd_vbmeta; 130 | int fd_fec; 131 | struct stat stat_partition; // NOLINT(cppcoreguidelines-pro-type-member-init) 132 | struct stat stat_vbmeta; // NOLINT(cppcoreguidelines-pro-type-member-init) 133 | struct stat stat_fec; // NOLINT(cppcoreguidelines-pro-type-member-init) 134 | void *addr_partition; 135 | void *addr_vbmeta; 136 | void *addr_fec; 137 | uint8_t *buf_partition; 138 | uint8_t *buf_vbmeta; 139 | uint8_t *buf_fec; 140 | uint64_t size_vbmeta; 141 | 142 | auto partition_name = argv[2]; 143 | auto partition_image = argv[3]; 144 | auto vbmeta_image = argv[4]; 145 | 146 | // https://man7.org/linux/man-pages/man2/mmap.2.html#EXAMPLES 147 | fd_partition = open(partition_image, O_RDWR | O_CLOEXEC); 148 | if (fd_partition == -1) { 149 | fprintf(stderr, "! Unable to open %s\n", partition_image); 150 | exit(EXIT_FAILURE); 151 | } 152 | 153 | if (fstat(fd_partition, &stat_partition) == -1) { 154 | fprintf(stderr, "! Unable to fstat %s\n", partition_image); 155 | exit(EXIT_FAILURE); 156 | } 157 | 158 | addr_partition = mmap(nullptr, stat_partition.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_partition, 0); 159 | if (addr_partition == MAP_FAILED) { 160 | fprintf(stderr, "! Unable to mmap %s\n", partition_image); 161 | exit(EXIT_FAILURE); 162 | } 163 | 164 | fd_vbmeta = open(vbmeta_image, O_RDWR | O_CLOEXEC); 165 | if (fd_vbmeta == -1) { 166 | fprintf(stderr, "! Unable to open %s\n", vbmeta_image); 167 | exit(EXIT_FAILURE); 168 | } 169 | 170 | if (fstat(fd_vbmeta, &stat_vbmeta) == -1) { 171 | fprintf(stderr, "! Unable to fstat %s\n", vbmeta_image); 172 | exit(EXIT_FAILURE); 173 | } 174 | 175 | // https://github.com/topjohnwu/Magisk/blob/6ef86d8d20ceb4674c02e334a53aaee22c090ea6/native/jni/base/files.cpp#L511 176 | if (S_ISBLK(stat_vbmeta.st_mode)) { 177 | ioctl(fd_vbmeta, BLKGETSIZE64, &size_vbmeta); 178 | } else { 179 | size_vbmeta = stat_vbmeta.st_size; 180 | } 181 | 182 | addr_vbmeta = mmap(nullptr, size_vbmeta, PROT_READ | PROT_WRITE, MAP_SHARED, fd_vbmeta, 0); 183 | if (addr_vbmeta == MAP_FAILED) { 184 | fprintf(stderr, "! Unable to mmap %s\n", vbmeta_image); 185 | exit(EXIT_FAILURE); 186 | } 187 | 188 | buf_partition = static_cast(addr_partition); 189 | buf_vbmeta = static_cast(addr_vbmeta); 190 | 191 | const uint8_t* header_block = buf_vbmeta; 192 | AvbVBMetaImageHeader vbmeta_header; 193 | size_t vbmeta_length; 194 | AvbHashtreeDescriptor* partition_desc_orig; 195 | AvbHashtreeDescriptor partition_desc; 196 | const uint8_t* partition_salt; 197 | const uint8_t* partition_digest; 198 | uint8_t digest_size; 199 | uint8_t digest_padding; 200 | uint64_t image_size = stat_partition.st_size; 201 | uint64_t combined_size = stat_partition.st_size; 202 | uint64_t tree_offset = stat_partition.st_size; 203 | uint16_t block_size = 4096; 204 | std::vector hash_level_offsets; 205 | uint32_t tree_size; 206 | uint16_t tree_padding; 207 | uint32_t fec_offset = 0; 208 | std::vector root_digest; 209 | std::vector hash_tree; 210 | uint32_t fec_size = 0; 211 | uint16_t fec_padding = 0; 212 | uint32_t fec_num_roots = 0; 213 | 214 | // https://android.googlesource.com/platform/external/avb/+/refs/tags/android-12.0.0_r12/libavb/avb_vbmeta_image.c#63 215 | if (avb_safe_memcmp(header_block, AVB_MAGIC, AVB_MAGIC_LEN) != 0) { 216 | fprintf(stderr, "! Header magic is incorrect\n"); 217 | exit(EXIT_FAILURE); 218 | } 219 | avb_vbmeta_image_header_to_host_byte_order((AvbVBMetaImageHeader*)(header_block), &vbmeta_header); 220 | 221 | vbmeta_length = sizeof(AvbVBMetaImageHeader) + vbmeta_header.authentication_data_block_size + vbmeta_header.auxiliary_data_block_size; 222 | 223 | // https://android.googlesource.com/platform/external/avb/+/refs/tags/android-12.0.0_r12/libavb/avb_slot_verify.c#940 224 | size_t num_descriptors; 225 | size_t n; 226 | bool partition_found = false; 227 | const AvbDescriptor** descriptors = avb_descriptor_get_all(buf_vbmeta, vbmeta_length, &num_descriptors); 228 | for (n = 0; n < num_descriptors; n++) { 229 | // https://android.googlesource.com/platform/external/avb/+/refs/tags/android-12.0.0_r12/libavb/avb_hash_descriptor.c#34 230 | AvbDescriptor desc; 231 | if (!avb_descriptor_validate_and_byteswap(descriptors[n], &desc)) { 232 | fprintf(stderr, "! Descriptor is invalid\n"); 233 | exit(EXIT_FAILURE); 234 | } 235 | switch (desc.tag) { 236 | case AVB_DESCRIPTOR_TAG_HASHTREE: { 237 | // https://android.googlesource.com/platform/external/avb/+/refs/tags/android-12.0.0_r12/libavb/avb_slot_verify.c#1121 238 | AvbHashtreeDescriptor hashtree_desc; 239 | const uint8_t* desc_partition_name; 240 | if (!avb_hashtree_descriptor_validate_and_byteswap((AvbHashtreeDescriptor*)descriptors[n], &hashtree_desc)) { 241 | fprintf(stderr, "! Hashtree descriptor is invalid\n"); 242 | exit(EXIT_FAILURE); 243 | } 244 | 245 | desc_partition_name = (const uint8_t*)descriptors[n] + sizeof(AvbHashtreeDescriptor); 246 | 247 | if (hashtree_desc.partition_name_len == 11 && strncmp((const char*)desc_partition_name, partition_name, hashtree_desc.partition_name_len) == 0) { 248 | partition_desc_orig = (AvbHashtreeDescriptor*)descriptors[n]; 249 | partition_desc = hashtree_desc; 250 | partition_found = true; 251 | 252 | partition_salt = desc_partition_name + hashtree_desc.partition_name_len; 253 | partition_digest = partition_salt + hashtree_desc.salt_len; 254 | } 255 | } break; 256 | } 257 | if (partition_found) { 258 | break; 259 | } 260 | } 261 | if (!partition_found) { 262 | fprintf(stderr, "! partition descriptor missing\n"); 263 | exit(EXIT_FAILURE); 264 | } 265 | 266 | // https://android.googlesource.com/platform/external/avb/+/refs/tags/android-12.0.0_r12/avbtool.py#3595 267 | // https://android.googlesource.com/platform/external/avb/+/refs/tags/android-12.0.0_r12/libavb/avb_slot_verify.c#1167 268 | if (avb_strcmp((const char*)partition_desc.hash_algorithm, "sha1") == 0) { 269 | digest_size = AVB_SHA1_DIGEST_SIZE; 270 | } else if (avb_strcmp((const char*)partition_desc.hash_algorithm, "sha256") == 0) { 271 | digest_size = AVB_SHA256_DIGEST_SIZE; 272 | } else if (avb_strcmp((const char*)partition_desc.hash_algorithm, "sha512") == 0) { 273 | digest_size = AVB_SHA512_DIGEST_SIZE; 274 | } else { 275 | fprintf(stderr, "! Unsupported hash algorithm\n"); 276 | exit(EXIT_FAILURE); 277 | } 278 | 279 | digest_padding = round_to_pow2(digest_size) - digest_size; 280 | 281 | // https://android.googlesource.com/platform/external/avb/+/refs/tags/android-12.0.0_r12/avbtool.py#3630 282 | // https://android.googlesource.com/platform/external/avb/+/refs/tags/android-12.0.0_r12/avbtool.py#771 283 | if (image_size % block_size != 0) { 284 | fprintf(stderr, "! File size of %" PRIu64 " is not a multiple of the image block size %u\n", image_size, block_size); 285 | exit(EXIT_FAILURE); 286 | } 287 | 288 | // https://android.googlesource.com/platform/external/avb/+/refs/tags/android-12.0.0_r12/avbtool.py#3679 289 | auto calculated = calc_hash_level_offsets(image_size, block_size, digest_size + digest_padding); 290 | hash_level_offsets = calculated.first; 291 | tree_size = calculated.second; 292 | 293 | // https://android.googlesource.com/platform/external/avb/+/refs/tags/android-12.0.0_r12/avbtool.py#3691 294 | auto generated = generate_hash_tree(buf_partition, image_size, block_size, digest_size, partition_salt, partition_desc.salt_len, digest_padding, hash_level_offsets, tree_size); 295 | root_digest = generated.first; 296 | hash_tree = generated.second; 297 | 298 | // https://android.googlesource.com/platform/external/avb/+/refs/tags/android-12.0.0_r12/avbtool.py#3720 299 | tree_padding = round_to_multiple(tree_size, block_size) - tree_size; 300 | combined_size = tree_offset + tree_size + tree_padding; 301 | 302 | munmap(addr_partition, tree_offset); 303 | 304 | if (ftruncate64(fd_partition, combined_size) != 0) { // NOLINT(cppcoreguidelines-narrowing-conversions) 305 | fprintf(stderr, "! Unable to resize %s\n", partition_image); 306 | exit(EXIT_FAILURE); 307 | } 308 | 309 | addr_partition = mmap(nullptr, combined_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_partition, 0); 310 | if (addr_partition == MAP_FAILED) { 311 | fprintf(stderr, "! Unable to mmap %s\n", partition_image); 312 | exit(EXIT_FAILURE); 313 | } 314 | 315 | buf_partition = static_cast(addr_partition); 316 | memset(&buf_partition[tree_offset], 0, tree_size + tree_padding); 317 | memcpy(&buf_partition[tree_offset], hash_tree.data(), tree_size); 318 | 319 | bool try_fec = false; 320 | fd_fec = open("fec", O_RDONLY); 321 | if (fd_fec != -1) { 322 | if (fstat(fd_fec, &stat_fec) != -1) { 323 | if (stat_fec.st_mode & S_IXUSR) { 324 | try_fec = true; 325 | } 326 | } 327 | close(fd_fec); 328 | } 329 | if (try_fec) { 330 | fec_offset = combined_size; 331 | const char *fec_filename = "fec.bin"; 332 | 333 | // https://android.googlesource.com/platform/external/avb/+/refs/tags/android-12.0.0_r12/avbtool.py#4023 334 | char command[256]; 335 | sprintf(command, "./fec --encode --roots 2 \"%s\" %s > /dev/null 2>&1", partition_image, fec_filename); 336 | system(command); 337 | 338 | fd_fec = open(fec_filename, O_RDWR); 339 | if (fd_fec == -1) { 340 | fprintf(stderr, "! Unable to open %s\n", fec_filename); 341 | exit(EXIT_FAILURE); 342 | } 343 | 344 | if (fstat(fd_fec, &stat_fec) == -1) { 345 | fprintf(stderr, "! Unable to fstat %s\n", fec_filename); 346 | exit(EXIT_FAILURE); 347 | } 348 | 349 | addr_fec = mmap(nullptr, stat_fec.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_fec, 0); 350 | if (addr_fec == MAP_FAILED) { 351 | fprintf(stderr, "! Unable to mmap %s\n", fec_filename); 352 | exit(EXIT_FAILURE); 353 | } 354 | 355 | buf_fec = static_cast(addr_fec); 356 | 357 | auto *footer = reinterpret_cast(&buf_fec[stat_fec.st_size - sizeof(fec_header)]); 358 | if (footer->magic != FEC_MAGIC) { 359 | fprintf(stderr, "! Header magic is incorrect\n"); 360 | exit(EXIT_FAILURE); 361 | } 362 | 363 | fec_size = footer->fec_size; 364 | fec_num_roots = footer->roots; 365 | 366 | // https://android.googlesource.com/platform/external/avb/+/refs/tags/android-12.0.0_r12/avbtool.py#4023 367 | fec_padding = round_to_multiple(fec_size, block_size) - fec_size; 368 | combined_size = fec_offset + fec_size + fec_padding; 369 | 370 | munmap(addr_partition, fec_offset); 371 | 372 | if (ftruncate64(fd_partition, combined_size) != 0) { // NOLINT(cppcoreguidelines-narrowing-conversions) 373 | fprintf(stderr, "! Unable to resize %s\n", partition_image); 374 | exit(EXIT_FAILURE); 375 | } 376 | 377 | addr_partition = mmap(nullptr, combined_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_partition, 0); 378 | if (addr_partition == MAP_FAILED) { 379 | fprintf(stderr, "! Unable to mmap %s\n", partition_image); 380 | exit(EXIT_FAILURE); 381 | } 382 | 383 | buf_partition = static_cast(addr_partition); 384 | memset(&buf_partition[fec_offset], 0, fec_size + fec_padding); 385 | memcpy(&buf_partition[fec_offset], buf_fec, fec_size); 386 | 387 | munmap(addr_fec, stat_fec.st_size); 388 | close(fd_fec); 389 | unlink(fec_filename); 390 | } 391 | 392 | partition_desc.image_size = image_size; 393 | partition_desc.tree_offset = tree_offset; 394 | partition_desc.tree_size = tree_size + tree_padding; 395 | partition_desc.fec_num_roots = fec_num_roots; 396 | partition_desc.fec_offset = fec_offset; 397 | partition_desc.fec_size = fec_size + fec_padding; 398 | avb_hashtree_descriptor_byteunswap((const AvbHashtreeDescriptor*)&partition_desc, partition_desc_orig); 399 | avb_memcpy((void *)partition_digest, root_digest.data(), root_digest.size()); 400 | printf("- Patching complete\n"); 401 | 402 | printf("" 403 | " Hashtree descriptor:\n" 404 | " Image Size: %" PRIu64 " bytes\n" 405 | " Tree Offset: %" PRIu64 "\n" 406 | " Tree Size: %d bytes\n" 407 | " Data Block Size: %d bytes\n" 408 | " Hash Block Size: %d bytes\n" 409 | " FEC num roots: %d\n" 410 | " FEC offset: %" PRIu64 "\n" 411 | " FEC size: %" PRIu64 " bytes\n" 412 | " Hash Algorithm: %s\n" 413 | " Partition Name: %s\n" 414 | " Salt: %s\n" 415 | " Root Digest: %s\n" 416 | " Flags: %d\n", 417 | image_size, image_size, tree_size, block_size, block_size, partition_desc.fec_num_roots, partition_desc.fec_offset, partition_desc.fec_size, (const char *)partition_desc.hash_algorithm, 418 | partition_name, mem_to_hexstring(partition_salt, partition_desc.salt_len).c_str(), mem_to_hexstring(root_digest.data(), root_digest.size()).c_str(), partition_desc.flags); 419 | 420 | munmap(addr_partition, combined_size); 421 | close(fd_partition); 422 | 423 | munmap(addr_vbmeta, stat_vbmeta.st_size); 424 | close(fd_vbmeta); 425 | 426 | exit(EXIT_SUCCESS); 427 | } else if (strcmp(argv[1], "mount") == 0) { 428 | if (argc != 3) { 429 | fprintf(stderr, "%s mount \n", command_name); 430 | exit(EXIT_FAILURE); 431 | } 432 | auto partition_name = argv[2]; 433 | auto fstab_entry = find_fstab_entry(partition_name); 434 | 435 | // https://cs.android.com/android/platform/superproject/+/android-12.1.0_r8:system/core/fs_mgr/fs_mgr.cpp;l=1391 436 | if (IsMountPointMounted(fstab_entry.mount_point)) { 437 | exit(EXIT_SUCCESS); 438 | } 439 | 440 | // https://cs.android.com/android/platform/superproject/+/android-12.1.0_r8:system/core/fs_mgr/fs_mgr.cpp;l=1432 441 | if (fstab_entry.fs_mgr_flags.logical) { 442 | if (!fs_mgr_update_logical_partition(&fstab_entry)) { 443 | fprintf(stderr, "! Could not set up logical partition\n"); 444 | exit(EXIT_FAILURE); 445 | } 446 | } 447 | 448 | if (!are_flags_disabled()) { 449 | // https://cs.android.com/android/platform/superproject/+/android-12.1.0_r8:system/core/fs_mgr/fs_mgr.cpp;l=1450 450 | if (fstab_entry.fs_mgr_flags.avb) { 451 | // https://cs.android.com/android/platform/superproject/+/android-12.1.0_r8:system/core/fs_mgr/libfs_avb/fs_avb.cpp;l=377 452 | auto avb_handle = AvbHandle::LoadAndVerifyVbmeta("vbmeta", fs_mgr_get_slot_suffix(), fs_mgr_get_other_slot_suffix(), {}, HashAlgorithm::kSHA256, true, false, false, nullptr); 453 | if (avb_handle->SetUpAvbHashtree(&fstab_entry, true) == AvbHashtreeResult::kFail) { 454 | fprintf(stderr, "! Failed to set up AVB on partition: %s\n", fstab_entry.mount_point.c_str()); 455 | exit(EXIT_FAILURE); 456 | } 457 | } else if (!fstab_entry.avb_keys.empty()) { 458 | if (AvbHandle::SetUpStandaloneAvbHashtree(&fstab_entry) == AvbHashtreeResult::kFail) { 459 | fprintf(stderr, "! Failed to set up AVB on standalone partition: %s\n", fstab_entry.mount_point.c_str()); 460 | exit(EXIT_FAILURE); 461 | } 462 | } 463 | } 464 | 465 | if (fs_mgr_do_mount_one(fstab_entry) != 0) { 466 | fprintf(stderr, "! Failed to mount %s\n", fstab_entry.mount_point.c_str()); 467 | exit(EXIT_FAILURE); 468 | } 469 | 470 | exit(EXIT_SUCCESS); 471 | } else if (strcmp(argv[1], "umount") == 0) { 472 | if (argc != 3) { 473 | fprintf(stderr, "%s umount \n", command_name); 474 | exit(EXIT_FAILURE); 475 | } 476 | auto partition_name = argv[2]; 477 | auto fstab_entry = find_fstab_entry(partition_name); 478 | 479 | 480 | // https://cs.android.com/android/platform/superproject/+/android-12.1.0_r8:system/core/fs_mgr/fs_mgr.cpp;l=1650 481 | if (!IsMountPointMounted(fstab_entry.mount_point)) { 482 | exit(EXIT_SUCCESS); 483 | } 484 | 485 | if (umount(fstab_entry.mount_point.c_str()) == -1) { 486 | fprintf(stderr, "! Failed to umount %s\n", fstab_entry.mount_point.c_str()); 487 | exit(EXIT_FAILURE); 488 | } 489 | 490 | if (fstab_entry.fs_mgr_flags.logical) { 491 | if (!fs_mgr_update_logical_partition(&fstab_entry)) { 492 | fprintf(stderr, "! Could not get logical partition blk_device\n"); 493 | exit(EXIT_FAILURE); 494 | } 495 | } 496 | 497 | if (!are_flags_disabled()) { 498 | if (fstab_entry.fs_mgr_flags.avb || !fstab_entry.avb_keys.empty()) { 499 | if (!AvbHandle::TearDownAvbHashtree(&fstab_entry, true)) { 500 | fprintf(stderr, "! Failed to tear down AVB on mount point: %s\n", fstab_entry.mount_point.c_str()); 501 | exit(EXIT_FAILURE); 502 | } 503 | } 504 | } 505 | 506 | exit(EXIT_SUCCESS); 507 | } else { 508 | fprintf(stderr, "%s [-v|--version] [exists|dump|avb|disable-flags|patch|mount|umount]\n", command_name); 509 | exit(EXIT_FAILURE); 510 | } 511 | } 512 | 513 | bool are_flags_disabled() { 514 | if (getuid() == 0) { 515 | // https://android.googlesource.com/platform/system/core/+/refs/tags/android-12.0.0_r12/fs_mgr/libfs_avb/fs_avb.cpp#376 516 | auto avb_handle = AvbHandle::LoadAndVerifyVbmeta("vbmeta", fs_mgr_get_slot_suffix(), fs_mgr_get_other_slot_suffix(), {}, HashAlgorithm::kSHA256, true, false, false, nullptr); 517 | // https://android.googlesource.com/platform/system/core/+/refs/tags/android-12.0.0_r12/init/first_stage_mount.cpp#813 518 | if (!avb_handle) { 519 | fprintf(stderr, "! Unable to load top-level vbmeta\n"); 520 | exit(EXIT_FAILURE); 521 | } 522 | // https://android.googlesource.com/platform/system/core/+/refs/tags/android-12.0.0_r12/init/first_stage_mount.cpp#804 523 | if (avb_handle->status() == AvbHandleStatus::kHashtreeDisabled || avb_handle->status() == AvbHandleStatus::kVerificationDisabled) { 524 | return true; 525 | } else { 526 | return false; 527 | } 528 | } else { 529 | fprintf(stderr, "! Run as root\n"); 530 | exit(EXIT_FAILURE); 531 | } 532 | } 533 | 534 | FstabEntry find_fstab_entry(char* partition_name) { 535 | // https://android.googlesource.com/platform/system/core/+/refs/tags/android-12.0.0_r12/init/first_stage_mount.cpp#241 536 | // https://android.googlesource.com/platform/system/core/+/refs/tags/android-12.0.0_r12/fastboot/device/fastboot_device.cpp#82 537 | // https://android.googlesource.com/platform/system/core/+/refs/tags/android-12.0.0_r12/fs_mgr/include_fstab/fstab/fstab.h#96 538 | Fstab fstab; 539 | if (!ReadDefaultFstab(&fstab)) { 540 | fprintf(stderr, "! Unable to read default fstab\n"); 541 | exit(EXIT_FAILURE); 542 | } 543 | 544 | auto partition_name_slotted = std::string(partition_name) + fs_mgr_get_slot_suffix(); 545 | 546 | // https://android.googlesource.com/platform/system/core/+/refs/tags/android-12.0.0_r12/init/first_stage_mount.cpp#513 547 | auto it = std::find_if(fstab.begin(), fstab.end(), [partition_name_slotted](const auto& entry) { 548 | return basename(entry.blk_device.c_str()) == partition_name_slotted; 549 | }); 550 | 551 | if (it == fstab.end()) { 552 | fprintf(stderr, "! Unable to find %s in fstab\n", partition_name); 553 | exit(EXIT_FAILURE); 554 | } 555 | 556 | return *it; 557 | } 558 | 559 | // https://cs.android.com/android/platform/superproject/+/android-12.1.0_r8:system/core/fs_mgr/fs_mgr.cpp;l=1358 560 | static bool IsMountPointMounted(const std::string& mount_point) { 561 | Fstab fstab; 562 | if (!ReadFstabFromFile("/proc/mounts", &fstab)) { 563 | return false; 564 | } 565 | return GetEntryForMountPoint(&fstab, mount_point) != nullptr; 566 | } 567 | 568 | // https://android.googlesource.com/platform/external/avb/+/refs/tags/android-12.0.0_r12/test/avb_unittest_util.cc#29 569 | std::string mem_to_hexstring(const uint8_t* data, size_t len) { 570 | std::string ret; 571 | char digits[17] = "0123456789abcdef"; 572 | for (size_t n = 0; n < len; n++) { 573 | ret.push_back(digits[data[n] >> 4]); 574 | ret.push_back(digits[data[n] & 0x0f]); 575 | } 576 | return ret; 577 | } 578 | 579 | // https://android.googlesource.com/platform/system/core/+/refs/tags/android-12.0.0_r12/fs_mgr/libfs_avb/util.cpp#32 580 | bool NibbleValue(const char& c, uint8_t* value) { 581 | switch (c) { 582 | case '0' ... '9': 583 | *value = c - '0'; 584 | break; 585 | case 'a' ... 'f': 586 | *value = c - 'a' + 10; 587 | break; 588 | case 'A' ... 'F': 589 | *value = c - 'A' + 10; 590 | break; 591 | default: 592 | return false; 593 | } 594 | return true; 595 | } 596 | 597 | // https://android.googlesource.com/platform/system/core/+/refs/tags/android-12.0.0_r12/fs_mgr/libfs_avb/util.cpp#52 598 | bool HexToBytes(uint8_t* bytes, size_t bytes_len, const std::string& hex) { 599 | if (hex.size() % 2 != 0) { 600 | return false; 601 | } 602 | if (hex.size() / 2 > bytes_len) { 603 | return false; 604 | } 605 | for (size_t i = 0, j = 0, n = hex.size(); i < n; i += 2, ++j) { 606 | uint8_t high; 607 | if (!NibbleValue(hex[i], &high)) { 608 | return false; 609 | } 610 | uint8_t low; 611 | if (!NibbleValue(hex[i + 1], &low)) { 612 | return false; 613 | } 614 | bytes[j] = (high << 4) | low; 615 | } 616 | return true; 617 | } 618 | 619 | // https://android.googlesource.com/platform/external/avb/+/refs/tags/android-12.0.0_r12/libavb/avb_descriptor.c#29 620 | void avb_descriptor_byteunswap(const AvbDescriptor* src, AvbDescriptor* dest) { 621 | dest->tag = avb_htobe64(src->tag); 622 | dest->num_bytes_following = avb_htobe64(src->num_bytes_following); 623 | } 624 | 625 | // https://android.googlesource.com/platform/external/avb/+/refs/tags/android-12.0.0_r12/libavb/avb_hashtree_descriptor.c#28 626 | void avb_hashtree_descriptor_byteunswap(const AvbHashtreeDescriptor* src, AvbHashtreeDescriptor* dest) { 627 | avb_memcpy(dest, src, sizeof(AvbHashtreeDescriptor)); 628 | avb_descriptor_byteunswap((const AvbDescriptor*)src, (AvbDescriptor*)dest); 629 | dest->dm_verity_version = avb_htobe32(dest->dm_verity_version); 630 | dest->image_size = avb_htobe64(dest->image_size); 631 | dest->tree_offset = avb_htobe64(dest->tree_offset); 632 | dest->tree_size = avb_htobe64(dest->tree_size); 633 | dest->data_block_size = avb_htobe32(dest->data_block_size); 634 | dest->hash_block_size = avb_htobe32(dest->hash_block_size); 635 | dest->fec_num_roots = avb_htobe32(dest->fec_num_roots); 636 | dest->fec_offset = avb_htobe64(dest->fec_offset); 637 | dest->fec_size = avb_htobe64(dest->fec_size); 638 | dest->partition_name_len = avb_htobe32(dest->partition_name_len); 639 | dest->salt_len = avb_htobe32(dest->salt_len); 640 | dest->root_digest_len = avb_htobe32(dest->root_digest_len); 641 | dest->flags = avb_htobe32(dest->flags); 642 | } 643 | 644 | // https://stackoverflow.com/a/29389440/434343 645 | uint8_t bit_length(uint32_t x) { 646 | uint8_t i; 647 | for (i = 0; x != 0; ++i) { 648 | x >>= 1; 649 | } 650 | return i; 651 | } 652 | 653 | // https://android.googlesource.com/platform/external/avb/+/refs/tags/android-12.0.0_r12/avbtool.py#220 654 | uint8_t round_to_pow2(uint8_t number) { 655 | return (uint8_t)pow(2, bit_length(number - 1)); 656 | } 657 | 658 | // https://android.googlesource.com/platform/external/avb/+/refs/tags/android-12.0.0_r12/avbtool.py#203 659 | uint32_t round_to_multiple(uint32_t number, uint16_t size) { 660 | uint16_t remainder = number % size; 661 | if (remainder == 0) { 662 | return number; 663 | } 664 | return number + size - remainder; 665 | } 666 | 667 | // https://android.googlesource.com/platform/external/avb/+/refs/tags/android-12.0.0_r12/avbtool.py#3956 668 | std::pair, uint32_t> calc_hash_level_offsets(uint32_t image_size, uint16_t block_size, uint8_t digest_size) { 669 | std::vector level_offsets; 670 | std::vector level_sizes; 671 | uint32_t tree_size = 0; 672 | uint8_t num_levels = 0; 673 | uint32_t size = image_size; 674 | while (size > block_size) { 675 | uint32_t num_blocks = (size + block_size - 1) / block_size; 676 | uint32_t level_size = round_to_multiple(num_blocks * digest_size, block_size); 677 | level_sizes.push_back(level_size); 678 | tree_size += level_size; 679 | num_levels += 1; 680 | size = level_size; 681 | } 682 | for (uint8_t n = 0; n < num_levels; ++n) { 683 | uint32_t offset = 0; 684 | for (uint8_t m = n + 1; m < num_levels; ++m) { 685 | offset += level_sizes[m]; 686 | } 687 | level_offsets.push_back(offset); 688 | } 689 | return std::make_pair(level_offsets, tree_size); 690 | } 691 | 692 | // https://android.googlesource.com/platform/external/avb/+/refs/tags/android-12.0.0_r12/avbtool.py#4055 693 | std::pair, std::vector> generate_hash_tree(uint8_t *image, uint32_t image_size, uint16_t block_size, uint8_t digest_size, const uint8_t *salt, uint8_t salt_size, uint16_t digest_padding, std::vector hash_level_offsets, uint32_t tree_size) { 694 | SHA_CTX sha_ctx; 695 | SHA256_CTX sha256_ctx; 696 | SHA512_CTX sha512_ctx; 697 | std::vector root_digest(digest_size); 698 | std::vector hash_ret(tree_size); 699 | std::vector level_output; 700 | 701 | uint32_t hash_src_offset = 0; 702 | uint32_t hash_src_size = image_size; 703 | uint8_t level_num = 0; 704 | while (hash_src_size > block_size) { 705 | std::vector level_output_list; 706 | uint32_t remaining = hash_src_size; 707 | while (remaining > 0) { 708 | uint32_t level_output_offset = level_output_list.size(); 709 | level_output_list.resize(level_output_list.size() + digest_size + digest_padding, 0); 710 | // Only read from the file for the first level - for subsequent 711 | // levels, access the array we're building. 712 | std::vector data; 713 | if (level_num == 0) { 714 | uint32_t offset = hash_src_offset + hash_src_size - remaining; 715 | uint32_t size = std::min(remaining, (uint32_t)block_size); 716 | data.resize(size); 717 | memcpy(data.data(), &image[offset], size); 718 | } else { 719 | uint32_t offset = hash_level_offsets[level_num - 1] + hash_src_size - remaining; 720 | data.resize(block_size); 721 | memcpy(data.data(), &hash_ret.data()[offset], block_size); // NOLINT(readability-simplify-subscript-expr) 722 | } 723 | switch (digest_size) { 724 | case AVB_SHA1_DIGEST_SIZE: 725 | SHA1_Init(&sha_ctx); 726 | SHA1_Update(&sha_ctx, salt, salt_size); 727 | SHA1_Update(&sha_ctx, data.data(), data.size()); 728 | SHA1_Final(&level_output_list.data()[level_output_offset], &sha_ctx); // NOLINT(readability-simplify-subscript-expr) 729 | break; 730 | case AVB_SHA256_DIGEST_SIZE: 731 | SHA256_Init(&sha256_ctx); 732 | SHA256_Update(&sha256_ctx, salt, salt_size); 733 | SHA256_Update(&sha256_ctx, data.data(), data.size()); 734 | SHA256_Final(&level_output_list.data()[level_output_offset], &sha256_ctx); // NOLINT(readability-simplify-subscript-expr) 735 | break; 736 | case AVB_SHA512_DIGEST_SIZE: 737 | SHA512_Init(&sha512_ctx); 738 | SHA512_Update(&sha512_ctx, salt, salt_size); 739 | SHA512_Update(&sha512_ctx, data.data(), data.size()); 740 | SHA512_Final(&level_output_list.data()[level_output_offset], &sha512_ctx); // NOLINT(readability-simplify-subscript-expr) 741 | break; 742 | default: 743 | fprintf(stderr, "! Unknown digest type\n"); 744 | exit(EXIT_FAILURE); 745 | } 746 | remaining -= data.size(); 747 | } 748 | level_output.clear(); 749 | level_output.swap(level_output_list); 750 | uint16_t padding_needed = round_to_multiple(level_output.size(), block_size) - level_output.size(); 751 | if (padding_needed != 0) { 752 | level_output.resize(level_output.size() + padding_needed, 0); 753 | } 754 | // Copy level-output into resulting tree. 755 | uint32_t offset = hash_level_offsets[level_num]; 756 | memcpy(&hash_ret.data()[offset], level_output.data(), level_output.size()); // NOLINT(readability-simplify-subscript-expr) 757 | // Continue on to the next level. 758 | hash_src_size = level_output.size(); 759 | level_num += 1; 760 | } 761 | switch (digest_size) { 762 | case AVB_SHA1_DIGEST_SIZE: 763 | SHA1_Init(&sha_ctx); 764 | SHA1_Update(&sha_ctx, salt, salt_size); 765 | SHA1_Update(&sha_ctx, level_output.data(), level_output.size()); 766 | SHA1_Final(root_digest.data(), &sha_ctx); 767 | break; 768 | case AVB_SHA256_DIGEST_SIZE: 769 | SHA256_Init(&sha256_ctx); 770 | SHA256_Update(&sha256_ctx, salt, salt_size); 771 | SHA256_Update(&sha256_ctx, level_output.data(), level_output.size()); 772 | SHA256_Final(root_digest.data(), &sha256_ctx); 773 | break; 774 | case AVB_SHA512_DIGEST_SIZE: 775 | SHA512_Init(&sha512_ctx); 776 | SHA512_Update(&sha512_ctx, salt, salt_size); 777 | SHA512_Update(&sha512_ctx, level_output.data(), level_output.size()); 778 | SHA512_Final(root_digest.data(), &sha512_ctx); 779 | break; 780 | default: 781 | fprintf(stderr, "! Unknown digest type\n"); 782 | exit(EXIT_FAILURE); 783 | } 784 | return std::make_pair(root_digest, hash_ret); 785 | } -------------------------------------------------------------------------------- /version.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | const char *version = "v3.2.0"; --------------------------------------------------------------------------------