• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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 package com.android.server.selinux;
17 
18 import static com.android.sdksandbox.flags.Flags.selinuxSdkSandboxAudit;
19 import static com.android.server.selinux.flags.Flags.selinuxLogsCollect;
20 
21 import android.app.job.JobInfo;
22 import android.app.job.JobParameters;
23 import android.app.job.JobScheduler;
24 import android.app.job.JobService;
25 import android.content.ComponentName;
26 import android.content.Context;
27 import android.provider.DeviceConfig;
28 import android.provider.DeviceConfig.Properties;
29 import android.util.EventLog;
30 import android.util.Slog;
31 
32 import java.time.Duration;
33 import java.util.Set;
34 import java.util.concurrent.ExecutorService;
35 import java.util.concurrent.Executors;
36 import java.util.concurrent.TimeUnit;
37 
38 /**
39  * Scheduled jobs related to logging of SELinux denials and audits. The job runs daily on idle
40  * devices.
41  */
42 public class SelinuxAuditLogsService extends JobService {
43 
44     private static final String TAG = "SelinuxAuditLogs";
45     private static final String SELINUX_AUDIT_NAMESPACE = "SelinuxAuditLogsNamespace";
46 
47     static final int AUDITD_TAG_CODE = EventLog.getTagCode("auditd");
48 
49     private static final String CONFIG_SELINUX_AUDIT_JOB_FREQUENCY_HOURS =
50             "selinux_audit_job_frequency_hours";
51     private static final String CONFIG_SELINUX_ENABLE_AUDIT_JOB = "selinux_enable_audit_job";
52     private static final String CONFIG_SELINUX_AUDIT_CAP = "selinux_audit_cap";
53     private static final String DEVICE_CONFIG_SECURITY_NAMESPACE = "security";
54     private static final String CONFIG_SECURITY_SELINUX_AUDIT_JOB_ENABLED =
55             "selinux_audit_job_enabled";
56     private static final int MAX_PERMITS_CAP_DEFAULT = 50000;
57 
58     private static final int SELINUX_AUDIT_JOB_ID = 25327386;
59     private static final ComponentName SELINUX_AUDIT_JOB_COMPONENT =
60             new ComponentName("android", SelinuxAuditLogsService.class.getName());
61 
62     private static final ExecutorService EXECUTOR_SERVICE = Executors.newSingleThreadExecutor();
63 
64     // Audit logging is subject to both rate and quota limiting. A {@link RateLimiter} makes sure
65     // that we push no more than one atom every 10 milliseconds. A {@link QuotaLimiter} caps the
66     // number of atoms pushed per day to CONFIG_SELINUX_AUDIT_CAP. The quota limiter is static
67     // because new job executions happen in a new instance of this class. Making the quota limiter
68     // an instance reference would reset the quota limitations between jobs executions.
69     private static final Duration RATE_LIMITER_WINDOW = Duration.ofMillis(10);
70     private static final QuotaLimiter QUOTA_LIMITER =
71             new QuotaLimiter(
72                     DeviceConfig.getInt(
73                             DeviceConfig.NAMESPACE_ADSERVICES,
74                             CONFIG_SELINUX_AUDIT_CAP,
75                             MAX_PERMITS_CAP_DEFAULT));
76     private static final SelinuxAuditLogsJob LOGS_COLLECTOR_JOB =
77             new SelinuxAuditLogsJob(
78                     new SelinuxAuditLogsCollector(
79                             new RateLimiter(RATE_LIMITER_WINDOW), QUOTA_LIMITER));
80 
81     /** Schedule jobs with the {@link JobScheduler}. */
schedule(Context context)82     public static void schedule(Context context) {
83         if (!selinuxSdkSandboxAudit() && !enabledForAllDomains()) {
84             Slog.d(TAG, "SelinuxAuditLogsService not enabled");
85             return;
86         }
87 
88         if (AUDITD_TAG_CODE == -1) {
89             Slog.e(TAG, "auditd is not a registered tag on this system");
90             return;
91         }
92 
93         LogsCollectorJobScheduler scheduler =
94                 new LogsCollectorJobScheduler(
95                         context.getSystemService(JobScheduler.class)
96                                 .forNamespace(SELINUX_AUDIT_NAMESPACE));
97         scheduler.schedule();
98 
99         AdServicesPropertyMonitor adServicesProperties = new AdServicesPropertyMonitor(scheduler);
100         DeviceConfig.addOnPropertiesChangedListener(
101                 DeviceConfig.NAMESPACE_ADSERVICES, context.getMainExecutor(), adServicesProperties);
102 
103         SecurityPropertyMonitor securityProperties = new SecurityPropertyMonitor(scheduler);
104         DeviceConfig.addOnPropertiesChangedListener(
105                 DEVICE_CONFIG_SECURITY_NAMESPACE, context.getMainExecutor(), securityProperties);
106 
107     }
108 
109     @Override
onStartJob(JobParameters params)110     public boolean onStartJob(JobParameters params) {
111         if (params.getJobId() != SELINUX_AUDIT_JOB_ID) {
112             Slog.e(TAG, "The job id does not match the expected selinux job id.");
113             return false;
114         }
115         if (!selinuxSdkSandboxAudit() && !enabledForAllDomains()) {
116             Slog.i(TAG, "Selinux audit job disabled.");
117             return false;
118         }
119 
120         EXECUTOR_SERVICE.execute(() -> LOGS_COLLECTOR_JOB.start(this, params));
121         return true; // the job is running
122     }
123 
124     @Override
onStopJob(JobParameters params)125     public boolean onStopJob(JobParameters params) {
126         if (params.getJobId() != SELINUX_AUDIT_JOB_ID) {
127             return false;
128         }
129 
130         if (LOGS_COLLECTOR_JOB.isRunning()) {
131             LOGS_COLLECTOR_JOB.requestStop();
132             return true;
133         }
134         return false;
135     }
136 
137     /** Checks if the service is enabled for all domains */
enabledForAllDomains()138     public static final boolean enabledForAllDomains() {
139         if (selinuxLogsCollect()) {
140             return DeviceConfig.getBoolean(
141                     DEVICE_CONFIG_SECURITY_NAMESPACE,
142                     CONFIG_SECURITY_SELINUX_AUDIT_JOB_ENABLED,
143                     false);
144         }
145         return false;
146     }
147 
148     /** Checks if the service is enabled for SDK Sandbox */
enabledForSdkSandbox()149     public static final boolean enabledForSdkSandbox() {
150         if (selinuxSdkSandboxAudit()) {
151             return DeviceConfig.getBoolean(
152                     DeviceConfig.NAMESPACE_ADSERVICES, CONFIG_SELINUX_ENABLE_AUDIT_JOB, false);
153         }
154         return false;
155     }
156 
157     private static final class AdServicesPropertyMonitor
158             implements DeviceConfig.OnPropertiesChangedListener {
159 
160         private final LogsCollectorJobScheduler mScheduler;
161 
AdServicesPropertyMonitor(LogsCollectorJobScheduler scheduler)162         private AdServicesPropertyMonitor(LogsCollectorJobScheduler scheduler) {
163             mScheduler = scheduler;
164         }
165 
166         @Override
onPropertiesChanged(Properties changedProperties)167         public void onPropertiesChanged(Properties changedProperties) {
168             Set<String> keyset = changedProperties.getKeyset();
169 
170             if (keyset.contains(CONFIG_SELINUX_AUDIT_CAP)) {
171                 QUOTA_LIMITER.setMaxPermits(
172                         changedProperties.getInt(
173                                 CONFIG_SELINUX_AUDIT_CAP, MAX_PERMITS_CAP_DEFAULT));
174             }
175 
176             if (keyset.contains(CONFIG_SELINUX_ENABLE_AUDIT_JOB)) {
177                 boolean enabled =
178                         changedProperties.getBoolean(
179                                 CONFIG_SELINUX_ENABLE_AUDIT_JOB, /* defaultValue= */ false)
180                         || enabledForAllDomains();
181                 if (enabled) {
182                     mScheduler.schedule();
183                 } else {
184                     mScheduler.cancel();
185                 }
186             } else if (keyset.contains(CONFIG_SELINUX_AUDIT_JOB_FREQUENCY_HOURS)) {
187                 // The job frequency changed, reschedule.
188                 mScheduler.schedule();
189             }
190         }
191     }
192 
193     private static final class SecurityPropertyMonitor
194             implements DeviceConfig.OnPropertiesChangedListener {
195 
196         private final LogsCollectorJobScheduler mScheduler;
197 
SecurityPropertyMonitor(LogsCollectorJobScheduler scheduler)198         private SecurityPropertyMonitor(LogsCollectorJobScheduler scheduler) {
199             mScheduler = scheduler;
200         }
201 
202         @Override
onPropertiesChanged(Properties changedProperties)203         public void onPropertiesChanged(Properties changedProperties) {
204             Set<String> keyset = changedProperties.getKeyset();
205 
206             if (keyset.contains(CONFIG_SECURITY_SELINUX_AUDIT_JOB_ENABLED)) {
207                 boolean enabled =
208                         changedProperties.getBoolean(
209                                 CONFIG_SECURITY_SELINUX_AUDIT_JOB_ENABLED,
210                                 /* defaultValue= */ false)
211                         || enabledForSdkSandbox();
212                 if (enabled) {
213                     mScheduler.schedule();
214                 } else {
215                     mScheduler.cancel();
216                 }
217             }
218         }
219     }
220 
221     /**
222      * This class is in charge of scheduling the job service, and keeping the scheduling up to date
223      * when the parameters change.
224      */
225     private static final class LogsCollectorJobScheduler {
226 
227         private final JobScheduler mJobScheduler;
228 
LogsCollectorJobScheduler(JobScheduler jobScheduler)229         private LogsCollectorJobScheduler(JobScheduler jobScheduler) {
230             mJobScheduler = jobScheduler;
231         }
232 
cancel()233         public void cancel() {
234             mJobScheduler.cancel(SELINUX_AUDIT_JOB_ID);
235         }
236 
schedule()237         public void schedule() {
238             long frequencyMillis =
239                     TimeUnit.HOURS.toMillis(
240                             DeviceConfig.getInt(
241                                     DeviceConfig.NAMESPACE_ADSERVICES,
242                                     CONFIG_SELINUX_AUDIT_JOB_FREQUENCY_HOURS,
243                                     24));
244             if (mJobScheduler.schedule(
245                             new JobInfo.Builder(SELINUX_AUDIT_JOB_ID, SELINUX_AUDIT_JOB_COMPONENT)
246                                     .setPeriodic(frequencyMillis)
247                                     .setRequiresDeviceIdle(true)
248                                     .setRequiresBatteryNotLow(true)
249                                     .build())
250                     == JobScheduler.RESULT_FAILURE) {
251                 Slog.e(TAG, "SelinuxAuditLogsService could not be scheduled.");
252             } else {
253                 Slog.d(TAG, "SelinuxAuditLogsService scheduled successfully.");
254             }
255         }
256     }
257 }
258