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