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