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 20 import android.app.job.JobInfo; 21 import android.app.job.JobParameters; 22 import android.app.job.JobScheduler; 23 import android.app.job.JobService; 24 import android.content.ComponentName; 25 import android.content.Context; 26 import android.provider.DeviceConfig; 27 import android.provider.DeviceConfig.Properties; 28 import android.util.EventLog; 29 import android.util.Slog; 30 31 import java.time.Duration; 32 import java.util.Set; 33 import java.util.concurrent.ExecutorService; 34 import java.util.concurrent.Executors; 35 import java.util.concurrent.TimeUnit; 36 37 /** 38 * Scheduled jobs related to logging of SELinux denials and audits. The job runs daily on idle 39 * devices. 40 */ 41 public class SelinuxAuditLogsService extends JobService { 42 43 private static final String TAG = "SelinuxAuditLogs"; 44 private static final String SELINUX_AUDIT_NAMESPACE = "SelinuxAuditLogsNamespace"; 45 46 static final int AUDITD_TAG_CODE = EventLog.getTagCode("auditd"); 47 48 private static final String CONFIG_SELINUX_AUDIT_JOB_FREQUENCY_HOURS = 49 "selinux_audit_job_frequency_hours"; 50 private static final String CONFIG_SELINUX_ENABLE_AUDIT_JOB = "selinux_enable_audit_job"; 51 private static final String CONFIG_SELINUX_AUDIT_CAP = "selinux_audit_cap"; 52 private static final int MAX_PERMITS_CAP_DEFAULT = 50000; 53 54 private static final int SELINUX_AUDIT_JOB_ID = 25327386; 55 private static final ComponentName SELINUX_AUDIT_JOB_COMPONENT = 56 new ComponentName("android", SelinuxAuditLogsService.class.getName()); 57 58 private static final ExecutorService EXECUTOR_SERVICE = Executors.newSingleThreadExecutor(); 59 60 // Audit logging is subject to both rate and quota limiting. A {@link RateLimiter} makes sure 61 // that we push no more than one atom every 10 milliseconds. A {@link QuotaLimiter} caps the 62 // number of atoms pushed per day to CONFIG_SELINUX_AUDIT_CAP. The quota limiter is static 63 // because new job executions happen in a new instance of this class. Making the quota limiter 64 // an instance reference would reset the quota limitations between jobs executions. 65 private static final Duration RATE_LIMITER_WINDOW = Duration.ofMillis(10); 66 private static final QuotaLimiter QUOTA_LIMITER = 67 new QuotaLimiter( 68 DeviceConfig.getInt( 69 DeviceConfig.NAMESPACE_ADSERVICES, 70 CONFIG_SELINUX_AUDIT_CAP, 71 MAX_PERMITS_CAP_DEFAULT)); 72 private static final SelinuxAuditLogsJob LOGS_COLLECTOR_JOB = 73 new SelinuxAuditLogsJob( 74 new SelinuxAuditLogsCollector( 75 new RateLimiter(RATE_LIMITER_WINDOW), QUOTA_LIMITER)); 76 77 /** Schedule jobs with the {@link JobScheduler}. */ schedule(Context context)78 public static void schedule(Context context) { 79 if (!selinuxSdkSandboxAudit()) { 80 Slog.d(TAG, "SelinuxAuditLogsService not enabled"); 81 return; 82 } 83 84 if (AUDITD_TAG_CODE == -1) { 85 Slog.e(TAG, "auditd is not a registered tag on this system"); 86 return; 87 } 88 89 LogsCollectorJobScheduler propertiesListener = 90 new LogsCollectorJobScheduler( 91 context.getSystemService(JobScheduler.class) 92 .forNamespace(SELINUX_AUDIT_NAMESPACE)); 93 propertiesListener.schedule(); 94 DeviceConfig.addOnPropertiesChangedListener( 95 DeviceConfig.NAMESPACE_ADSERVICES, context.getMainExecutor(), propertiesListener); 96 } 97 98 @Override onStartJob(JobParameters params)99 public boolean onStartJob(JobParameters params) { 100 if (params.getJobId() != SELINUX_AUDIT_JOB_ID) { 101 Slog.e(TAG, "The job id does not match the expected selinux job id."); 102 return false; 103 } 104 if (!selinuxSdkSandboxAudit()) { 105 Slog.i(TAG, "Selinux audit job disabled."); 106 return false; 107 } 108 109 EXECUTOR_SERVICE.execute(() -> LOGS_COLLECTOR_JOB.start(this, params)); 110 return true; // the job is running 111 } 112 113 @Override onStopJob(JobParameters params)114 public boolean onStopJob(JobParameters params) { 115 if (params.getJobId() != SELINUX_AUDIT_JOB_ID) { 116 return false; 117 } 118 119 if (LOGS_COLLECTOR_JOB.isRunning()) { 120 LOGS_COLLECTOR_JOB.requestStop(); 121 return true; 122 } 123 return false; 124 } 125 126 /** 127 * This class is in charge of scheduling the job service, and keeping the scheduling up to date 128 * when the parameters change. 129 */ 130 private static final class LogsCollectorJobScheduler 131 implements DeviceConfig.OnPropertiesChangedListener { 132 133 private final JobScheduler mJobScheduler; 134 LogsCollectorJobScheduler(JobScheduler jobScheduler)135 private LogsCollectorJobScheduler(JobScheduler jobScheduler) { 136 mJobScheduler = jobScheduler; 137 } 138 139 @Override onPropertiesChanged(Properties changedProperties)140 public void onPropertiesChanged(Properties changedProperties) { 141 Set<String> keyset = changedProperties.getKeyset(); 142 143 if (keyset.contains(CONFIG_SELINUX_AUDIT_CAP)) { 144 QUOTA_LIMITER.setMaxPermits( 145 changedProperties.getInt( 146 CONFIG_SELINUX_AUDIT_CAP, MAX_PERMITS_CAP_DEFAULT)); 147 } 148 149 if (keyset.contains(CONFIG_SELINUX_ENABLE_AUDIT_JOB)) { 150 boolean enabled = 151 changedProperties.getBoolean( 152 CONFIG_SELINUX_ENABLE_AUDIT_JOB, /* defaultValue= */ false); 153 if (enabled) { 154 schedule(); 155 } else { 156 mJobScheduler.cancel(SELINUX_AUDIT_JOB_ID); 157 } 158 } else if (keyset.contains(CONFIG_SELINUX_AUDIT_JOB_FREQUENCY_HOURS)) { 159 // The job frequency changed, reschedule. 160 schedule(); 161 } 162 } 163 schedule()164 private void schedule() { 165 long frequencyMillis = 166 TimeUnit.HOURS.toMillis( 167 DeviceConfig.getInt( 168 DeviceConfig.NAMESPACE_ADSERVICES, 169 CONFIG_SELINUX_AUDIT_JOB_FREQUENCY_HOURS, 170 24)); 171 if (mJobScheduler.schedule( 172 new JobInfo.Builder(SELINUX_AUDIT_JOB_ID, SELINUX_AUDIT_JOB_COMPONENT) 173 .setPeriodic(frequencyMillis) 174 .setRequiresDeviceIdle(true) 175 .setRequiresBatteryNotLow(true) 176 .build()) 177 == JobScheduler.RESULT_FAILURE) { 178 Slog.e(TAG, "SelinuxAuditLogsService could not be scheduled."); 179 } else { 180 Slog.d(TAG, "SelinuxAuditLogsService scheduled successfully."); 181 } 182 } 183 } 184 } 185