1 /*
2  * Copyright (C) 2017 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 //#define LOG_NDEBUG 0
18 #define LOG_TAG "MediaMetricsService"
19 #include <utils/Log.h>
20 
21 #include "MediaMetricsService.h"
22 
23 #include <pwd.h> //getpwuid
24 
25 #include <android/content/pm/IPackageManagerNative.h>  // package info
26 #include <audio_utils/clock.h>                 // clock conversions
27 #include <binder/IPCThreadState.h>             // get calling uid
28 #include <cutils/properties.h>                 // for property_get
29 #include <mediautils/MemoryLeakTrackUtil.h>
30 #include <memunreachable/memunreachable.h>
31 #include <private/android_filesystem_config.h> // UID
32 
33 namespace android {
34 
35 using mediametrics::Item;
36 using mediametrics::startsWith;
37 
38 // individual records kept in memory: age or count
39 // age: <= 28 hours (1 1/6 days)
40 // count: hard limit of # records
41 // (0 for either of these disables that threshold)
42 //
43 static constexpr nsecs_t kMaxRecordAgeNs = 28 * 3600 * NANOS_PER_SECOND;
44 // 2019/6: average daily per device is currently 375-ish;
45 // setting this to 2000 is large enough to catch most devices
46 // we'll lose some data on very very media-active devices, but only for
47 // the gms collection; statsd will have already covered those for us.
48 // This also retains enough information to help with bugreports
49 static constexpr size_t kMaxRecords = 2000;
50 
51 // max we expire in a single call, to constrain how long we hold the
52 // mutex, which also constrains how long a client might wait.
53 static constexpr size_t kMaxExpiredAtOnce = 50;
54 
55 // TODO: need to look at tuning kMaxRecords and friends for low-memory devices
56 
57 /* static */
roundTime(nsecs_t timeNs)58 nsecs_t MediaMetricsService::roundTime(nsecs_t timeNs)
59 {
60     return (timeNs + NANOS_PER_SECOND / 2) / NANOS_PER_SECOND * NANOS_PER_SECOND;
61 }
62 
63 /* static */
useUidForPackage(const std::string & package,const std::string & installer)64 bool MediaMetricsService::useUidForPackage(
65         const std::string& package, const std::string& installer)
66 {
67     if (strchr(package.c_str(), '.') == nullptr) {
68         return false;  // not of form 'com.whatever...'; assume internal and ok
69     } else if (strncmp(package.c_str(), "android.", 8) == 0) {
70         return false;  // android.* packages are assumed fine
71     } else if (strncmp(installer.c_str(), "com.android.", 12) == 0) {
72         return false;  // from play store
73     } else if (strncmp(installer.c_str(), "com.google.", 11) == 0) {
74         return false;  // some google source
75     } else if (strcmp(installer.c_str(), "preload") == 0) {
76         return false;  // preloads
77     } else {
78         return true;  // we're not sure where it came from, use uid only.
79     }
80 }
81 
82 /* static */
83 std::pair<std::string, int64_t>
getSanitizedPackageNameAndVersionCode(uid_t uid)84 MediaMetricsService::getSanitizedPackageNameAndVersionCode(uid_t uid) {
85     // Meyer's singleton, initialized on first access.
86     // mUidInfo is locked internally.
87     static mediautils::UidInfo uidInfo;
88 
89     // get info.
90     mediautils::UidInfo::Info info = uidInfo.getInfo(uid);
91     if (useUidForPackage(info.package, info.installer)) {
92         return { std::to_string(uid), /* versionCode */ 0 };
93     } else {
94         return { info.package, info.versionCode };
95     }
96 }
97 
MediaMetricsService()98 MediaMetricsService::MediaMetricsService()
99         : mMaxRecords(kMaxRecords),
100           mMaxRecordAgeNs(kMaxRecordAgeNs),
101           mMaxRecordsExpiredAtOnce(kMaxExpiredAtOnce)
102 {
103     ALOGD("%s", __func__);
104 }
105 
~MediaMetricsService()106 MediaMetricsService::~MediaMetricsService()
107 {
108     ALOGD("%s", __func__);
109     // the class destructor clears anyhow, but we enforce clearing items first.
110     mItemsDiscarded += mItems.size();
111     mItems.clear();
112 }
113 
submitInternal(mediametrics::Item * item,bool release)114 status_t MediaMetricsService::submitInternal(mediametrics::Item *item, bool release)
115 {
116     // calling PID is 0 for one-way calls.
117     const pid_t pid = IPCThreadState::self()->getCallingPid();
118     const pid_t pid_given = item->getPid();
119     const uid_t uid = IPCThreadState::self()->getCallingUid();
120     const uid_t uid_given = item->getUid();
121 
122     //ALOGD("%s: caller pid=%d uid=%d,  item pid=%d uid=%d", __func__,
123     //        (int)pid, (int)uid, (int) pid_given, (int)uid_given);
124 
125     bool isTrusted;
126     switch (uid) {
127     case AID_AUDIOSERVER:
128     case AID_BLUETOOTH:
129     case AID_CAMERA:
130     case AID_DRM:
131     case AID_MEDIA:
132     case AID_MEDIA_CODEC:
133     case AID_MEDIA_EX:
134     case AID_MEDIA_DRM:
135     // case AID_SHELL: // DEBUG ONLY - used for mediametrics_tests to add new keys
136     case AID_SYSTEM:
137         // trusted source, only override default values
138         isTrusted = true;
139         if (uid_given == (uid_t)-1) {
140             item->setUid(uid);
141         }
142         if (pid_given == (pid_t)-1) {
143             item->setPid(pid); // if one-way then this is 0.
144         }
145         break;
146     default:
147         isTrusted = false;
148         item->setPid(pid); // always use calling pid, if one-way then this is 0.
149         item->setUid(uid);
150         break;
151     }
152 
153     // Overwrite package name and version if the caller was untrusted or empty
154     if (!isTrusted || item->getPkgName().empty()) {
155         const uid_t uidItem = item->getUid();
156         const auto [ pkgName, version ] =
157                 MediaMetricsService::getSanitizedPackageNameAndVersionCode(uidItem);
158         item->setPkgName(pkgName);
159         item->setPkgVersionCode(version);
160     }
161 
162     ALOGV("%s: isTrusted:%d given uid %d; sanitized uid: %d sanitized pkg: %s "
163           "sanitized pkg version: %lld",
164           __func__,
165           (int)isTrusted,
166           uid_given, item->getUid(),
167           item->getPkgName().c_str(),
168           (long long)item->getPkgVersionCode());
169 
170     mItemsSubmitted++;
171 
172     // validate the record; we discard if we don't like it
173     if (isContentValid(item, isTrusted) == false) {
174         if (release) delete item;
175         return PERMISSION_DENIED;
176     }
177 
178     // XXX: if we have a sessionid in the new record, look to make
179     // sure it doesn't appear in the finalized list.
180 
181     if (item->count() == 0) {
182         ALOGV("%s: dropping empty record...", __func__);
183         if (release) delete item;
184         return BAD_VALUE;
185     }
186 
187     if (!isTrusted || item->getTimestamp() == 0) {
188         // Statsd logs two times for events: ElapsedRealTimeNs (BOOTTIME) and
189         // WallClockTimeNs (REALTIME), but currently logs REALTIME to cloud.
190         //
191         // For consistency and correlation with other logging mechanisms
192         // we use REALTIME here.
193         const int64_t now = systemTime(SYSTEM_TIME_REALTIME);
194         item->setTimestamp(now);
195     }
196 
197     // now attach either the item or its dup to a const shared pointer
198     std::shared_ptr<const mediametrics::Item> sitem(release ? item : item->dup());
199 
200     (void)mAudioAnalytics.submit(sitem, isTrusted);
201 
202     extern bool dump2Statsd(const std::shared_ptr<const mediametrics::Item>& item);
203     (void)dump2Statsd(sitem);  // failure should be logged in function.
204     saveItem(sitem);
205     return NO_ERROR;
206 }
207 
dump(int fd,const Vector<String16> & args)208 status_t MediaMetricsService::dump(int fd, const Vector<String16>& args)
209 {
210     String8 result;
211 
212     if (checkCallingPermission(String16("android.permission.DUMP")) == false) {
213         result.appendFormat("Permission Denial: "
214                 "can't dump MediaMetricsService from pid=%d, uid=%d\n",
215                 IPCThreadState::self()->getCallingPid(),
216                 IPCThreadState::self()->getCallingUid());
217         write(fd, result.string(), result.size());
218         return NO_ERROR;
219     }
220 
221     static const String16 allOption("--all");
222     static const String16 clearOption("--clear");
223     static const String16 heapOption("--heap");
224     static const String16 helpOption("--help");
225     static const String16 prefixOption("--prefix");
226     static const String16 sinceOption("--since");
227     static const String16 unreachableOption("--unreachable");
228 
229     bool all = false;
230     bool clear = false;
231     bool heap = false;
232     bool unreachable = false;
233     int64_t sinceNs = 0;
234     std::string prefix;
235 
236     const size_t n = args.size();
237     for (size_t i = 0; i < n; i++) {
238         if (args[i] == allOption) {
239             all = true;
240         } else if (args[i] == clearOption) {
241             clear = true;
242         } else if (args[i] == heapOption) {
243             heap = true;
244         } else if (args[i] == helpOption) {
245             // TODO: consider function area dumping.
246             // dumpsys media.metrics audiotrack,codec
247             // or dumpsys media.metrics audiotrack codec
248 
249             result.append("Recognized parameters:\n");
250             result.append("--all         show all records\n");
251             result.append("--clear       clear out saved records\n");
252             result.append("--heap        show heap usage (top 100)\n");
253             result.append("--help        display help\n");
254             result.append("--prefix X    process records for component X\n");
255             result.append("--since X     X < 0: records from -X seconds in the past\n");
256             result.append("              X = 0: ignore\n");
257             result.append("              X > 0: records from X seconds since Unix epoch\n");
258             result.append("--unreachable show unreachable memory (leaks)\n");
259             write(fd, result.string(), result.size());
260             return NO_ERROR;
261         } else if (args[i] == prefixOption) {
262             ++i;
263             if (i < n) {
264                 prefix = String8(args[i]).string();
265             }
266         } else if (args[i] == sinceOption) {
267             ++i;
268             if (i < n) {
269                 String8 value(args[i]);
270                 char *endp;
271                 const char *p = value.string();
272                 const auto sec = (int64_t)strtoll(p, &endp, 10);
273                 if (endp == p || *endp != '\0' || sec == 0) {
274                     sinceNs = 0;
275                 } else if (sec < 0) {
276                     sinceNs = systemTime(SYSTEM_TIME_REALTIME) + sec * NANOS_PER_SECOND;
277                 } else {
278                     sinceNs = sec * NANOS_PER_SECOND;
279                 }
280             }
281         } else if (args[i] == unreachableOption) {
282             unreachable = true;
283         }
284     }
285 
286     {
287         std::lock_guard _l(mLock);
288 
289         if (clear) {
290             mItemsDiscarded += mItems.size();
291             mItems.clear();
292             mAudioAnalytics.clear();
293         } else {
294             result.appendFormat("Dump of the %s process:\n", kServiceName);
295             const char *prefixptr = prefix.size() > 0 ? prefix.c_str() : nullptr;
296             dumpHeaders(result, sinceNs, prefixptr);
297             dumpQueue(result, sinceNs, prefixptr);
298 
299             // TODO: maybe consider a better way of dumping audio analytics info.
300             const int32_t linesToDump = all ? INT32_MAX : 1000;
301             auto [ dumpString, lines ] = mAudioAnalytics.dump(linesToDump, sinceNs, prefixptr);
302             result.append(dumpString.c_str());
303             if (lines == linesToDump) {
304                 result.append("-- some lines may be truncated --\n");
305             }
306         }
307     }
308     write(fd, result.string(), result.size());
309 
310     // Check heap and unreachable memory outside of lock.
311     if (heap) {
312         dprintf(fd, "\nDumping heap:\n");
313         std::string s = dumpMemoryAddresses(100 /* limit */);
314         write(fd, s.c_str(), s.size());
315     }
316     if (unreachable) {
317         dprintf(fd, "\nDumping unreachable memory:\n");
318         // TODO - should limit be an argument parameter?
319         std::string s = GetUnreachableMemoryString(true /* contents */, 100 /* limit */);
320         write(fd, s.c_str(), s.size());
321     }
322     return NO_ERROR;
323 }
324 
325 // dump headers
dumpHeaders(String8 & result,int64_t sinceNs,const char * prefix)326 void MediaMetricsService::dumpHeaders(String8 &result, int64_t sinceNs, const char* prefix)
327 {
328     if (mediametrics::Item::isEnabled()) {
329         result.append("Metrics gathering: enabled\n");
330     } else {
331         result.append("Metrics gathering: DISABLED via property\n");
332     }
333     result.appendFormat(
334             "Since Boot: Submissions: %lld Accepted: %lld\n",
335             (long long)mItemsSubmitted.load(), (long long)mItemsFinalized);
336     result.appendFormat(
337             "Records Discarded: %lld (by Count: %lld by Expiration: %lld)\n",
338             (long long)mItemsDiscarded, (long long)mItemsDiscardedCount,
339             (long long)mItemsDiscardedExpire);
340     if (prefix != nullptr) {
341         result.appendFormat("Restricting to prefix %s", prefix);
342     }
343     if (sinceNs != 0) {
344         result.appendFormat(
345             "Emitting Queue entries more recent than: %lld\n",
346             (long long)sinceNs);
347     }
348 }
349 
350 // TODO: should prefix be a set<string>?
dumpQueue(String8 & result,int64_t sinceNs,const char * prefix)351 void MediaMetricsService::dumpQueue(String8 &result, int64_t sinceNs, const char* prefix)
352 {
353     if (mItems.empty()) {
354         result.append("empty\n");
355         return;
356     }
357 
358     int slot = 0;
359     for (const auto &item : mItems) {         // TODO: consider std::lower_bound() on mItems
360         if (item->getTimestamp() < sinceNs) { // sinceNs == 0 means all items shown
361             continue;
362         }
363         if (prefix != nullptr && !startsWith(item->getKey(), prefix)) {
364             ALOGV("%s: omit '%s', it's not '%s'",
365                     __func__, item->getKey().c_str(), prefix);
366             continue;
367         }
368         result.appendFormat("%5d: %s\n", slot, item->toString().c_str());
369         slot++;
370     }
371 }
372 
373 //
374 // Our Cheap in-core, non-persistent records management.
375 
376 // if item != NULL, it's the item we just inserted
377 // true == more items eligible to be recovered
expirations(const std::shared_ptr<const mediametrics::Item> & item)378 bool MediaMetricsService::expirations(const std::shared_ptr<const mediametrics::Item>& item)
379 {
380     bool more = false;
381 
382     // check queue size
383     size_t overlimit = 0;
384     if (mMaxRecords > 0 && mItems.size() > mMaxRecords) {
385         overlimit = mItems.size() - mMaxRecords;
386         if (overlimit > mMaxRecordsExpiredAtOnce) {
387             more = true;
388             overlimit = mMaxRecordsExpiredAtOnce;
389         }
390     }
391 
392     // check queue times
393     size_t expired = 0;
394     if (!more && mMaxRecordAgeNs > 0) {
395         const nsecs_t now = systemTime(SYSTEM_TIME_REALTIME);
396         // we check one at a time, skip search would be more efficient.
397         size_t i = overlimit;
398         for (; i < mItems.size(); ++i) {
399             auto &oitem = mItems[i];
400             nsecs_t when = oitem->getTimestamp();
401             if (oitem.get() == item.get()) {
402                 break;
403             }
404             if (now > when && (now - when) <= mMaxRecordAgeNs) {
405                 break; // Note SYSTEM_TIME_REALTIME may not be monotonic.
406             }
407             if (i >= mMaxRecordsExpiredAtOnce) {
408                 // this represents "one too many"; tell caller there are
409                 // more to be reclaimed.
410                 more = true;
411                 break;
412             }
413         }
414         expired = i - overlimit;
415     }
416 
417     if (const size_t toErase = overlimit + expired;
418             toErase > 0) {
419         mItemsDiscardedCount += overlimit;
420         mItemsDiscardedExpire += expired;
421         mItemsDiscarded += toErase;
422         mItems.erase(mItems.begin(), mItems.begin() + toErase); // erase from front
423     }
424     return more;
425 }
426 
processExpirations()427 void MediaMetricsService::processExpirations()
428 {
429     bool more;
430     do {
431         sleep(1);
432         std::lock_guard _l(mLock);
433         more = expirations(nullptr);
434     } while (more);
435 }
436 
saveItem(const std::shared_ptr<const mediametrics::Item> & item)437 void MediaMetricsService::saveItem(const std::shared_ptr<const mediametrics::Item>& item)
438 {
439     std::lock_guard _l(mLock);
440     // we assume the items are roughly in time order.
441     mItems.emplace_back(item);
442     ++mItemsFinalized;
443     if (expirations(item)
444             && (!mExpireFuture.valid()
445                || mExpireFuture.wait_for(std::chrono::seconds(0)) == std::future_status::ready)) {
446         mExpireFuture = std::async(std::launch::async, [this] { processExpirations(); });
447     }
448 }
449 
450 /* static */
isContentValid(const mediametrics::Item * item,bool isTrusted)451 bool MediaMetricsService::isContentValid(const mediametrics::Item *item, bool isTrusted)
452 {
453     if (isTrusted) return true;
454     // untrusted uids can only send us a limited set of keys
455     const std::string &key = item->getKey();
456     if (startsWith(key, "audio.")) return true;
457     if (startsWith(key, "drm.vendor.")) return true;
458     // the list of allowedKey uses statsd_handlers
459     // in iface_statsd.cpp as reference
460     // drmmanager is from a trusted uid, therefore not needed here
461     for (const char *allowedKey : {
462                                      // legacy audio
463                                      "audiopolicy",
464                                      "audiorecord",
465                                      "audiothread",
466                                      "audiotrack",
467                                      // other media
468                                      "codec",
469                                      "extractor",
470                                      "mediadrm",
471                                      "nuplayer",
472                                  }) {
473         if (key == allowedKey) {
474             return true;
475         }
476     }
477     ALOGD("%s: invalid key: %s", __func__, item->toString().c_str());
478     return false;
479 }
480 
481 // are we rate limited, normally false
isRateLimited(mediametrics::Item *) const482 bool MediaMetricsService::isRateLimited(mediametrics::Item *) const
483 {
484     return false;
485 }
486 
487 } // namespace android
488