• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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("&quot;");
61         break;
62       case '\'':
63         output->append("&apos;");
64         break;
65       case '&':
66         output->append("&amp;");
67         break;
68       case '<':
69         output->append("&lt;");
70         break;
71       case '>':
72         output->append("&gt;");
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