1 //
2 // Copyright (C) 2019 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
17 #include "update_engine/cros/omaha_request_builder_xml.h"
18
19 #include <inttypes.h>
20
21 #include <string>
22
23 #include <base/guid.h>
24 #include <base/logging.h>
25 #include <base/strings/string_number_conversions.h>
26 #include <base/strings/string_util.h>
27 #include <base/strings/stringprintf.h>
28 #include <base/time/time.h>
29
30 #include "update_engine/common/constants.h"
31 #include "update_engine/common/system_state.h"
32 #include "update_engine/common/utils.h"
33 #include "update_engine/cros/omaha_request_params.h"
34
35 using std::string;
36
37 namespace chromeos_update_engine {
38
39 const char kNoVersion[] = "0.0.0.0";
40 const int kPingNeverPinged = -1;
41 const int kPingUnknownValue = -2;
42 const int kPingActiveValue = 1;
43 const int kPingInactiveValue = 0;
44
XmlEncode(const string & input,string * output)45 bool XmlEncode(const string& input, string* output) {
46 if (std::find_if(input.begin(), input.end(), [](const char c) {
47 return c & 0x80;
48 }) != input.end()) {
49 LOG(WARNING) << "Invalid ASCII-7 string passed to the XML encoder:";
50 utils::HexDumpString(input);
51 return false;
52 }
53 output->clear();
54 // We need at least input.size() space in the output, but the code below will
55 // handle it if we need more.
56 output->reserve(input.size());
57 for (char c : input) {
58 switch (c) {
59 case '\"':
60 output->append(""");
61 break;
62 case '\'':
63 output->append("'");
64 break;
65 case '&':
66 output->append("&");
67 break;
68 case '<':
69 output->append("<");
70 break;
71 case '>':
72 output->append(">");
73 break;
74 default:
75 output->push_back(c);
76 }
77 }
78 return true;
79 }
80
XmlEncodeWithDefault(const string & input,const string & default_value)81 string XmlEncodeWithDefault(const string& input, const string& default_value) {
82 string output;
83 if (XmlEncode(input, &output))
84 return output;
85 return default_value;
86 }
87
GetPing() const88 string OmahaRequestBuilderXml::GetPing() const {
89 // Returns an XML ping element attribute assignment with attribute
90 // |name| and value |ping_days| if |ping_days| has a value that needs
91 // to be sent, or an empty string otherwise.
92 auto GetPingAttribute = [](const char* name, int ping_days) -> string {
93 if (ping_days > 0 || ping_days == kPingNeverPinged)
94 return base::StringPrintf(" %s=\"%d\"", name, ping_days);
95 return "";
96 };
97
98 string ping_active = GetPingAttribute("a", ping_active_days_);
99 string ping_roll_call = GetPingAttribute("r", ping_roll_call_days_);
100 if (!ping_active.empty() || !ping_roll_call.empty()) {
101 return base::StringPrintf(" <ping active=\"1\"%s%s></ping>\n",
102 ping_active.c_str(),
103 ping_roll_call.c_str());
104 }
105 return "";
106 }
107
GetPingDateBased(const OmahaRequestParams::AppParams & app_params) const108 string OmahaRequestBuilderXml::GetPingDateBased(
109 const OmahaRequestParams::AppParams& app_params) const {
110 if (!app_params.send_ping)
111 return "";
112 string ping_active = "";
113 string ping_ad = "";
114 if (app_params.ping_active == kPingActiveValue) {
115 ping_active =
116 base::StringPrintf(" active=\"%" PRId64 "\"", app_params.ping_active);
117 ping_ad = base::StringPrintf(" ad=\"%" PRId64 "\"",
118 app_params.ping_date_last_active);
119 }
120
121 string ping_rd = base::StringPrintf(" rd=\"%" PRId64 "\"",
122 app_params.ping_date_last_rollcall);
123
124 return base::StringPrintf(" <ping%s%s%s></ping>\n",
125 ping_active.c_str(),
126 ping_ad.c_str(),
127 ping_rd.c_str());
128 }
129
GetAppBody(const OmahaAppData & app_data) const130 string OmahaRequestBuilderXml::GetAppBody(const OmahaAppData& app_data) const {
131 string app_body;
132 if (event_ == nullptr) {
133 if (app_data.app_params.send_ping) {
134 switch (app_data.app_params.active_counting_type) {
135 case OmahaRequestParams::kDayBased:
136 app_body = GetPing();
137 break;
138 case OmahaRequestParams::kDateBased:
139 app_body = GetPingDateBased(app_data.app_params);
140 break;
141 default:
142 NOTREACHED();
143 }
144 }
145 if (!ping_only_) {
146 if (!app_data.skip_update) {
147 const auto* params = SystemState::Get()->request_params();
148 app_body += " <updatecheck";
149 if (!params->target_version_prefix().empty()) {
150 app_body += base::StringPrintf(
151 " targetversionprefix=\"%s\"",
152 XmlEncodeWithDefault(params->target_version_prefix()).c_str());
153 // Rollback requires target_version_prefix set.
154 if (params->rollback_allowed()) {
155 app_body += " rollback_allowed=\"true\"";
156 }
157 }
158 if (!params->lts_tag().empty()) {
159 app_body += base::StringPrintf(
160 " ltstag=\"%s\"",
161 XmlEncodeWithDefault(params->lts_tag()).c_str());
162 }
163 app_body += "></updatecheck>\n";
164 }
165
166 // If this is the first update check after a reboot following a previous
167 // update, generate an event containing the previous version number. If
168 // the previous version preference file doesn't exist the event is still
169 // generated with a previous version of 0.0.0.0 -- this is relevant for
170 // older clients or new installs. The previous version event is not sent
171 // for ping-only requests because they come before the client has
172 // rebooted. The previous version event is also not sent if it was already
173 // sent for this new version with a previous updatecheck.
174 auto* prefs = SystemState::Get()->prefs();
175 string prev_version;
176 if (!prefs->GetString(kPrefsPreviousVersion, &prev_version)) {
177 prev_version = kNoVersion;
178 }
179 // We only store a non-empty previous version value after a successful
180 // update in the previous boot. After reporting it back to the server,
181 // we clear the previous version value so it doesn't get reported again.
182 if (!prev_version.empty()) {
183 app_body += base::StringPrintf(
184 " <event eventtype=\"%d\" eventresult=\"%d\" "
185 "previousversion=\"%s\"></event>\n",
186 OmahaEvent::kTypeRebootedAfterUpdate,
187 OmahaEvent::kResultSuccess,
188 XmlEncodeWithDefault(prev_version, kNoVersion).c_str());
189 LOG_IF(WARNING, !prefs->SetString(kPrefsPreviousVersion, ""))
190 << "Unable to reset the previous version.";
191 }
192 }
193 } else {
194 int event_result = event_->result;
195 // The error code is an optional attribute so append it only if the result
196 // is not success.
197 string error_code;
198 if (event_result != OmahaEvent::kResultSuccess) {
199 error_code = base::StringPrintf(" errorcode=\"%d\"",
200 static_cast<int>(event_->error_code));
201 } else if (app_data.is_dlc && !app_data.app_params.updated) {
202 // On a |OmahaEvent::kResultSuccess|, if the event is for an update
203 // completion and the App is a DLC, send error for excluded DLCs as they
204 // did not update.
205 event_result = OmahaEvent::Result::kResultError;
206 error_code = base::StringPrintf(
207 " errorcode=\"%d\"",
208 static_cast<int>(ErrorCode::kPackageExcludedFromUpdate));
209 }
210 app_body = base::StringPrintf(
211 " <event eventtype=\"%d\" eventresult=\"%d\"%s></event>\n",
212 event_->type,
213 event_result,
214 error_code.c_str());
215 }
216
217 return app_body;
218 }
219
GetCohortArg(const string & arg_name,const string & prefs_key,const string & override_value) const220 string OmahaRequestBuilderXml::GetCohortArg(
221 const string& arg_name,
222 const string& prefs_key,
223 const string& override_value) const {
224 string cohort_value;
225 if (!override_value.empty()) {
226 // |override_value| take precedence over pref value.
227 cohort_value = override_value;
228 } else {
229 // There's nothing wrong with not having a given cohort setting, so we check
230 // existence first to avoid the warning log message.
231 const auto* prefs = SystemState::Get()->prefs();
232 if (!prefs->Exists(prefs_key))
233 return "";
234 if (!prefs->GetString(prefs_key, &cohort_value) || cohort_value.empty())
235 return "";
236 }
237 // This is a validity check to avoid sending a huge XML file back to Ohama due
238 // to a compromised stateful partition making the update check fail in low
239 // network environments envent after a reboot.
240 if (cohort_value.size() > 1024) {
241 LOG(WARNING) << "The omaha cohort setting " << arg_name
242 << " has a too big value, which must be an error or an "
243 "attacker trying to inhibit updates.";
244 return "";
245 }
246
247 string escaped_xml_value;
248 if (!XmlEncode(cohort_value, &escaped_xml_value)) {
249 LOG(WARNING) << "The omaha cohort setting " << arg_name
250 << " is ASCII-7 invalid, ignoring it.";
251 return "";
252 }
253
254 return base::StringPrintf(
255 "%s=\"%s\" ", arg_name.c_str(), escaped_xml_value.c_str());
256 }
257
IsValidComponentID(const string & id)258 bool IsValidComponentID(const string& id) {
259 for (char c : id) {
260 if (!isalnum(c) && c != '-' && c != '_' && c != '.')
261 return false;
262 }
263 return true;
264 }
265
GetApp(const OmahaAppData & app_data) const266 string OmahaRequestBuilderXml::GetApp(const OmahaAppData& app_data) const {
267 string app_body = GetAppBody(app_data);
268 string app_versions;
269 const auto* params = SystemState::Get()->request_params();
270
271 // If we are downgrading to a more stable channel and we are allowed to do
272 // powerwash, then pass 0.0.0.0 as the version. This is needed to get the
273 // highest-versioned payload on the destination channel.
274 if (params->ShouldPowerwash()) {
275 LOG(INFO) << "Passing OS version as 0.0.0.0 as we are set to powerwash "
276 << "on downgrading to the version in the more stable channel";
277 app_versions = "version=\"" + string(kNoVersion) + "\" from_version=\"" +
278 XmlEncodeWithDefault(app_data.version, kNoVersion) + "\" ";
279 } else {
280 app_versions = "version=\"" +
281 XmlEncodeWithDefault(app_data.version, kNoVersion) + "\" ";
282 }
283
284 string download_channel = params->download_channel();
285 string app_channels =
286 "track=\"" + XmlEncodeWithDefault(download_channel) + "\" ";
287 if (params->current_channel() != download_channel) {
288 app_channels += "from_track=\"" +
289 XmlEncodeWithDefault(params->current_channel()) + "\" ";
290 }
291
292 string delta_okay_str =
293 params->delta_okay() && !params->is_install() ? "true" : "false";
294
295 // If install_date_days is not set (e.g. its value is -1 ), don't
296 // include the attribute.
297 string install_date_in_days_str = "";
298 if (install_date_in_days_ >= 0) {
299 install_date_in_days_str =
300 base::StringPrintf("installdate=\"%d\" ", install_date_in_days_);
301 }
302
303 string app_cohort_args;
304 string cohort_key = kPrefsOmahaCohort;
305 string cohortname_key = kPrefsOmahaCohortName;
306 string cohorthint_key = kPrefsOmahaCohortHint;
307
308 // Override the cohort keys for DLC App IDs.
309 const auto& dlc_apps_params = params->dlc_apps_params();
310 auto itr = dlc_apps_params.find(app_data.id);
311 if (itr != dlc_apps_params.end()) {
312 auto dlc_id = itr->second.name;
313 const auto* prefs = SystemState::Get()->prefs();
314 cohort_key =
315 prefs->CreateSubKey({kDlcPrefsSubDir, dlc_id, kPrefsOmahaCohort});
316 cohortname_key =
317 prefs->CreateSubKey({kDlcPrefsSubDir, dlc_id, kPrefsOmahaCohortName});
318 cohorthint_key =
319 prefs->CreateSubKey({kDlcPrefsSubDir, dlc_id, kPrefsOmahaCohortHint});
320 }
321
322 app_cohort_args += GetCohortArg("cohort", cohort_key);
323 app_cohort_args += GetCohortArg("cohortname", cohortname_key);
324 // Policy provided value overrides pref.
325 app_cohort_args +=
326 GetCohortArg("cohorthint",
327 cohorthint_key,
328 params->autoupdate_token() /* override_value */);
329
330 string fingerprint_arg;
331 if (!params->os_build_fingerprint().empty()) {
332 fingerprint_arg = "fingerprint=\"" +
333 XmlEncodeWithDefault(params->os_build_fingerprint()) +
334 "\" ";
335 }
336
337 string buildtype_arg;
338 if (!params->os_build_type().empty()) {
339 buildtype_arg = "os_build_type=\"" +
340 XmlEncodeWithDefault(params->os_build_type()) + "\" ";
341 }
342
343 string product_components_args;
344 if (!params->ShouldPowerwash() && !app_data.product_components.empty()) {
345 brillo::KeyValueStore store;
346 if (store.LoadFromString(app_data.product_components)) {
347 for (const string& key : store.GetKeys()) {
348 if (!IsValidComponentID(key)) {
349 LOG(ERROR) << "Invalid component id: " << key;
350 continue;
351 }
352 string version;
353 if (!store.GetString(key, &version)) {
354 LOG(ERROR) << "Failed to get version for " << key
355 << " in product_components.";
356 continue;
357 }
358 product_components_args +=
359 base::StringPrintf("_%s.version=\"%s\" ",
360 key.c_str(),
361 XmlEncodeWithDefault(version).c_str());
362 }
363 } else {
364 LOG(ERROR) << "Failed to parse product_components:\n"
365 << app_data.product_components;
366 }
367 }
368
369 string requisition_arg;
370 if (!params->device_requisition().empty()) {
371 requisition_arg = "requisition=\"" +
372 XmlEncodeWithDefault(params->device_requisition()) +
373 "\" ";
374 }
375
376 // clang-format off
377 string app_xml = " <app "
378 "appid=\"" + XmlEncodeWithDefault(app_data.id) + "\" " +
379 app_cohort_args +
380 app_versions +
381 app_channels +
382 product_components_args +
383 fingerprint_arg +
384 buildtype_arg +
385 "board=\"" + XmlEncodeWithDefault(params->os_board()) + "\" " +
386 "hardware_class=\"" + XmlEncodeWithDefault(params->hwid()) + "\" " +
387 "delta_okay=\"" + delta_okay_str + "\" " +
388 install_date_in_days_str +
389
390 // DLC excluded for installs and updates.
391 (app_data.is_dlc ? "" :
392 "lang=\"" + XmlEncodeWithDefault(params->app_lang(), "en-US") + "\" " +
393 requisition_arg) +
394
395 ">\n" +
396 app_body +
397 " </app>\n";
398 // clang-format on
399 return app_xml;
400 }
401
GetOs() const402 string OmahaRequestBuilderXml::GetOs() const {
403 const auto* params = SystemState::Get()->request_params();
404 string os_xml =
405 " <os "
406 "version=\"" +
407 XmlEncodeWithDefault(params->os_version()) + "\" " + "platform=\"" +
408 XmlEncodeWithDefault(params->os_platform()) + "\" " + "sp=\"" +
409 XmlEncodeWithDefault(params->os_sp()) +
410 "\">"
411 "</os>\n";
412 return os_xml;
413 }
414
GetRequest() const415 string OmahaRequestBuilderXml::GetRequest() const {
416 const auto* params = SystemState::Get()->request_params();
417 string os_xml = GetOs();
418 string app_xml = GetApps();
419
420 string request_xml = base::StringPrintf(
421 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
422 "<request requestid=\"%s\" sessionid=\"%s\""
423 " protocol=\"3.0\" updater=\"%s\" updaterversion=\"%s\""
424 " installsource=\"%s\" ismachine=\"1\">\n%s%s</request>\n",
425 base::GenerateGUID().c_str() /* requestid */,
426 session_id_.c_str(),
427 constants::kOmahaUpdaterID,
428 kOmahaUpdaterVersion,
429 params->interactive() ? "ondemandupdate" : "scheduler",
430 os_xml.c_str(),
431 app_xml.c_str());
432
433 return request_xml;
434 }
435
GetApps() const436 string OmahaRequestBuilderXml::GetApps() const {
437 const auto* params = SystemState::Get()->request_params();
438 string app_xml = "";
439 OmahaAppData product_app = {
440 .id = params->GetAppId(),
441 .version = params->app_version(),
442 .product_components = params->product_components(),
443 // Skips updatecheck for platform app in case of an install operation.
444 .skip_update = params->is_install(),
445 .is_dlc = false,
446
447 .app_params = {.active_counting_type = OmahaRequestParams::kDayBased,
448 .send_ping = include_ping_}};
449 app_xml += GetApp(product_app);
450 for (const auto& it : params->dlc_apps_params()) {
451 OmahaAppData dlc_app_data = {
452 .id = it.first,
453 .version = params->is_install() ? kNoVersion : params->app_version(),
454 .skip_update = false,
455 .is_dlc = true,
456 .app_params = it.second};
457 app_xml += GetApp(dlc_app_data);
458 }
459 return app_xml;
460 }
461
462 } // namespace chromeos_update_engine
463