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/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/prefs_interface.h"
32 #include "update_engine/common/utils.h"
33 #include "update_engine/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         app_body += "        <updatecheck";
148         if (!params_->target_version_prefix().empty()) {
149           app_body += base::StringPrintf(
150               " targetversionprefix=\"%s\"",
151               XmlEncodeWithDefault(params_->target_version_prefix()).c_str());
152           // Rollback requires target_version_prefix set.
153           if (params_->rollback_allowed()) {
154             app_body += " rollback_allowed=\"true\"";
155           }
156         }
157         app_body += "></updatecheck>\n";
158       }
159 
160       // If this is the first update check after a reboot following a previous
161       // update, generate an event containing the previous version number. If
162       // the previous version preference file doesn't exist the event is still
163       // generated with a previous version of 0.0.0.0 -- this is relevant for
164       // older clients or new installs. The previous version event is not sent
165       // for ping-only requests because they come before the client has
166       // rebooted. The previous version event is also not sent if it was already
167       // sent for this new version with a previous updatecheck.
168       string prev_version;
169       if (!prefs_->GetString(kPrefsPreviousVersion, &prev_version)) {
170         prev_version = kNoVersion;
171       }
172       // We only store a non-empty previous version value after a successful
173       // update in the previous boot. After reporting it back to the server,
174       // we clear the previous version value so it doesn't get reported again.
175       if (!prev_version.empty()) {
176         app_body += base::StringPrintf(
177             "        <event eventtype=\"%d\" eventresult=\"%d\" "
178             "previousversion=\"%s\"></event>\n",
179             OmahaEvent::kTypeRebootedAfterUpdate,
180             OmahaEvent::kResultSuccess,
181             XmlEncodeWithDefault(prev_version, kNoVersion).c_str());
182         LOG_IF(WARNING, !prefs_->SetString(kPrefsPreviousVersion, ""))
183             << "Unable to reset the previous version.";
184       }
185     }
186   } else {
187     // The error code is an optional attribute so append it only if the result
188     // is not success.
189     string error_code;
190     if (event_->result != OmahaEvent::kResultSuccess) {
191       error_code = base::StringPrintf(" errorcode=\"%d\"",
192                                       static_cast<int>(event_->error_code));
193     }
194     app_body = base::StringPrintf(
195         "        <event eventtype=\"%d\" eventresult=\"%d\"%s></event>\n",
196         event_->type,
197         event_->result,
198         error_code.c_str());
199   }
200 
201   return app_body;
202 }
203 
GetCohortArg(const string arg_name,const string prefs_key,const string override_value) const204 string OmahaRequestBuilderXml::GetCohortArg(const string arg_name,
205                                             const string prefs_key,
206                                             const string override_value) const {
207   string cohort_value;
208   if (!override_value.empty()) {
209     // |override_value| take precedence over pref value.
210     cohort_value = override_value;
211   } else {
212     // There's nothing wrong with not having a given cohort setting, so we check
213     // existence first to avoid the warning log message.
214     if (!prefs_->Exists(prefs_key))
215       return "";
216     if (!prefs_->GetString(prefs_key, &cohort_value) || cohort_value.empty())
217       return "";
218   }
219   // This is a validity check to avoid sending a huge XML file back to Ohama due
220   // to a compromised stateful partition making the update check fail in low
221   // network environments envent after a reboot.
222   if (cohort_value.size() > 1024) {
223     LOG(WARNING) << "The omaha cohort setting " << arg_name
224                  << " has a too big value, which must be an error or an "
225                     "attacker trying to inhibit updates.";
226     return "";
227   }
228 
229   string escaped_xml_value;
230   if (!XmlEncode(cohort_value, &escaped_xml_value)) {
231     LOG(WARNING) << "The omaha cohort setting " << arg_name
232                  << " is ASCII-7 invalid, ignoring it.";
233     return "";
234   }
235 
236   return base::StringPrintf(
237       "%s=\"%s\" ", arg_name.c_str(), escaped_xml_value.c_str());
238 }
239 
IsValidComponentID(const string & id)240 bool IsValidComponentID(const string& id) {
241   for (char c : id) {
242     if (!isalnum(c) && c != '-' && c != '_' && c != '.')
243       return false;
244   }
245   return true;
246 }
247 
GetApp(const OmahaAppData & app_data) const248 string OmahaRequestBuilderXml::GetApp(const OmahaAppData& app_data) const {
249   string app_body = GetAppBody(app_data);
250   string app_versions;
251 
252   // If we are downgrading to a more stable channel and we are allowed to do
253   // powerwash, then pass 0.0.0.0 as the version. This is needed to get the
254   // highest-versioned payload on the destination channel.
255   if (params_->ShouldPowerwash()) {
256     LOG(INFO) << "Passing OS version as 0.0.0.0 as we are set to powerwash "
257               << "on downgrading to the version in the more stable channel";
258     app_versions = "version=\"" + string(kNoVersion) + "\" from_version=\"" +
259                    XmlEncodeWithDefault(app_data.version, kNoVersion) + "\" ";
260   } else {
261     app_versions = "version=\"" +
262                    XmlEncodeWithDefault(app_data.version, kNoVersion) + "\" ";
263   }
264 
265   string download_channel = params_->download_channel();
266   string app_channels =
267       "track=\"" + XmlEncodeWithDefault(download_channel) + "\" ";
268   if (params_->current_channel() != download_channel) {
269     app_channels += "from_track=\"" +
270                     XmlEncodeWithDefault(params_->current_channel()) + "\" ";
271   }
272 
273   string delta_okay_str =
274       params_->delta_okay() && !params_->is_install() ? "true" : "false";
275 
276   // If install_date_days is not set (e.g. its value is -1 ), don't
277   // include the attribute.
278   string install_date_in_days_str = "";
279   if (install_date_in_days_ >= 0) {
280     install_date_in_days_str =
281         base::StringPrintf("installdate=\"%d\" ", install_date_in_days_);
282   }
283 
284   string app_cohort_args;
285   app_cohort_args += GetCohortArg("cohort", kPrefsOmahaCohort);
286   app_cohort_args += GetCohortArg("cohortname", kPrefsOmahaCohortName);
287 
288   // Policy provided value overrides pref.
289   string autoupdate_token = params_->autoupdate_token();
290   app_cohort_args += GetCohortArg("cohorthint",
291                                   kPrefsOmahaCohortHint,
292                                   autoupdate_token /* override_value */);
293 
294   string fingerprint_arg;
295   if (!params_->os_build_fingerprint().empty()) {
296     fingerprint_arg = "fingerprint=\"" +
297                       XmlEncodeWithDefault(params_->os_build_fingerprint()) +
298                       "\" ";
299   }
300 
301   string buildtype_arg;
302   if (!params_->os_build_type().empty()) {
303     buildtype_arg = "os_build_type=\"" +
304                     XmlEncodeWithDefault(params_->os_build_type()) + "\" ";
305   }
306 
307   string product_components_args;
308   if (!params_->ShouldPowerwash() && !app_data.product_components.empty()) {
309     brillo::KeyValueStore store;
310     if (store.LoadFromString(app_data.product_components)) {
311       for (const string& key : store.GetKeys()) {
312         if (!IsValidComponentID(key)) {
313           LOG(ERROR) << "Invalid component id: " << key;
314           continue;
315         }
316         string version;
317         if (!store.GetString(key, &version)) {
318           LOG(ERROR) << "Failed to get version for " << key
319                      << " in product_components.";
320           continue;
321         }
322         product_components_args +=
323             base::StringPrintf("_%s.version=\"%s\" ",
324                                key.c_str(),
325                                XmlEncodeWithDefault(version).c_str());
326       }
327     } else {
328       LOG(ERROR) << "Failed to parse product_components:\n"
329                  << app_data.product_components;
330     }
331   }
332 
333   string requisition_arg;
334   if (!params_->device_requisition().empty()) {
335     requisition_arg = "requisition=\"" +
336                       XmlEncodeWithDefault(params_->device_requisition()) +
337                       "\" ";
338   }
339 
340   // clang-format off
341   string app_xml = "    <app "
342       "appid=\"" + XmlEncodeWithDefault(app_data.id) + "\" " +
343       app_cohort_args +
344       app_versions +
345       app_channels +
346       product_components_args +
347       fingerprint_arg +
348       buildtype_arg +
349       "board=\"" + XmlEncodeWithDefault(params_->os_board()) + "\" " +
350       "hardware_class=\"" + XmlEncodeWithDefault(params_->hwid()) + "\" " +
351       "delta_okay=\"" + delta_okay_str + "\" " +
352       install_date_in_days_str +
353 
354       // DLC excluded for installs and updates.
355       (app_data.is_dlc ? "" :
356       "lang=\"" + XmlEncodeWithDefault(params_->app_lang(), "en-US") + "\" " +
357       "fw_version=\"" + XmlEncodeWithDefault(params_->fw_version()) + "\" " +
358       "ec_version=\"" + XmlEncodeWithDefault(params_->ec_version()) + "\" " +
359       requisition_arg) +
360 
361       ">\n" +
362          app_body +
363       "    </app>\n";
364   // clang-format on
365   return app_xml;
366 }
367 
GetOs() const368 string OmahaRequestBuilderXml::GetOs() const {
369   string os_xml =
370       "    <os "
371       "version=\"" +
372       XmlEncodeWithDefault(params_->os_version()) + "\" " + "platform=\"" +
373       XmlEncodeWithDefault(params_->os_platform()) + "\" " + "sp=\"" +
374       XmlEncodeWithDefault(params_->os_sp()) +
375       "\">"
376       "</os>\n";
377   return os_xml;
378 }
379 
GetRequest() const380 string OmahaRequestBuilderXml::GetRequest() const {
381   string os_xml = GetOs();
382   string app_xml = GetApps();
383 
384   string request_xml = base::StringPrintf(
385       "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
386       "<request requestid=\"%s\" sessionid=\"%s\""
387       " protocol=\"3.0\" updater=\"%s\" updaterversion=\"%s\""
388       " installsource=\"%s\" ismachine=\"1\">\n%s%s</request>\n",
389       base::GenerateGUID().c_str() /* requestid */,
390       session_id_.c_str(),
391       constants::kOmahaUpdaterID,
392       kOmahaUpdaterVersion,
393       params_->interactive() ? "ondemandupdate" : "scheduler",
394       os_xml.c_str(),
395       app_xml.c_str());
396 
397   return request_xml;
398 }
399 
GetApps() const400 string OmahaRequestBuilderXml::GetApps() const {
401   string app_xml = "";
402   OmahaAppData product_app = {
403       .id = params_->GetAppId(),
404       .version = params_->app_version(),
405       .product_components = params_->product_components(),
406       // Skips updatecheck for platform app in case of an install operation.
407       .skip_update = params_->is_install(),
408       .is_dlc = false,
409 
410       .app_params = {.active_counting_type = OmahaRequestParams::kDayBased,
411                      .send_ping = include_ping_}};
412   app_xml += GetApp(product_app);
413   if (!params_->system_app_id().empty()) {
414     OmahaAppData system_app = {
415         .id = params_->system_app_id(),
416         .version = params_->system_version(),
417         .skip_update = false,
418         .is_dlc = false,
419         .app_params = {.active_counting_type = OmahaRequestParams::kDayBased,
420                        .send_ping = include_ping_}};
421     app_xml += GetApp(system_app);
422   }
423   for (const auto& it : params_->dlc_apps_params()) {
424     OmahaAppData dlc_app_data = {
425         .id = it.first,
426         .version = params_->is_install() ? kNoVersion : params_->app_version(),
427         .skip_update = false,
428         .is_dlc = true,
429         .app_params = it.second};
430     app_xml += GetApp(dlc_app_data);
431   }
432   return app_xml;
433 }
434 
435 }  // namespace chromeos_update_engine
436