blob: 5b765dfee3a44162caa6755a55dcc339d0e8e1f1 [file] [log] [blame]
Alan Stokesbe9ec972018-12-10 14:07:47 +00001/*
2 * Copyright 2018 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
17package com.android.server.pm;
18
19import android.app.job.JobInfo;
20import android.app.job.JobParameters;
21import android.app.job.JobScheduler;
22import android.app.job.JobService;
23import android.content.ComponentName;
24import android.content.Context;
Alan Stokese9d33142018-12-19 09:31:21 +000025import android.os.Process;
Alan Stokesbe9ec972018-12-10 14:07:47 +000026import android.os.ServiceManager;
Alan Stokese9d33142018-12-19 09:31:21 +000027import android.util.ByteStringUtils;
28import android.util.EventLog;
Alan Stokesbe9ec972018-12-10 14:07:47 +000029import android.util.Log;
30
31import com.android.server.pm.dex.DexLogger;
32
Alan Stokese9d33142018-12-19 09:31:21 +000033import java.util.ArrayList;
34import java.util.List;
Alan Stokesbe9ec972018-12-10 14:07:47 +000035import java.util.concurrent.TimeUnit;
Alan Stokese9d33142018-12-19 09:31:21 +000036import java.util.regex.Matcher;
37import java.util.regex.Pattern;
Alan Stokesbe9ec972018-12-10 14:07:47 +000038
39/**
Alan Stokese9d33142018-12-19 09:31:21 +000040 * Scheduled jobs related to logging of app dynamic code loading. The idle logging job runs daily
41 * while idle and charging and calls {@link DexLogger} to write dynamic code information to the
42 * event log. The audit watching job scans the event log periodically while idle to find AVC audit
43 * messages indicating use of dynamic native code and adds the information to {@link DexLogger}.
Alan Stokesbe9ec972018-12-10 14:07:47 +000044 * {@hide}
45 */
46public class DynamicCodeLoggingService extends JobService {
47 private static final String TAG = DynamicCodeLoggingService.class.getName();
48
Alan Stokesbe9ec972018-12-10 14:07:47 +000049 private static final boolean DEBUG = false;
50
Alan Stokese9d33142018-12-19 09:31:21 +000051 private static final int IDLE_LOGGING_JOB_ID = 2030028;
52 private static final int AUDIT_WATCHING_JOB_ID = 203142925;
53
54 private static final long IDLE_LOGGING_PERIOD_MILLIS = TimeUnit.DAYS.toMillis(1);
55 private static final long AUDIT_WATCHING_PERIOD_MILLIS = TimeUnit.HOURS.toMillis(2);
56
57 private static final int AUDIT_AVC = 1400; // Defined in linux/audit.h
58 private static final String AVC_PREFIX = "type=" + AUDIT_AVC + " ";
59
60 private static final Pattern EXECUTE_NATIVE_AUDIT_PATTERN =
61 Pattern.compile(".*\\bavc: granted \\{ execute(?:_no_trans|) \\} .*"
62 + "\\bpath=(?:\"([^\" ]*)\"|([0-9A-F]+)) .*"
63 + "\\bscontext=u:r:untrusted_app_2(?:5|7):.*"
64 + "\\btcontext=u:object_r:app_data_file:.*"
65 + "\\btclass=file\\b.*");
66
67 private volatile boolean mIdleLoggingStopRequested = false;
68 private volatile boolean mAuditWatchingStopRequested = false;
69
Alan Stokesbe9ec972018-12-10 14:07:47 +000070 /**
Alan Stokese9d33142018-12-19 09:31:21 +000071 * Schedule our jobs with the {@link JobScheduler}.
Alan Stokesbe9ec972018-12-10 14:07:47 +000072 */
73 public static void schedule(Context context) {
74 ComponentName serviceName = new ComponentName(
75 "android", DynamicCodeLoggingService.class.getName());
76
77 JobScheduler js = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
Alan Stokese9d33142018-12-19 09:31:21 +000078 js.schedule(new JobInfo.Builder(IDLE_LOGGING_JOB_ID, serviceName)
Alan Stokesbe9ec972018-12-10 14:07:47 +000079 .setRequiresDeviceIdle(true)
80 .setRequiresCharging(true)
Alan Stokese9d33142018-12-19 09:31:21 +000081 .setPeriodic(IDLE_LOGGING_PERIOD_MILLIS)
Alan Stokesbe9ec972018-12-10 14:07:47 +000082 .build());
Alan Stokese9d33142018-12-19 09:31:21 +000083 js.schedule(new JobInfo.Builder(AUDIT_WATCHING_JOB_ID, serviceName)
84 .setRequiresDeviceIdle(true)
85 .setRequiresBatteryNotLow(true)
86 .setPeriodic(AUDIT_WATCHING_PERIOD_MILLIS)
87 .build());
88
Alan Stokesbe9ec972018-12-10 14:07:47 +000089 if (DEBUG) {
Alan Stokese9d33142018-12-19 09:31:21 +000090 Log.d(TAG, "Jobs scheduled");
Alan Stokesbe9ec972018-12-10 14:07:47 +000091 }
92 }
93
94 @Override
95 public boolean onStartJob(JobParameters params) {
Alan Stokese9d33142018-12-19 09:31:21 +000096 int jobId = params.getJobId();
Alan Stokesbe9ec972018-12-10 14:07:47 +000097 if (DEBUG) {
Alan Stokese9d33142018-12-19 09:31:21 +000098 Log.d(TAG, "onStartJob " + jobId);
Alan Stokesbe9ec972018-12-10 14:07:47 +000099 }
Alan Stokese9d33142018-12-19 09:31:21 +0000100 switch (jobId) {
101 case IDLE_LOGGING_JOB_ID:
102 mIdleLoggingStopRequested = false;
103 new IdleLoggingThread(params).start();
104 return true; // Job is running on another thread
105 case AUDIT_WATCHING_JOB_ID:
106 mAuditWatchingStopRequested = false;
107 new AuditWatchingThread(params).start();
108 return true; // Job is running on another thread
109 default:
110 // Shouldn't happen, but indicate nothing is running.
111 return false;
112 }
Alan Stokesbe9ec972018-12-10 14:07:47 +0000113 }
114
115 @Override
116 public boolean onStopJob(JobParameters params) {
Alan Stokese9d33142018-12-19 09:31:21 +0000117 int jobId = params.getJobId();
Alan Stokesbe9ec972018-12-10 14:07:47 +0000118 if (DEBUG) {
Alan Stokese9d33142018-12-19 09:31:21 +0000119 Log.d(TAG, "onStopJob " + jobId);
Alan Stokesbe9ec972018-12-10 14:07:47 +0000120 }
Alan Stokese9d33142018-12-19 09:31:21 +0000121 switch (jobId) {
122 case IDLE_LOGGING_JOB_ID:
123 mIdleLoggingStopRequested = true;
124 return true; // Requests job be re-scheduled.
125 case AUDIT_WATCHING_JOB_ID:
126 mAuditWatchingStopRequested = true;
127 return true; // Requests job be re-scheduled.
128 default:
129 return false;
130 }
131 }
132
133 private static DexLogger getDexLogger() {
134 PackageManagerService pm = (PackageManagerService) ServiceManager.getService("package");
135 return pm.getDexManager().getDexLogger();
Alan Stokesbe9ec972018-12-10 14:07:47 +0000136 }
137
138 private class IdleLoggingThread extends Thread {
139 private final JobParameters mParams;
140
141 IdleLoggingThread(JobParameters params) {
142 super("DynamicCodeLoggingService_IdleLoggingJob");
143 mParams = params;
144 }
145
146 @Override
147 public void run() {
148 if (DEBUG) {
Alan Stokese9d33142018-12-19 09:31:21 +0000149 Log.d(TAG, "Starting IdleLoggingJob run");
Alan Stokesbe9ec972018-12-10 14:07:47 +0000150 }
151
Alan Stokese9d33142018-12-19 09:31:21 +0000152 DexLogger dexLogger = getDexLogger();
Alan Stokesbe9ec972018-12-10 14:07:47 +0000153 for (String packageName : dexLogger.getAllPackagesWithDynamicCodeLoading()) {
Alan Stokese9d33142018-12-19 09:31:21 +0000154 if (mIdleLoggingStopRequested) {
155 Log.w(TAG, "Stopping IdleLoggingJob run at scheduler request");
Alan Stokesbe9ec972018-12-10 14:07:47 +0000156 return;
157 }
158
159 dexLogger.logDynamicCodeLoading(packageName);
160 }
161
162 jobFinished(mParams, /* reschedule */ false);
163 if (DEBUG) {
Alan Stokese9d33142018-12-19 09:31:21 +0000164 Log.d(TAG, "Finished IdleLoggingJob run");
Alan Stokesbe9ec972018-12-10 14:07:47 +0000165 }
166 }
167 }
Alan Stokese9d33142018-12-19 09:31:21 +0000168
169 private class AuditWatchingThread extends Thread {
170 private final JobParameters mParams;
171
172 AuditWatchingThread(JobParameters params) {
173 super("DynamicCodeLoggingService_AuditWatchingJob");
174 mParams = params;
175 }
176
177 @Override
178 public void run() {
179 if (DEBUG) {
180 Log.d(TAG, "Starting AuditWatchingJob run");
181 }
182
183 if (processAuditEvents()) {
184 jobFinished(mParams, /* reschedule */ false);
185 if (DEBUG) {
186 Log.d(TAG, "Finished AuditWatchingJob run");
187 }
188 }
189 }
190
191 private boolean processAuditEvents() {
192 // Scan the event log for SELinux (avc) audit messages indicating when an
193 // (untrusted) app has executed native code from an app data
194 // file. Matches are recorded in DexLogger.
195 //
196 // These messages come from the kernel audit system via logd. (Note that
197 // some devices may not generate these messages at all, or the format may
198 // be different, in which case nothing will be recorded.)
199 //
200 // The messages use the auditd tag and the uid of the app that executed
201 // the code.
202 //
203 // A typical message might look like this:
204 // type=1400 audit(0.0:521): avc: granted { execute } for comm="executable"
205 // path="/data/data/com.dummy.app/executable" dev="sda13" ino=1655302
206 // scontext=u:r:untrusted_app_27:s0:c66,c257,c512,c768
207 // tcontext=u:object_r:app_data_file:s0:c66,c257,c512,c768 tclass=file
208 //
209 // The information we want is the uid and the path. (Note this may be
210 // either a quoted string, as shown above, or a sequence of hex-encoded
211 // bytes.)
212 //
213 // On each run we process all the matching events in the log. This may
214 // mean re-processing events we have already seen, and in any case there
215 // may be duplicate events for the same app+file. These are de-duplicated
216 // by DexLogger.
217 //
218 // Note that any app can write a message to the event log, including one
219 // that looks exactly like an AVC audit message, so the information may
220 // be spoofed by an app; in such a case the uid we see will be the app
221 // that generated the spoof message.
222
223 try {
224 int[] tags = { EventLog.getTagCode("auditd") };
225 if (tags[0] == -1) {
226 // auditd is not a registered tag on this system, so there can't be any messages
227 // of interest.
228 return true;
229 }
230
231 DexLogger dexLogger = getDexLogger();
232
233 List<EventLog.Event> events = new ArrayList<>();
234 EventLog.readEvents(tags, events);
235
236 for (int i = 0; i < events.size(); ++i) {
237 if (mAuditWatchingStopRequested) {
238 Log.w(TAG, "Stopping AuditWatchingJob run at scheduler request");
239 return false;
240 }
241
242 EventLog.Event event = events.get(i);
243
244 // Discard clearly unrelated messages as quickly as we can.
245 int uid = event.getUid();
246 if (!Process.isApplicationUid(uid)) {
247 continue;
248 }
249 Object data = event.getData();
250 if (!(data instanceof String)) {
251 continue;
252 }
253 String message = (String) data;
254 if (!message.startsWith(AVC_PREFIX)) {
255 continue;
256 }
257
258 // And then use a regular expression to verify it's one of the messages we're
259 // interested in and to extract the path of the file being loaded.
260 Matcher matcher = EXECUTE_NATIVE_AUDIT_PATTERN.matcher(message);
261 if (!matcher.matches()) {
262 continue;
263 }
264 String path = matcher.group(1);
265 if (path == null) {
266 // If the path contains spaces or various weird characters the kernel
267 // hex-encodes the bytes; we need to undo that.
268 path = unhex(matcher.group(2));
269 }
270 dexLogger.recordNative(uid, path);
271 }
272
273 return true;
274 } catch (Exception e) {
275 Log.e(TAG, "AuditWatchingJob failed", e);
276 return true;
277 }
278 }
279 }
280
281 private static String unhex(String hexEncodedPath) {
282 byte[] bytes = ByteStringUtils.fromHexToByteArray(hexEncodedPath);
283 if (bytes == null || bytes.length == 0) {
284 return "";
285 }
286 return new String(bytes);
287 }
Alan Stokesbe9ec972018-12-10 14:07:47 +0000288}