diff options
author | Treehugger Robot <treehugger-gerrit@google.com> | 2019-09-11 12:54:54 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2019-09-11 12:54:54 +0000 |
commit | f24b408a258fd197434e5c278b1203968327bc05 (patch) | |
tree | edba58c6c0ca1a336bb9341318ffcd3bf00ffb6d | |
parent | 708a89255b62a7f0ab45165ce76ab092c6d68930 (diff) | |
parent | 9aabe430a3e1e2ab09c787cbc8a6b1c09fecd781 (diff) | |
download | experimental-f24b408a258fd197434e5c278b1203968327bc05.tar.gz |
Merge "Add HiddenApiLogging sample"
-rw-r--r-- | HiddenApiLoggingApp/Android.bp | 36 | ||||
-rw-r--r-- | HiddenApiLoggingApp/AndroidManifest.xml | 33 | ||||
-rw-r--r-- | HiddenApiLoggingApp/README | 12 | ||||
-rw-r--r-- | HiddenApiLoggingApp/src/com/android/hiddenapiapp/Receiver.java | 116 | ||||
-rw-r--r-- | HiddenApiLoggingApp/tool/hidden_api_log_tracking.mf | 1 | ||||
-rw-r--r-- | HiddenApiLoggingApp/tool/src/com/hiddenapi/HiddenApiLogTracking.java | 133 |
6 files changed, 331 insertions, 0 deletions
diff --git a/HiddenApiLoggingApp/Android.bp b/HiddenApiLoggingApp/Android.bp new file mode 100644 index 0000000..c29e7b0 --- /dev/null +++ b/HiddenApiLoggingApp/Android.bp @@ -0,0 +1,36 @@ +// Copyright (C) 2019 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +android_app { + name: "HiddenApiLoggingApp", + certificate: "platform", + sdk_version: "system_current", + srcs: ["src/**/*.java"], + static_libs: [ + "androidx.annotation_annotation", + "androidx.core_core", + "androidx.fragment_fragment", + ], +} + +java_binary_host { + name: "hidden_api_log_tracking", + manifest: "tool/hidden_api_log_tracking.mf", + srcs: [ + "tool/src/com/hiddenapi/HiddenApiLogTracking.java", + ], + static_libs: [ + "platformprotos", + ] +}
\ No newline at end of file diff --git a/HiddenApiLoggingApp/AndroidManifest.xml b/HiddenApiLoggingApp/AndroidManifest.xml new file mode 100644 index 0000000..87e3081 --- /dev/null +++ b/HiddenApiLoggingApp/AndroidManifest.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + --> + +<manifest + xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.hiddenapiapp"> + <!-- Add permission for changing DeviceConfig --> + <uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG" /> + <!-- Add permissions for StatsManager.addConfig --> + <uses-permission android:name="android.permission.DUMP" /> + <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" /> + <application + android:label="Hidden Api Logging Manager"> + <receiver + android:name=".Receiver" + android:exported="true"> + </receiver> + </application> +</manifest> diff --git a/HiddenApiLoggingApp/README b/HiddenApiLoggingApp/README new file mode 100644 index 0000000..fe6af71 --- /dev/null +++ b/HiddenApiLoggingApp/README @@ -0,0 +1,12 @@ +HiddenApiLoggingApp.apk: + +Demonstrates how to obtain the hidden api logging data. +Usage: +1. mmma packages/experimental/HiddenApiLoggingApp -j +2. adb install -r ${OUT}/system/app/HiddenApiLoggingApp/HiddenApiLoggingApp.apk +3 ./out/host/linux-x86/bin/hidden_api_log_tracking +4. <wait for the 'Waiting for hidden api accesses... Press Ctrl-c to read logs' prompt> +5. <trigger hidden api usage> +6. <press Ctrl-c to stop execution and read accumulated hidden api usage logs> + +Owner: Andrei Onea <andreionea@google.com> diff --git a/HiddenApiLoggingApp/src/com/android/hiddenapiapp/Receiver.java b/HiddenApiLoggingApp/src/com/android/hiddenapiapp/Receiver.java new file mode 100644 index 0000000..96e1172 --- /dev/null +++ b/HiddenApiLoggingApp/src/com/android/hiddenapiapp/Receiver.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.hiddenapiapp; + +import android.app.StatsManager; +import android.app.StatsManager.StatsUnavailableException; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.provider.DeviceConfig; +import android.util.Log; +import java.util.Base64; + +public class Receiver extends BroadcastReceiver { + public static final String SET_RATE = "com.android.hiddenapiapp.SET_RATE"; + public static final String SET_CONFIG = "com.android.hiddenapiapp.SET_CONFIG"; + public static final String GET_DATA = "com.android.hiddenapiapp.GET_DATA"; + public static final String REMOVE_CONFIG = "com.android.hiddenapiapp.REMOVE_CONFIG"; + + // ID of the config to be sent to statsd; this is an arbitrary value - but, obviously, + // every config must have its own id (can be assigned either client or server side, + // depending on use case) + private static final long CONFIG_ID = 54321; + private static final String TAG = "HiddenApiLogging"; + + @Override + public void onReceive(Context context, Intent intent) { + StatsManager statsManager = (StatsManager) context.getSystemService(StatsManager.class); + switch (intent.getAction()) { + case SET_RATE: + setLogRate(intent.getIntExtra("rate", 0)); + setResult(1, null, null); + break; + case SET_CONFIG: + String config = intent.getStringExtra("config"); + if (setupConfig(Base64.getDecoder().decode(config), statsManager)) { + setResult(1, null, null); + } else { + setResult(2, null, null); + } + case GET_DATA: + // This is for demo purposes only; in real applications, this data would be accumulated + // periodically in a buffer, and sent when the buffer fills up or otherwise convenient + setResult(1, Base64.getEncoder().encodeToString(getData(statsManager)), null); + break; + case REMOVE_CONFIG: + if (removeConfig(statsManager)) { + setResult(1, null, null); + } else { + setResult(2, null, null); + } + default: + setResult(2, null, null); + } + } + + private void setLogRate(int rate) { + DeviceConfig.setProperty( + "app_compat", + "hidden_api_access_statslog_sampling_rate", + new Integer(rate).toString(), + false); + } + + private boolean setupConfig(byte[] config, StatsManager statsManager) { + if (statsManager == null) { + return false; + } + try { + statsManager.addConfig(CONFIG_ID, config); + return true; + } catch (StatsUnavailableException e) { + Log.d(TAG, "Failed to send config to statsd"); + return false; + } + } + + private byte[] getData(StatsManager statsManager) { + if (statsManager == null) { + return null; + } + try { + return statsManager.getReports(CONFIG_ID); + } catch (StatsUnavailableException e) { + return null; + } + } + + private boolean removeConfig(StatsManager statsManager) { + if (statsManager == null) { + return false; + } + try { + statsManager.removeConfig(CONFIG_ID); + statsManager = null; + return true; + } catch (StatsUnavailableException e) { + Log.d(TAG, "Failed to remove config from statsd"); + return false; + } + } +} diff --git a/HiddenApiLoggingApp/tool/hidden_api_log_tracking.mf b/HiddenApiLoggingApp/tool/hidden_api_log_tracking.mf new file mode 100644 index 0000000..6249801 --- /dev/null +++ b/HiddenApiLoggingApp/tool/hidden_api_log_tracking.mf @@ -0,0 +1 @@ +Main-Class: com.hiddenapi.HiddenApiLogTracking diff --git a/HiddenApiLoggingApp/tool/src/com/hiddenapi/HiddenApiLogTracking.java b/HiddenApiLoggingApp/tool/src/com/hiddenapi/HiddenApiLogTracking.java new file mode 100644 index 0000000..75f0cd2 --- /dev/null +++ b/HiddenApiLoggingApp/tool/src/com/hiddenapi/HiddenApiLogTracking.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hiddenapi; + +import com.android.internal.os.StatsdConfigProto.AtomMatcher; +import com.android.internal.os.StatsdConfigProto.EventMetric; +import com.android.internal.os.StatsdConfigProto.SimpleAtomMatcher; +import com.android.internal.os.StatsdConfigProto.StatsdConfig; +import com.android.os.AtomsProto.Atom; +import com.android.os.StatsLog.ConfigMetricsReportList; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.Base64; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +// This tool represents an approximation of what a server would be doing +// - except the directionality of the flow of data is reversed at times; +// The device should send its data directly to the server when its internal +// buffers are full or when otherwise convenient (e.g. connecting to Wi-fi) +public class HiddenApiLogTracking { + private static String runOnDevice(String cmd) { + String[] finalCommand = ("adb shell " + cmd).split(" "); + System.out.println(String.join(" ", finalCommand)); + ProcessBuilder pb = new ProcessBuilder(finalCommand); + Process process; + try { + process = pb.start(); + } catch (IOException e) { + throw new RuntimeException("Could not start new process.", e); + } + StringBuilder output = new StringBuilder(); + try (BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()))) { + for (String line = br.readLine(); line != null; line = br.readLine()) { + output.append(line).append('\n'); + } + process.waitFor(); + return output.toString(); + } catch (Exception e) { + process.destroy(); + throw new RuntimeException("Failed to read command output", e); + } + } + + private static String createConfig() { + StatsdConfig.Builder builder = StatsdConfig.newBuilder().setId(54321); + final String atomName = "Atom" + System.nanoTime(); + final String eventName = "Event" + System.nanoTime(); + + SimpleAtomMatcher.Builder sam = + SimpleAtomMatcher.newBuilder().setAtomId(Atom.HIDDEN_API_USED_FIELD_NUMBER); + builder.addAtomMatcher( + AtomMatcher.newBuilder().setId(atomName.hashCode()).setSimpleAtomMatcher(sam)); + builder.addEventMetric( + EventMetric.newBuilder().setId(eventName.hashCode()).setWhat(atomName.hashCode())); + builder.addAllowedLogSource("AID_STATSD"); + return Base64.getEncoder().encodeToString(builder.build().toByteArray()); + } + + private static void processData(String dataAsString) { + dataAsString = dataAsString.substring(1, dataAsString.length() - 1); + byte[] data = Base64.getDecoder().decode(dataAsString); + try { + ConfigMetricsReportList cmrl = ConfigMetricsReportList.parser().parseFrom(data); + String output = + cmrl.getReportsList().stream() + .flatMap(report -> report.getMetricsList().stream()) + .flatMap(metric -> metric.getEventMetrics().getDataList().stream()) + .map(eventMetric -> eventMetric.getAtom()) + .map(atom -> atom.getHiddenApiUsed().getSignature()) + .collect(Collectors.joining("\n")); + System.out.println("Used hidden apis"); + System.out.println(output); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw new RuntimeException("Cannot parse protocol buffer.", e); + } + } + + public static void main(String[] args) { + System.out.println("Setting rate to max rate"); + runOnDevice( + "am broadcast -n com.android.hiddenapiapp/.Receiver " + + "-a com.android.hiddenapiapp.SET_RATE --ei rate 65535"); + System.out.println("Sending config"); + String config = createConfig(); + runOnDevice( + "am broadcast -n com.android.hiddenapiapp/.Receiver " + + "-a com.android.hiddenapiapp.SET_CONFIG --es config " + + config); + + System.out.println("Waiting for hidden api accesses... Press enter to read logs"); + + try { + System.in.read(); + } catch (IOException e) { + System.err.println("There was an IO error"); + e.printStackTrace(System.err); + System.err.println("Proceeding normally..."); + } + + String output = + runOnDevice( + "am broadcast -n com.android.hiddenapiapp/.Receiver " + + "-a com.android.hiddenapiapp.GET_DATA"); + Pattern p = Pattern.compile("\"([^\"]+)\""); + Matcher m = p.matcher(output); + if (m.find()) { + processData(m.group(0)); + } else { + System.out.println("No config received!"); + } + + runOnDevice( + "am broadcast -n com.android.hiddenapiapp/.Receiver " + + "-a com.android.hiddenapiapp.REMOVE_CONFIG"); + } +} |