1 //
2 // Copyright (C) 2020 The Android Open Source Project
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 //      http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 
16 #include "misc_info.h"
17 
18 #include <algorithm>
19 #include <array>
20 #include <memory>
21 #include <set>
22 #include <string>
23 #include <string_view>
24 #include <unordered_set>
25 #include <vector>
26 
27 #include <android-base/logging.h>
28 #include <android-base/parseint.h>
29 #include <android-base/strings.h>
30 #include <fmt/format.h>
31 
32 #include "common/libs/fs/shared_buf.h"
33 #include "common/libs/fs/shared_fd.h"
34 #include "common/libs/utils/contains.h"
35 #include "common/libs/utils/result.h"
36 #include "host/libs/avb/avb.h"
37 #include "host/libs/config/known_paths.h"
38 
39 namespace cuttlefish {
40 namespace {
41 
42 constexpr char kAvbVbmetaAlgorithm[] = "avb_vbmeta_algorithm";
43 constexpr char kAvbVbmetaArgs[] = "avb_vbmeta_args";
44 constexpr char kAvbVbmetaKeyPath[] = "avb_vbmeta_key_path";
45 constexpr char kDynamicPartitions[] = "dynamic_partition_list";
46 constexpr char kGoogleDynamicPartitions[] = "google_dynamic_partitions";
47 constexpr char kRollbackIndexSuffix[] = "_rollback_index_location";
48 constexpr char kSuperBlockDevices[] = "super_block_devices";
49 constexpr char kSuperPartitionGroups[] = "super_partition_groups";
50 constexpr char kUseDynamicPartitions[] = "use_dynamic_partitions";
51 constexpr char kRsa2048Algorithm[] = "SHA256_RSA2048";
52 constexpr char kRsa4096Algorithm[] = "SHA256_RSA4096";
53 constexpr std::array kNonPartitionKeysToMerge = {
54     "ab_update", "default_system_dev_certificate"};
55 // based on build/make/tools/releasetools/common.py:AVB_PARTITIONS
56 constexpr std::array kVbmetaPartitions = {"boot",
57                                           "init_boot",
58                                           "odm",
59                                           "odm_dlkm",
60                                           "vbmeta_system",
61                                           "vbmeta_system_dlkm",
62                                           "vbmeta_vendor_dlkm",
63                                           "vendor",
64                                           "vendor_boot"};
65 
GetExpected(const MiscInfo & misc_info,const std::string & key)66 Result<std::string> GetExpected(const MiscInfo& misc_info,
67                                 const std::string& key) {
68   auto lookup = misc_info.find(key);
69   CF_EXPECTF(lookup != misc_info.end(),
70              "Unable to retrieve expected value from key: {}", key);
71   return lookup->second;
72 }
73 
MergePartitionLists(const std::string & vendor,const std::string & system,const std::set<std::string> & extracted_images)74 std::string MergePartitionLists(const std::string& vendor,
75                                 const std::string& system,
76                                 const std::set<std::string>& extracted_images) {
77   const std::string full_string = fmt::format("{} {}", vendor, system);
78   const std::vector<std::string> full_list =
79       android::base::Tokenize(full_string, " ");
80   // std::set removes duplicates and orders the elements, which we want
81   const std::set<std::string> full_set(full_list.begin(), full_list.end());
82   std::set<std::string> filtered_set;
83   std::set_intersection(full_set.cbegin(), full_set.cend(),
84                         extracted_images.cbegin(), extracted_images.cend(),
85                         std::inserter(filtered_set, filtered_set.begin()));
86   return android::base::Join(filtered_set, " ");
87 }
88 
GetPartitionList(const MiscInfo & vendor_info,const MiscInfo & system_info,const std::string & key,const std::set<std::string> & extracted_images)89 std::string GetPartitionList(const MiscInfo& vendor_info,
90                              const MiscInfo& system_info,
91                              const std::string& key,
92                              const std::set<std::string>& extracted_images) {
93   std::string vendor_list = GetExpected(vendor_info, key).value_or("");
94   std::string system_list = GetExpected(system_info, key).value_or("");
95   return MergePartitionLists(vendor_list, system_list, extracted_images);
96 }
97 
GeneratePartitionKeys(const std::string & name)98 std::vector<std::string> GeneratePartitionKeys(const std::string& name) {
99   std::vector<std::string> result;
100   result.emplace_back("avb_" + name);
101   result.emplace_back("avb_" + name + "_algorithm");
102   result.emplace_back("avb_" + name + "_key_path");
103   result.emplace_back("avb_" + name + kRollbackIndexSuffix);
104   result.emplace_back("avb_" + name + "_hashtree_enable");
105   result.emplace_back("avb_" + name + "_add_hashtree_footer_args");
106   result.emplace_back(name + "_disable_sparse");
107   result.emplace_back("building_" + name + "_image");
108   auto fs_type_key = name + "_fs_type";
109   if (name == "system") {
110     fs_type_key = "fs_type";
111   }
112   result.emplace_back(fs_type_key);
113   return result;
114 }
115 
ResolveRollbackIndexConflicts(const std::string & index_string,const std::unordered_set<int> used_indices)116 Result<int> ResolveRollbackIndexConflicts(
117     const std::string& index_string,
118     const std::unordered_set<int> used_indices) {
119   int index;
120   CF_EXPECTF(android::base::ParseInt(index_string, &index),
121              "Unable to parse value {} to string.  Maybe a wrong or bad value "
122              "read for the rollback index?",
123              index_string);
124   while (Contains(used_indices, index)) {
125     ++index;
126   }
127   return index;
128 }
129 
GetKeyPath(const std::string_view algorithm)130 Result<std::string> GetKeyPath(const std::string_view algorithm) {
131   if (algorithm == kRsa4096Algorithm) {
132     return TestKeyRsa4096();
133   } else if (algorithm == kRsa2048Algorithm) {
134     return TestKeyRsa2048();
135   } else {
136     return CF_ERR("Unexpected algorithm.  No key available.");
137   }
138 }
139 
GetPubKeyPath(const std::string_view algorithm)140 Result<std::string> GetPubKeyPath(const std::string_view algorithm) {
141   if (algorithm == kRsa4096Algorithm) {
142     return TestPubKeyRsa4096();
143   } else if (algorithm == kRsa2048Algorithm) {
144     return TestPubKeyRsa2048();
145   } else {
146     return CF_ERR("Unexpected algorithm.  No key available.");
147   }
148 }
149 
150 }  // namespace
151 
ParseMiscInfo(const std::string & misc_info_contents)152 Result<MiscInfo> ParseMiscInfo(const std::string& misc_info_contents) {
153   auto lines = android::base::Split(misc_info_contents, "\n");
154   MiscInfo misc_info;
155   for (auto& line : lines) {
156     line = android::base::Trim(line);
157     if (line.size() == 0) {
158       continue;
159     }
160     auto eq_pos = line.find('=');
161     if (eq_pos == std::string::npos) {
162       LOG(WARNING) << "Line in unknown format: \"" << line << "\"";
163       continue;
164     }
165     // Not using android::base::Split here to only capture the first =
166     const auto key = android::base::Trim(line.substr(0, eq_pos));
167     const auto value = android::base::Trim(line.substr(eq_pos + 1));
168     const bool duplicate = Contains(misc_info, key) && misc_info[key] != value;
169     CF_EXPECTF(!duplicate,
170                "Duplicate key with different value. key:\"{}\", previous "
171                "value:\"{}\", this value:\"{}\"",
172                key, misc_info[key], value);
173     misc_info[key] = value;
174   }
175   return misc_info;
176 }
177 
WriteMiscInfo(const MiscInfo & misc_info,const std::string & output_path)178 Result<void> WriteMiscInfo(const MiscInfo& misc_info,
179                            const std::string& output_path) {
180   std::stringstream file_content;
181   for (const auto& entry : misc_info) {
182     file_content << entry.first << "=" << entry.second << "\n";
183   }
184 
185   SharedFD output_file = SharedFD::Creat(output_path.c_str(), 0644);
186   CF_EXPECT(output_file->IsOpen(),
187             "Failed to open output misc file: " << output_file->StrError());
188 
189   CF_EXPECT(
190       WriteAll(output_file, file_content.str()) >= 0,
191       "Failed to write output misc file contents: " << output_file->StrError());
192   return {};
193 }
194 
195 // based on build/make/tools/releasetools/merge/merge_target_files.py
GetCombinedDynamicPartitions(const MiscInfo & vendor_info,const MiscInfo & system_info,const std::set<std::string> & extracted_images)196 Result<MiscInfo> GetCombinedDynamicPartitions(
197     const MiscInfo& vendor_info, const MiscInfo& system_info,
198     const std::set<std::string>& extracted_images) {
199   auto vendor_use_dp =
200       CF_EXPECT(GetExpected(vendor_info, kUseDynamicPartitions));
201   CF_EXPECTF(vendor_use_dp == "true", "Vendor build must have {}=true",
202              kUseDynamicPartitions);
203   auto system_use_dp =
204       CF_EXPECT(GetExpected(system_info, kUseDynamicPartitions));
205   CF_EXPECTF(system_use_dp == "true", "System build must have {}=true",
206              kUseDynamicPartitions);
207   MiscInfo result;
208   // copy where both files are equal
209   for (const auto& key_val : vendor_info) {
210     const auto value_result = GetExpected(system_info, key_val.first);
211     if (value_result.ok() && *value_result == key_val.second) {
212       result[key_val.first] = key_val.second;
213     }
214   }
215 
216   result[kDynamicPartitions] = GetPartitionList(
217       vendor_info, system_info, kDynamicPartitions, extracted_images);
218 
219   const auto block_devices_result =
220       GetExpected(vendor_info, kSuperBlockDevices);
221   if (block_devices_result.ok()) {
222     result[kSuperBlockDevices] = *block_devices_result;
223     for (const auto& block_device :
224          android::base::Tokenize(result[kSuperBlockDevices], " ")) {
225       const auto key = "super_" + block_device + "_device_size";
226       result[key] = CF_EXPECT(GetExpected(vendor_info, key));
227     }
228   }
229 
230   result[kSuperPartitionGroups] =
231       CF_EXPECT(GetExpected(vendor_info, kSuperPartitionGroups));
232   for (const auto& group :
233        android::base::Tokenize(result[kSuperPartitionGroups], " ")) {
234     const auto group_size_key = "super_" + group + "_group_size";
235     result[group_size_key] =
236         CF_EXPECT(GetExpected(vendor_info, group_size_key));
237 
238     const auto partition_list_key = "super_" + group + "_partition_list";
239     result[partition_list_key] = GetPartitionList(
240         vendor_info, system_info, partition_list_key, extracted_images);
241   }
242 
243   // TODO(chadreynolds): add vabc_cow_version logic if we need to support older
244   // builds
245   for (const auto& key :
246        {"virtual_ab", "virtual_ab_retrofit", "lpmake", "super_metadata_device",
247         "super_partition_error_limit", "super_partition_size"}) {
248     const auto value_result = GetExpected(vendor_info, key);
249     if (value_result.ok()) {
250       result[key] = *value_result;
251     }
252   }
253   return std::move(result);
254 }
255 
MergeMiscInfos(const MiscInfo & vendor_info,const MiscInfo & system_info,const MiscInfo & combined_dp_info,const std::vector<std::string> & system_partitions)256 Result<MiscInfo> MergeMiscInfos(
257     const MiscInfo& vendor_info, const MiscInfo& system_info,
258     const MiscInfo& combined_dp_info,
259     const std::vector<std::string>& system_partitions) {
260   // the combined misc info uses the vendor values as defaults
261   MiscInfo result = vendor_info;
262   std::unordered_set<int> used_indices;
263   for (const auto& partition : system_partitions) {
264     for (const auto& key : GeneratePartitionKeys(partition)) {
265       if (!Contains(system_info, key)) {
266         continue;
267       }
268       auto system_value = system_info.find(key)->second;
269       // avb_<partition>_rollback_index_location values can conflict across
270       // different builds
271       if (android::base::EndsWith(key, kRollbackIndexSuffix)) {
272         const auto index = CF_EXPECT(
273             ResolveRollbackIndexConflicts(system_value, used_indices));
274         used_indices.insert(index);
275         system_value = std::to_string(index);
276       }
277       result[key] = system_value;
278     }
279   }
280   for (const auto& key : kNonPartitionKeysToMerge) {
281     const auto value_result = GetExpected(system_info, key);
282     if (value_result.ok()) {
283       result[key] = *value_result;
284     }
285   }
286   for (const auto& key_val : combined_dp_info) {
287     result[key_val.first] = key_val.second;
288   }
289   return std::move(result);
290 }
291 
GetVbmetaArgs(const MiscInfo & misc_info,const std::string & image_path)292 Result<VbmetaArgs> GetVbmetaArgs(const MiscInfo& misc_info,
293                                  const std::string& image_path) {
294   // The key_path value should exist, but it is a build system path
295   // We use a host artifacts relative path instead
296   CF_EXPECT(Contains(misc_info, kAvbVbmetaKeyPath));
297   const auto algorithm = CF_EXPECT(GetExpected(misc_info, kAvbVbmetaAlgorithm));
298   auto result = VbmetaArgs{
299       .algorithm = algorithm,
300       .key_path = CF_EXPECT(GetKeyPath(algorithm)),
301   };
302   // must split and add --<flag> <arg> arguments(non-equals format) separately
303   // due to how Command.AddParameter handles each argument
304   const auto extra_args_result = GetExpected(misc_info, kAvbVbmetaArgs);
305   if (extra_args_result.ok()) {
306     for (const auto& arg : android::base::Tokenize(*extra_args_result, " ")) {
307       result.extra_arguments.emplace_back(arg);
308     }
309   }
310 
311   for (const auto& partition : kVbmetaPartitions) {
312     // The key_path value should exist, but it is a build system path
313     // We use a host artifacts relative path instead
314     if (Contains(misc_info, fmt::format("avb_{}_key_path", partition))) {
315       const auto partition_algorithm = CF_EXPECT(
316           GetExpected(misc_info, fmt::format("avb_{}_algorithm", partition)));
317       result.chained_partitions.emplace_back(ChainPartition{
318           .name = partition,
319           .rollback_index = CF_EXPECT(GetExpected(
320               misc_info,
321               fmt::format("avb_{}_rollback_index_location", partition))),
322           .key_path = CF_EXPECT(GetPubKeyPath(partition_algorithm)),
323       });
324     } else {
325       result.included_partitions.emplace_back(
326           fmt::format("{}/IMAGES/{}.img", image_path, partition));
327     }
328   }
329   return result;
330 }
331 
332 } // namespace cuttlefish
333