diff options
9 files changed, 584 insertions, 202 deletions
diff --git a/services/core/java/com/android/server/selinux/QuotaLimiter.java b/services/core/java/com/android/server/selinux/QuotaLimiter.java index e89ddfd2627c..34d18cecf909 100644 --- a/services/core/java/com/android/server/selinux/QuotaLimiter.java +++ b/services/core/java/com/android/server/selinux/QuotaLimiter.java @@ -34,10 +34,10 @@ public class QuotaLimiter { private final Clock mClock; private final Duration mWindowSize; - private final int mMaxPermits; - private long mCurrentWindow = 0; - private int mPermitsGranted = 0; + private int mMaxPermits; + private long mCurrentWindow; + private int mPermitsGranted; @VisibleForTesting QuotaLimiter(Clock clock, Duration windowSize, int maxPermits) { @@ -75,4 +75,8 @@ public class QuotaLimiter { return false; } + + public void setMaxPermits(int maxPermits) { + this.mMaxPermits = maxPermits; + } } diff --git a/services/core/java/com/android/server/selinux/SelinuxAuditLogBuilder.java b/services/core/java/com/android/server/selinux/SelinuxAuditLogBuilder.java index 8d8d5960038e..d69150d88e4f 100644 --- a/services/core/java/com/android/server/selinux/SelinuxAuditLogBuilder.java +++ b/services/core/java/com/android/server/selinux/SelinuxAuditLogBuilder.java @@ -15,35 +15,66 @@ */ package com.android.server.selinux; +import android.provider.DeviceConfig; +import android.text.TextUtils; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; + import java.util.Arrays; import java.util.Iterator; import java.util.Optional; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; import java.util.stream.Stream; /** Builder for SelinuxAuditLogs. */ class SelinuxAuditLogBuilder { - // Currently logs collection is hardcoded for the sdk_sandbox_audit. - private static final String SDK_SANDBOX_AUDIT = "sdk_sandbox_audit"; - static final Matcher SCONTEXT_MATCHER = - Pattern.compile( - "u:r:(?<stype>" - + SDK_SANDBOX_AUDIT - + "):s0(:c)?(?<scategories>((,c)?\\d+)+)*") - .matcher(""); + private static final String TAG = "SelinuxAuditLogs"; - static final Matcher TCONTEXT_MATCHER = - Pattern.compile("u:object_r:(?<ttype>\\w+):s0(:c)?(?<tcategories>((,c)?\\d+)+)*") - .matcher(""); + // This config indicates which Selinux logs for source domains to collect. The string will be + // inserted into a regex, so it must follow the regex syntax. For example, a valid value would + // be "system_server|untrusted_app". + @VisibleForTesting static final String CONFIG_SELINUX_AUDIT_DOMAIN = "selinux_audit_domain"; + private static final Matcher NO_OP_MATCHER = Pattern.compile("no-op^").matcher(""); + private static final String TCONTEXT_PATTERN = + "u:object_r:(?<ttype>\\w+):s0(:c)?(?<tcategories>((,c)?\\d+)+)*"; + private static final String PATH_PATTERN = "\"(?<path>/\\w+(/\\w+)?)(/\\w+)*\""; - static final Matcher PATH_MATCHER = - Pattern.compile("\"(?<path>/\\w+(/\\w+)?)(/\\w+)*\"").matcher(""); + @VisibleForTesting final Matcher mScontextMatcher; + @VisibleForTesting final Matcher mTcontextMatcher; + @VisibleForTesting final Matcher mPathMatcher; private Iterator<String> mTokens; private final SelinuxAuditLog mAuditLog = new SelinuxAuditLog(); + SelinuxAuditLogBuilder() { + Matcher scontextMatcher = NO_OP_MATCHER; + Matcher tcontextMatcher = NO_OP_MATCHER; + Matcher pathMatcher = NO_OP_MATCHER; + try { + scontextMatcher = + Pattern.compile( + TextUtils.formatSimple( + "u:r:(?<stype>%s):s0(:c)?(?<scategories>((,c)?\\d+)+)*", + DeviceConfig.getString( + DeviceConfig.NAMESPACE_ADSERVICES, + CONFIG_SELINUX_AUDIT_DOMAIN, + "no_match^"))) + .matcher(""); + tcontextMatcher = Pattern.compile(TCONTEXT_PATTERN).matcher(""); + pathMatcher = Pattern.compile(PATH_PATTERN).matcher(""); + } catch (PatternSyntaxException e) { + Slog.e(TAG, "Invalid pattern, setting every matcher to no-op.", e); + } + + mScontextMatcher = scontextMatcher; + mTcontextMatcher = tcontextMatcher; + mPathMatcher = pathMatcher; + } + void reset(String denialString) { mTokens = Arrays.asList( @@ -82,18 +113,18 @@ class SelinuxAuditLogBuilder { mAuditLog.mPermissions = permissionsStream.build().toArray(String[]::new); break; case "scontext": - if (!nextTokenMatches(SCONTEXT_MATCHER)) { + if (!nextTokenMatches(mScontextMatcher)) { return null; } - mAuditLog.mSType = SCONTEXT_MATCHER.group("stype"); - mAuditLog.mSCategories = toCategories(SCONTEXT_MATCHER.group("scategories")); + mAuditLog.mSType = mScontextMatcher.group("stype"); + mAuditLog.mSCategories = toCategories(mScontextMatcher.group("scategories")); break; case "tcontext": - if (!nextTokenMatches(TCONTEXT_MATCHER)) { + if (!nextTokenMatches(mTcontextMatcher)) { return null; } - mAuditLog.mTType = TCONTEXT_MATCHER.group("ttype"); - mAuditLog.mTCategories = toCategories(TCONTEXT_MATCHER.group("tcategories")); + mAuditLog.mTType = mTcontextMatcher.group("ttype"); + mAuditLog.mTCategories = toCategories(mTcontextMatcher.group("tcategories")); break; case "tclass": if (!mTokens.hasNext()) { @@ -102,8 +133,8 @@ class SelinuxAuditLogBuilder { mAuditLog.mTClass = mTokens.next(); break; case "path": - if (nextTokenMatches(PATH_MATCHER)) { - mAuditLog.mPath = PATH_MATCHER.group("path"); + if (nextTokenMatches(mPathMatcher)) { + mAuditLog.mPath = mPathMatcher.group("path"); } break; case "permissive": diff --git a/services/core/java/com/android/server/selinux/SelinuxAuditLogsCollector.java b/services/core/java/com/android/server/selinux/SelinuxAuditLogsCollector.java index 03822aaf76b2..c655d46eb9f4 100644 --- a/services/core/java/com/android/server/selinux/SelinuxAuditLogsCollector.java +++ b/services/core/java/com/android/server/selinux/SelinuxAuditLogsCollector.java @@ -18,10 +18,12 @@ package com.android.server.selinux; import android.util.EventLog; import android.util.EventLog.Event; import android.util.Log; +import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.FrameworkStatsLog; import com.android.server.selinux.SelinuxAuditLogBuilder.SelinuxAuditLog; +import com.android.server.utils.Slogf; import java.io.IOException; import java.time.Instant; @@ -37,6 +39,7 @@ import java.util.regex.Pattern; class SelinuxAuditLogsCollector { private static final String TAG = "SelinuxAuditLogs"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static final String SELINUX_PATTERN = "^.*\\bavc:\\s+(?<denial>.*)$"; @@ -48,13 +51,17 @@ class SelinuxAuditLogsCollector { @VisibleForTesting Instant mLastWrite = Instant.MIN; - final AtomicBoolean mStopRequested = new AtomicBoolean(false); + AtomicBoolean mStopRequested = new AtomicBoolean(false); SelinuxAuditLogsCollector(RateLimiter rateLimiter, QuotaLimiter quotaLimiter) { mRateLimiter = rateLimiter; mQuotaLimiter = quotaLimiter; } + public void setStopRequested(boolean stopRequested) { + mStopRequested.set(stopRequested); + } + /** * Collect and push SELinux audit logs for the provided {@code tagCode}. * @@ -66,7 +73,7 @@ class SelinuxAuditLogsCollector { boolean quotaExceeded = writeAuditLogs(logLines); if (quotaExceeded) { - Log.w(TAG, "Too many SELinux logs in the queue, I am giving up."); + Slog.w(TAG, "Too many SELinux logs in the queue, I am giving up."); mLastWrite = latestTimestamp; // next run we will ignore all these logs. logLines.clear(); } @@ -79,7 +86,7 @@ class SelinuxAuditLogsCollector { try { EventLog.readEvents(new int[] {tagCode}, events); } catch (IOException e) { - Log.e(TAG, "Error reading event logs", e); + Slog.e(TAG, "Error reading event logs", e); } Instant latestTimestamp = mLastWrite; @@ -102,6 +109,7 @@ class SelinuxAuditLogsCollector { private boolean writeAuditLogs(Queue<Event> logLines) { final SelinuxAuditLogBuilder auditLogBuilder = new SelinuxAuditLogBuilder(); + int auditsWritten = 0; while (!mStopRequested.get() && !logLines.isEmpty()) { Event event = logLines.poll(); @@ -118,6 +126,9 @@ class SelinuxAuditLogsCollector { } if (!mQuotaLimiter.acquire()) { + if (DEBUG) { + Slogf.d(TAG, "Running out of quota after %d logs.", auditsWritten); + } return true; } mRateLimiter.acquire(); @@ -133,12 +144,16 @@ class SelinuxAuditLogsCollector { auditLog.mTClass, auditLog.mPath, auditLog.mPermissive); + auditsWritten++; if (logTime.isAfter(mLastWrite)) { mLastWrite = logTime; } } + if (DEBUG) { + Slogf.d(TAG, "Written %d logs", auditsWritten); + } return false; } } diff --git a/services/core/java/com/android/server/selinux/SelinuxAuditLogsJob.java b/services/core/java/com/android/server/selinux/SelinuxAuditLogsJob.java new file mode 100644 index 000000000000..0092c3797156 --- /dev/null +++ b/services/core/java/com/android/server/selinux/SelinuxAuditLogsJob.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.selinux; + +import android.app.job.JobParameters; +import android.app.job.JobService; +import android.util.Slog; + +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * This class handles the start and stop requests for the logs collector job, in particular making + * sure that at most one job is running at any given moment. + */ +final class SelinuxAuditLogsJob { + + private static final String TAG = "SelinuxAuditLogs"; + + private final AtomicBoolean mIsRunning = new AtomicBoolean(false); + private final SelinuxAuditLogsCollector mAuditLogsCollector; + + SelinuxAuditLogsJob(SelinuxAuditLogsCollector auditLogsCollector) { + mAuditLogsCollector = auditLogsCollector; + } + + void requestStop() { + mAuditLogsCollector.mStopRequested.set(true); + } + + boolean isRunning() { + return mIsRunning.get(); + } + + public void start(JobService jobService, JobParameters params) { + mAuditLogsCollector.mStopRequested.set(false); + if (mIsRunning.get()) { + Slog.i(TAG, "Selinux audit job is already running, ignore start request."); + return; + } + mIsRunning.set(true); + boolean done = mAuditLogsCollector.collect(SelinuxAuditLogsService.AUDITD_TAG_CODE); + if (done) { + jobService.jobFinished(params, /* wantsReschedule= */ false); + } + mIsRunning.set(false); + } +} diff --git a/services/core/java/com/android/server/selinux/SelinuxAuditLogsService.java b/services/core/java/com/android/server/selinux/SelinuxAuditLogsService.java index 8a661bcc13af..d46e8916d9e9 100644 --- a/services/core/java/com/android/server/selinux/SelinuxAuditLogsService.java +++ b/services/core/java/com/android/server/selinux/SelinuxAuditLogsService.java @@ -23,14 +23,16 @@ import android.app.job.JobScheduler; import android.app.job.JobService; import android.content.ComponentName; import android.content.Context; +import android.provider.DeviceConfig; +import android.provider.DeviceConfig.Properties; import android.util.EventLog; -import android.util.Log; +import android.util.Slog; import java.time.Duration; +import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; /** * Scheduled jobs related to logging of SELinux denials and audits. The job runs daily on idle @@ -43,58 +45,68 @@ public class SelinuxAuditLogsService extends JobService { static final int AUDITD_TAG_CODE = EventLog.getTagCode("auditd"); + private static final String CONFIG_SELINUX_AUDIT_JOB_FREQUENCY_HOURS = + "selinux_audit_job_frequency_hours"; + private static final String CONFIG_SELINUX_ENABLE_AUDIT_JOB = "selinux_enable_audit_job"; + private static final String CONFIG_SELINUX_AUDIT_CAP = "selinux_audit_cap"; + private static final int MAX_PERMITS_CAP_DEFAULT = 50000; + private static final int SELINUX_AUDIT_JOB_ID = 25327386; - private static final JobInfo SELINUX_AUDIT_JOB = - new JobInfo.Builder( - SELINUX_AUDIT_JOB_ID, - new ComponentName("android", SelinuxAuditLogsService.class.getName())) - .setPeriodic(TimeUnit.DAYS.toMillis(1)) - .setRequiresDeviceIdle(true) - .setRequiresCharging(true) - .setRequiresBatteryNotLow(true) - .build(); + private static final ComponentName SELINUX_AUDIT_JOB_COMPONENT = + new ComponentName("android", SelinuxAuditLogsService.class.getName()); private static final ExecutorService EXECUTOR_SERVICE = Executors.newSingleThreadExecutor(); - private static final AtomicReference<Boolean> IS_RUNNING = new AtomicReference<>(false); - // Audit logging is subject to both rate and quota limiting. We can only push one atom every 10 - // milliseconds, and no more than 50K atoms can be pushed each day. - private static final SelinuxAuditLogsCollector AUDIT_LOGS_COLLECTOR = - new SelinuxAuditLogsCollector( - new RateLimiter(/* window= */ Duration.ofMillis(10)), - new QuotaLimiter(/* maxPermitsPerDay= */ 50000)); + // Audit logging is subject to both rate and quota limiting. A {@link RateLimiter} makes sure + // that we push no more than one atom every 10 milliseconds. A {@link QuotaLimiter} caps the + // number of atoms pushed per day to CONFIG_SELINUX_AUDIT_CAP. The quota limiter is static + // because new job executions happen in a new instance of this class. Making the quota limiter + // an instance reference would reset the quota limitations between jobs executions. + private static final Duration RATE_LIMITER_WINDOW = Duration.ofMillis(10); + private static final QuotaLimiter QUOTA_LIMITER = + new QuotaLimiter( + DeviceConfig.getInt( + DeviceConfig.NAMESPACE_ADSERVICES, + CONFIG_SELINUX_AUDIT_CAP, + MAX_PERMITS_CAP_DEFAULT)); + private static final SelinuxAuditLogsJob LOGS_COLLECTOR_JOB = + new SelinuxAuditLogsJob( + new SelinuxAuditLogsCollector( + new RateLimiter(RATE_LIMITER_WINDOW), QUOTA_LIMITER)); /** Schedule jobs with the {@link JobScheduler}. */ public static void schedule(Context context) { if (!selinuxSdkSandboxAudit()) { - Log.d(TAG, "SelinuxAuditLogsService not enabled"); + Slog.d(TAG, "SelinuxAuditLogsService not enabled"); return; } if (AUDITD_TAG_CODE == -1) { - Log.e(TAG, "auditd is not a registered tag on this system"); + Slog.e(TAG, "auditd is not a registered tag on this system"); return; } - if (context.getSystemService(JobScheduler.class) - .forNamespace(SELINUX_AUDIT_NAMESPACE) - .schedule(SELINUX_AUDIT_JOB) - == JobScheduler.RESULT_FAILURE) { - Log.e(TAG, "SelinuxAuditLogsService could not be started."); - } + LogsCollectorJobScheduler propertiesListener = + new LogsCollectorJobScheduler( + context.getSystemService(JobScheduler.class) + .forNamespace(SELINUX_AUDIT_NAMESPACE)); + propertiesListener.schedule(); + DeviceConfig.addOnPropertiesChangedListener( + DeviceConfig.NAMESPACE_ADSERVICES, context.getMainExecutor(), propertiesListener); } @Override public boolean onStartJob(JobParameters params) { if (params.getJobId() != SELINUX_AUDIT_JOB_ID) { - Log.e(TAG, "The job id does not match the expected selinux job id."); + Slog.e(TAG, "The job id does not match the expected selinux job id."); + return false; + } + if (!selinuxSdkSandboxAudit()) { + Slog.i(TAG, "Selinux audit job disabled."); return false; } - AUDIT_LOGS_COLLECTOR.mStopRequested.set(false); - IS_RUNNING.set(true); - EXECUTOR_SERVICE.execute(new LogsCollectorJob(this, params)); - + EXECUTOR_SERVICE.execute(() -> LOGS_COLLECTOR_JOB.start(this, params)); return true; // the job is running } @@ -104,29 +116,69 @@ public class SelinuxAuditLogsService extends JobService { return false; } - AUDIT_LOGS_COLLECTOR.mStopRequested.set(true); - return IS_RUNNING.get(); + if (LOGS_COLLECTOR_JOB.isRunning()) { + LOGS_COLLECTOR_JOB.requestStop(); + return true; + } + return false; } - private static class LogsCollectorJob implements Runnable { - private final JobService mAuditLogService; - private final JobParameters mParams; + /** + * This class is in charge of scheduling the job service, and keeping the scheduling up to date + * when the parameters change. + */ + private static final class LogsCollectorJobScheduler + implements DeviceConfig.OnPropertiesChangedListener { + + private final JobScheduler mJobScheduler; - LogsCollectorJob(JobService auditLogService, JobParameters params) { - mAuditLogService = auditLogService; - mParams = params; + private LogsCollectorJobScheduler(JobScheduler jobScheduler) { + mJobScheduler = jobScheduler; } @Override - public void run() { - IS_RUNNING.updateAndGet( - isRunning -> { - boolean done = AUDIT_LOGS_COLLECTOR.collect(AUDITD_TAG_CODE); - if (done) { - mAuditLogService.jobFinished(mParams, /* wantsReschedule= */ false); - } - return !done; - }); + public void onPropertiesChanged(Properties changedProperties) { + Set<String> keyset = changedProperties.getKeyset(); + + if (keyset.contains(CONFIG_SELINUX_AUDIT_CAP)) { + QUOTA_LIMITER.setMaxPermits( + changedProperties.getInt( + CONFIG_SELINUX_AUDIT_CAP, MAX_PERMITS_CAP_DEFAULT)); + } + + if (keyset.contains(CONFIG_SELINUX_ENABLE_AUDIT_JOB)) { + boolean enabled = + changedProperties.getBoolean( + CONFIG_SELINUX_ENABLE_AUDIT_JOB, /* defaultValue= */ false); + if (enabled) { + schedule(); + } else { + mJobScheduler.cancel(SELINUX_AUDIT_JOB_ID); + } + } else if (keyset.contains(CONFIG_SELINUX_AUDIT_JOB_FREQUENCY_HOURS)) { + // The job frequency changed, reschedule. + schedule(); + } + } + + private void schedule() { + long frequencyMillis = + TimeUnit.HOURS.toMillis( + DeviceConfig.getInt( + DeviceConfig.NAMESPACE_ADSERVICES, + CONFIG_SELINUX_AUDIT_JOB_FREQUENCY_HOURS, + 24)); + if (mJobScheduler.schedule( + new JobInfo.Builder(SELINUX_AUDIT_JOB_ID, SELINUX_AUDIT_JOB_COMPONENT) + .setPeriodic(frequencyMillis) + .setRequiresDeviceIdle(true) + .setRequiresBatteryNotLow(true) + .build()) + == JobScheduler.RESULT_FAILURE) { + Slog.e(TAG, "SelinuxAuditLogsService could not be scheduled."); + } else { + Slog.d(TAG, "SelinuxAuditLogsService scheduled successfully."); + } } } } diff --git a/services/tests/selinux/Android.bp b/services/tests/selinux/Android.bp index f38723854d6b..12a70387affd 100644 --- a/services/tests/selinux/Android.bp +++ b/services/tests/selinux/Android.bp @@ -52,6 +52,7 @@ android_test { "androidx.test.ext.junit", "androidx.test.ext.truth", "androidx.test.runner", + "compatibility-device-util-axt", "services.core", ], test_suites: [ diff --git a/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsBuilderTest.java b/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsBuilderTest.java index b36c9bdaf456..e86108d84538 100644 --- a/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsBuilderTest.java +++ b/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsBuilderTest.java @@ -15,98 +15,144 @@ */ package com.android.server.selinux; -import static com.android.server.selinux.SelinuxAuditLogBuilder.PATH_MATCHER; -import static com.android.server.selinux.SelinuxAuditLogBuilder.SCONTEXT_MATCHER; -import static com.android.server.selinux.SelinuxAuditLogBuilder.TCONTEXT_MATCHER; +import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity; import static com.android.server.selinux.SelinuxAuditLogBuilder.toCategories; import static com.google.common.truth.Truth.assertThat; +import android.provider.DeviceConfig; + import androidx.test.ext.junit.runners.AndroidJUnit4; import com.android.server.selinux.SelinuxAuditLogBuilder.SelinuxAuditLog; +import org.junit.After; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.regex.Matcher; + @RunWith(AndroidJUnit4.class) public class SelinuxAuditLogsBuilderTest { - private final SelinuxAuditLogBuilder mAuditLogBuilder = new SelinuxAuditLogBuilder(); + private static final String TEST_DOMAIN = "test_domain"; + + private SelinuxAuditLogBuilder mAuditLogBuilder; + private Matcher mScontextMatcher; + private Matcher mTcontextMatcher; + private Matcher mPathMatcher; + + @Before + public void setUp() { + runWithShellPermissionIdentity( + () -> + DeviceConfig.setLocalOverride( + DeviceConfig.NAMESPACE_ADSERVICES, + SelinuxAuditLogBuilder.CONFIG_SELINUX_AUDIT_DOMAIN, + TEST_DOMAIN)); + + mAuditLogBuilder = new SelinuxAuditLogBuilder(); + mScontextMatcher = mAuditLogBuilder.mScontextMatcher; + mTcontextMatcher = mAuditLogBuilder.mTcontextMatcher; + mPathMatcher = mAuditLogBuilder.mPathMatcher; + } + + @After + public void tearDown() { + runWithShellPermissionIdentity(() -> DeviceConfig.clearAllLocalOverrides()); + } @Test public void testMatcher_scontext() { - assertThat(SCONTEXT_MATCHER.reset("u:r:sdk_sandbox_audit:s0").matches()).isTrue(); - assertThat(SCONTEXT_MATCHER.group("stype")).isEqualTo("sdk_sandbox_audit"); - assertThat(SCONTEXT_MATCHER.group("scategories")).isNull(); + assertThat(mScontextMatcher.reset("u:r:" + TEST_DOMAIN + ":s0").matches()).isTrue(); + assertThat(mScontextMatcher.group("stype")).isEqualTo(TEST_DOMAIN); + assertThat(mScontextMatcher.group("scategories")).isNull(); - assertThat(SCONTEXT_MATCHER.reset("u:r:sdk_sandbox_audit:s0:c123,c456").matches()).isTrue(); - assertThat(SCONTEXT_MATCHER.group("stype")).isEqualTo("sdk_sandbox_audit"); - assertThat(toCategories(SCONTEXT_MATCHER.group("scategories"))) + assertThat(mScontextMatcher.reset("u:r:" + TEST_DOMAIN + ":s0:c123,c456").matches()) + .isTrue(); + assertThat(mScontextMatcher.group("stype")).isEqualTo(TEST_DOMAIN); + assertThat(toCategories(mScontextMatcher.group("scategories"))) .isEqualTo(new int[] {123, 456}); - assertThat(SCONTEXT_MATCHER.reset("u:r:not_sdk_sandbox:s0").matches()).isFalse(); - assertThat(SCONTEXT_MATCHER.reset("u:object_r:sdk_sandbox_audit:s0").matches()).isFalse(); - assertThat(SCONTEXT_MATCHER.reset("u:r:sdk_sandbox_audit:s0:p123").matches()).isFalse(); + assertThat(mScontextMatcher.reset("u:r:wrong_domain:s0").matches()).isFalse(); + assertThat(mScontextMatcher.reset("u:object_r:" + TEST_DOMAIN + ":s0").matches()).isFalse(); + assertThat(mScontextMatcher.reset("u:r:" + TEST_DOMAIN + ":s0:p123").matches()).isFalse(); } @Test public void testMatcher_tcontext() { - assertThat(TCONTEXT_MATCHER.reset("u:object_r:target_type:s0").matches()).isTrue(); - assertThat(TCONTEXT_MATCHER.group("ttype")).isEqualTo("target_type"); - assertThat(TCONTEXT_MATCHER.group("tcategories")).isNull(); + assertThat(mTcontextMatcher.reset("u:object_r:target_type:s0").matches()).isTrue(); + assertThat(mTcontextMatcher.group("ttype")).isEqualTo("target_type"); + assertThat(mTcontextMatcher.group("tcategories")).isNull(); - assertThat(TCONTEXT_MATCHER.reset("u:object_r:target_type2:s0:c666").matches()).isTrue(); - assertThat(TCONTEXT_MATCHER.group("ttype")).isEqualTo("target_type2"); - assertThat(toCategories(TCONTEXT_MATCHER.group("tcategories"))).isEqualTo(new int[] {666}); + assertThat(mTcontextMatcher.reset("u:object_r:target_type2:s0:c666").matches()).isTrue(); + assertThat(mTcontextMatcher.group("ttype")).isEqualTo("target_type2"); + assertThat(toCategories(mTcontextMatcher.group("tcategories"))).isEqualTo(new int[] {666}); - assertThat(TCONTEXT_MATCHER.reset("u:r:target_type:s0").matches()).isFalse(); - assertThat(TCONTEXT_MATCHER.reset("u:r:sdk_sandbox_audit:s0:x456").matches()).isFalse(); + assertThat(mTcontextMatcher.reset("u:r:target_type:s0").matches()).isFalse(); + assertThat(mTcontextMatcher.reset("u:r:" + TEST_DOMAIN + ":s0:x456").matches()).isFalse(); } @Test public void testMatcher_path() { - assertThat(PATH_MATCHER.reset("\"/data\"").matches()).isTrue(); - assertThat(PATH_MATCHER.group("path")).isEqualTo("/data"); - assertThat(PATH_MATCHER.reset("\"/data/local\"").matches()).isTrue(); - assertThat(PATH_MATCHER.group("path")).isEqualTo("/data/local"); - assertThat(PATH_MATCHER.reset("\"/data/local/tmp\"").matches()).isTrue(); - assertThat(PATH_MATCHER.group("path")).isEqualTo("/data/local"); - - assertThat(PATH_MATCHER.reset("\"/data/local").matches()).isFalse(); - assertThat(PATH_MATCHER.reset("\"_data_local\"").matches()).isFalse(); + assertThat(mPathMatcher.reset("\"/data\"").matches()).isTrue(); + assertThat(mPathMatcher.group("path")).isEqualTo("/data"); + assertThat(mPathMatcher.reset("\"/data/local\"").matches()).isTrue(); + assertThat(mPathMatcher.group("path")).isEqualTo("/data/local"); + assertThat(mPathMatcher.reset("\"/data/local/tmp\"").matches()).isTrue(); + assertThat(mPathMatcher.group("path")).isEqualTo("/data/local"); + + assertThat(mPathMatcher.reset("\"/data/local").matches()).isFalse(); + assertThat(mPathMatcher.reset("\"_data_local\"").matches()).isFalse(); + } + + @Test + public void testMatcher_scontextDefaultConfig() { + runWithShellPermissionIdentity( + () -> + DeviceConfig.clearLocalOverride( + DeviceConfig.NAMESPACE_ADSERVICES, + SelinuxAuditLogBuilder.CONFIG_SELINUX_AUDIT_DOMAIN)); + + Matcher scontexMatcher = new SelinuxAuditLogBuilder().mScontextMatcher; + + assertThat(scontexMatcher.reset("u:r:" + TEST_DOMAIN + ":s0").matches()).isFalse(); + assertThat(scontexMatcher.reset("u:r:" + TEST_DOMAIN + ":s0:c123,c456").matches()) + .isFalse(); + assertThat(scontexMatcher.reset("u:r:wrong_domain:s0").matches()).isFalse(); } @Test public void testSelinuxAuditLogsBuilder_noOptionals() { mAuditLogBuilder.reset( - "granted { p } scontext=u:r:sdk_sandbox_audit:s0 tcontext=u:object_r:t:s0" + "granted { p } scontext=u:r:" + + TEST_DOMAIN + + ":s0 tcontext=u:object_r:t:s0" + " tclass=c"); - assertAuditLog( - mAuditLogBuilder.build(), true, new String[] {"p"}, "sdk_sandbox_audit", "t", "c"); + assertAuditLog(mAuditLogBuilder.build(), true, new String[] {"p"}, TEST_DOMAIN, "t", "c"); mAuditLogBuilder.reset( "tclass=c2 granted { p2 } tcontext=u:object_r:t2:s0" - + " scontext=u:r:sdk_sandbox_audit:s0"); + + " scontext=u:r:" + + TEST_DOMAIN + + ":s0"); assertAuditLog( - mAuditLogBuilder.build(), - true, - new String[] {"p2"}, - "sdk_sandbox_audit", - "t2", - "c2"); + mAuditLogBuilder.build(), true, new String[] {"p2"}, TEST_DOMAIN, "t2", "c2"); } @Test public void testSelinuxAuditLogsBuilder_withCategories() { mAuditLogBuilder.reset( - "granted { p } scontext=u:r:sdk_sandbox_audit:s0:c123" + "granted { p } scontext=u:r:" + + TEST_DOMAIN + + ":s0:c123" + " tcontext=u:object_r:t:s0:c456,c666 tclass=c"); assertAuditLog( mAuditLogBuilder.build(), true, new String[] {"p"}, - "sdk_sandbox_audit", + TEST_DOMAIN, new int[] {123}, "t", new int[] {456, 666}, @@ -118,13 +164,15 @@ public class SelinuxAuditLogsBuilderTest { @Test public void testSelinuxAuditLogsBuilder_withPath() { mAuditLogBuilder.reset( - "granted { p } scontext=u:r:sdk_sandbox_audit:s0 path=\"/very/long/path\"" + "granted { p } scontext=u:r:" + + TEST_DOMAIN + + ":s0 path=\"/very/long/path\"" + " tcontext=u:object_r:t:s0 tclass=c"); assertAuditLog( mAuditLogBuilder.build(), true, new String[] {"p"}, - "sdk_sandbox_audit", + TEST_DOMAIN, null, "t", null, @@ -136,13 +184,15 @@ public class SelinuxAuditLogsBuilderTest { @Test public void testSelinuxAuditLogsBuilder_withPermissive() { mAuditLogBuilder.reset( - "granted { p } scontext=u:r:sdk_sandbox_audit:s0 permissive=0" + "granted { p } scontext=u:r:" + + TEST_DOMAIN + + ":s0 permissive=0" + " tcontext=u:object_r:t:s0 tclass=c"); assertAuditLog( mAuditLogBuilder.build(), true, new String[] {"p"}, - "sdk_sandbox_audit", + TEST_DOMAIN, null, "t", null, @@ -151,13 +201,15 @@ public class SelinuxAuditLogsBuilderTest { false); mAuditLogBuilder.reset( - "granted { p } scontext=u:r:sdk_sandbox_audit:s0 tcontext=u:object_r:t:s0 tclass=c" + "granted { p } scontext=u:r:" + + TEST_DOMAIN + + ":s0 tcontext=u:object_r:t:s0 tclass=c" + " permissive=1"); assertAuditLog( mAuditLogBuilder.build(), true, new String[] {"p"}, - "sdk_sandbox_audit", + TEST_DOMAIN, null, "t", null, @@ -166,6 +218,40 @@ public class SelinuxAuditLogsBuilderTest { true); } + @Test + public void testSelinuxAuditLogsBuilder_wrongConfig() { + String notARegexDomain = "not]a[regex"; + runWithShellPermissionIdentity( + () -> + DeviceConfig.setLocalOverride( + DeviceConfig.NAMESPACE_ADSERVICES, + SelinuxAuditLogBuilder.CONFIG_SELINUX_AUDIT_DOMAIN, + notARegexDomain)); + SelinuxAuditLogBuilder noOpBuilder = new SelinuxAuditLogBuilder(); + + noOpBuilder.reset( + "granted { p } scontext=u:r:" + + TEST_DOMAIN + + ":s0 tcontext=u:object_r:t:s0 tclass=c"); + assertThat(noOpBuilder.build()).isNull(); + noOpBuilder.reset( + "granted { p } scontext=u:r:" + + TEST_DOMAIN + + ":s0:c123 tcontext=u:object_r:t:s0:c456,c666 tclass=c"); + assertThat(noOpBuilder.build()).isNull(); + noOpBuilder.reset( + "granted { p } scontext=u:r:" + + TEST_DOMAIN + + ":s0 path=\"/very/long/path\"" + + " tcontext=u:object_r:t:s0 tclass=c"); + assertThat(noOpBuilder.build()).isNull(); + noOpBuilder.reset( + "granted { p } scontext=u:r:" + + TEST_DOMAIN + + ":s0 permissive=0 tcontext=u:object_r:t:s0 tclass=c"); + assertThat(noOpBuilder.build()).isNull(); + } + private void assertAuditLog( SelinuxAuditLog auditLog, boolean granted, diff --git a/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsCollectorTest.java b/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsCollectorTest.java index 4a70ad38a76f..b6ccf5e0ad80 100644 --- a/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsCollectorTest.java +++ b/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsCollectorTest.java @@ -15,6 +15,7 @@ */ package com.android.server.selinux; +import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; @@ -27,6 +28,7 @@ import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; +import android.provider.DeviceConfig; import android.util.EventLog; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -50,6 +52,7 @@ public class SelinuxAuditLogsCollectorTest { // Fake tag to use for testing private static final int ANSWER_TAG = 42; + private static final String TEST_DOMAIN = "test_domain"; private final MockClock mClock = new MockClock(); @@ -64,6 +67,14 @@ public class SelinuxAuditLogsCollectorTest { @Before public void setUp() { + runWithShellPermissionIdentity( + () -> + DeviceConfig.setLocalOverride( + DeviceConfig.NAMESPACE_ADSERVICES, + SelinuxAuditLogBuilder.CONFIG_SELINUX_AUDIT_DOMAIN, + TEST_DOMAIN)); + + mSelinuxAutidLogsCollector.setStopRequested(false); // move the clock forward for the limiters. mClock.currentTimeMillis += Duration.ofHours(1).toMillis(); // Ignore what was written in the event logs by previous tests. @@ -74,13 +85,14 @@ public class SelinuxAuditLogsCollectorTest { @After public void tearDown() { + runWithShellPermissionIdentity(() -> DeviceConfig.clearAllLocalOverrides()); mMockitoSession.finishMocking(); } @Test - public void testWriteSdkSandboxAuditLogs() { - writeTestLog("granted", "perm", "sdk_sandbox_audit", "ttype", "tclass"); - writeTestLog("denied", "perm1", "sdk_sandbox_audit", "ttype1", "tclass1"); + public void testWriteAuditLogs() { + writeTestLog("granted", "perm", TEST_DOMAIN, "ttype", "tclass"); + writeTestLog("denied", "perm1", TEST_DOMAIN, "ttype1", "tclass1"); boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG); @@ -91,7 +103,7 @@ public class SelinuxAuditLogsCollectorTest { FrameworkStatsLog.SELINUX_AUDIT_LOG, true, new String[] {"perm"}, - "sdk_sandbox_audit", + TEST_DOMAIN, null, "ttype", null, @@ -104,7 +116,7 @@ public class SelinuxAuditLogsCollectorTest { FrameworkStatsLog.SELINUX_AUDIT_LOG, false, new String[] {"perm1"}, - "sdk_sandbox_audit", + TEST_DOMAIN, null, "ttype1", null, @@ -114,9 +126,9 @@ public class SelinuxAuditLogsCollectorTest { } @Test - public void testWriteSdkSandboxAuditLogs_multiplePerms() { - writeTestLog("denied", "perm1 perm2", "sdk_sandbox_audit", "ttype", "tclass"); - writeTestLog("denied", "perm3 perm4", "sdk_sandbox_audit", "ttype", "tclass"); + public void testWriteAuditLogs_multiplePerms() { + writeTestLog("denied", "perm1 perm2", TEST_DOMAIN, "ttype", "tclass"); + writeTestLog("denied", "perm3 perm4", TEST_DOMAIN, "ttype", "tclass"); boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG); @@ -127,7 +139,7 @@ public class SelinuxAuditLogsCollectorTest { FrameworkStatsLog.SELINUX_AUDIT_LOG, false, new String[] {"perm1", "perm2"}, - "sdk_sandbox_audit", + TEST_DOMAIN, null, "ttype", null, @@ -140,7 +152,7 @@ public class SelinuxAuditLogsCollectorTest { FrameworkStatsLog.SELINUX_AUDIT_LOG, false, new String[] {"perm3", "perm4"}, - "sdk_sandbox_audit", + TEST_DOMAIN, null, "ttype", null, @@ -150,11 +162,11 @@ public class SelinuxAuditLogsCollectorTest { } @Test - public void testWriteSdkSandboxAuditLogs_withPaths() { - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass", "/good/path"); - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass", "/very/long/path"); - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass", "/short_path"); - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass", "not_a_path"); + public void testWriteAuditLogs_withPaths() { + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass", "/good/path"); + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass", "/very/long/path"); + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass", "/short_path"); + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass", "not_a_path"); boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG); @@ -165,7 +177,7 @@ public class SelinuxAuditLogsCollectorTest { FrameworkStatsLog.SELINUX_AUDIT_LOG, false, new String[] {"perm"}, - "sdk_sandbox_audit", + TEST_DOMAIN, null, "ttype", null, @@ -178,7 +190,7 @@ public class SelinuxAuditLogsCollectorTest { FrameworkStatsLog.SELINUX_AUDIT_LOG, false, new String[] {"perm"}, - "sdk_sandbox_audit", + TEST_DOMAIN, null, "ttype", null, @@ -191,7 +203,7 @@ public class SelinuxAuditLogsCollectorTest { FrameworkStatsLog.SELINUX_AUDIT_LOG, false, new String[] {"perm"}, - "sdk_sandbox_audit", + TEST_DOMAIN, null, "ttype", null, @@ -204,7 +216,7 @@ public class SelinuxAuditLogsCollectorTest { FrameworkStatsLog.SELINUX_AUDIT_LOG, false, new String[] {"perm"}, - "sdk_sandbox_audit", + TEST_DOMAIN, null, "ttype", null, @@ -214,23 +226,14 @@ public class SelinuxAuditLogsCollectorTest { } @Test - public void testWriteSdkSandboxAuditLogs_withCategories() { - writeTestLog( - "denied", "perm", "sdk_sandbox_audit", new int[] {123}, "ttype", null, "tclass"); - writeTestLog( - "denied", - "perm", - "sdk_sandbox_audit", - new int[] {123, 456}, - "ttype", - null, - "tclass"); - writeTestLog( - "denied", "perm", "sdk_sandbox_audit", null, "ttype", new int[] {666}, "tclass"); + public void testWriteAuditLogs_withCategories() { + writeTestLog("denied", "perm", TEST_DOMAIN, new int[] {123}, "ttype", null, "tclass"); + writeTestLog("denied", "perm", TEST_DOMAIN, new int[] {123, 456}, "ttype", null, "tclass"); + writeTestLog("denied", "perm", TEST_DOMAIN, null, "ttype", new int[] {666}, "tclass"); writeTestLog( "denied", "perm", - "sdk_sandbox_audit", + TEST_DOMAIN, new int[] {123, 456}, "ttype", new int[] {666, 777}, @@ -245,7 +248,7 @@ public class SelinuxAuditLogsCollectorTest { FrameworkStatsLog.SELINUX_AUDIT_LOG, false, new String[] {"perm"}, - "sdk_sandbox_audit", + TEST_DOMAIN, new int[] {123}, "ttype", null, @@ -258,7 +261,7 @@ public class SelinuxAuditLogsCollectorTest { FrameworkStatsLog.SELINUX_AUDIT_LOG, false, new String[] {"perm"}, - "sdk_sandbox_audit", + TEST_DOMAIN, new int[] {123, 456}, "ttype", null, @@ -271,7 +274,7 @@ public class SelinuxAuditLogsCollectorTest { FrameworkStatsLog.SELINUX_AUDIT_LOG, false, new String[] {"perm"}, - "sdk_sandbox_audit", + TEST_DOMAIN, null, "ttype", new int[] {666}, @@ -284,7 +287,7 @@ public class SelinuxAuditLogsCollectorTest { FrameworkStatsLog.SELINUX_AUDIT_LOG, false, new String[] {"perm"}, - "sdk_sandbox_audit", + TEST_DOMAIN, new int[] {123, 456}, "ttype", new int[] {666, 777}, @@ -294,11 +297,11 @@ public class SelinuxAuditLogsCollectorTest { } @Test - public void testWriteSdkSandboxAuditLogs_withPathAndCategories() { + public void testWriteAuditLogs_withPathAndCategories() { writeTestLog( "denied", "perm", - "sdk_sandbox_audit", + TEST_DOMAIN, new int[] {123}, "ttype", new int[] {666}, @@ -314,7 +317,7 @@ public class SelinuxAuditLogsCollectorTest { FrameworkStatsLog.SELINUX_AUDIT_LOG, false, new String[] {"perm"}, - "sdk_sandbox_audit", + TEST_DOMAIN, new int[] {123}, "ttype", new int[] {666}, @@ -324,10 +327,10 @@ public class SelinuxAuditLogsCollectorTest { } @Test - public void testWriteSdkSandboxAuditLogs_permissive() { - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass"); - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass", true); - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass", false); + public void testWriteAuditLogs_permissive() { + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass"); + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass", true); + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass", false); boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG); @@ -338,7 +341,7 @@ public class SelinuxAuditLogsCollectorTest { FrameworkStatsLog.SELINUX_AUDIT_LOG, false, new String[] {"perm"}, - "sdk_sandbox_audit", + TEST_DOMAIN, null, "ttype", null, @@ -352,7 +355,7 @@ public class SelinuxAuditLogsCollectorTest { FrameworkStatsLog.SELINUX_AUDIT_LOG, false, new String[] {"perm"}, - "sdk_sandbox_audit", + TEST_DOMAIN, null, "ttype", null, @@ -362,7 +365,7 @@ public class SelinuxAuditLogsCollectorTest { } @Test - public void testNotWriteAuditLogs_notSdkSandbox() { + public void testNotWriteAuditLogs_notTestDomain() { writeTestLog("denied", "perm", "stype", "ttype", "tclass"); boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG); @@ -385,15 +388,15 @@ public class SelinuxAuditLogsCollectorTest { } @Test - public void testWriteSdkSandboxAuditLogs_upToQuota() { - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass"); - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass"); - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass"); - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass"); - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass"); + public void testWriteAuditLogs_upToQuota() { + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass"); + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass"); + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass"); + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass"); + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass"); // These are not pushed. - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass"); - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass"); + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass"); + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass"); boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG); @@ -415,14 +418,14 @@ public class SelinuxAuditLogsCollectorTest { } @Test - public void testWriteSdkSandboxAuditLogs_resetQuota() { - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass"); - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass"); - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass"); - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass"); - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass"); - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass"); - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass"); + public void testWriteAuditLogs_resetQuota() { + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass"); + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass"); + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass"); + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass"); + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass"); + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass"); + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass"); boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG); assertThat(done).isTrue(); @@ -441,11 +444,11 @@ public class SelinuxAuditLogsCollectorTest { anyBoolean()), times(5)); - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass"); - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass"); - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass"); - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass"); - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass"); + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass"); + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass"); + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass"); + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass"); + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass"); // move the clock forward to reset the quota limiter. mClock.currentTimeMillis += Duration.ofHours(1).toMillis(); done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG); @@ -468,16 +471,16 @@ public class SelinuxAuditLogsCollectorTest { @Test public void testNotWriteAuditLogs_stopRequested() { - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass"); - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass"); - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass"); - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass"); - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass"); + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass"); + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass"); + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass"); + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass"); + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass"); // These are not pushed. - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass"); - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass"); + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass"); + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass"); - mSelinuxAutidLogsCollector.mStopRequested.set(true); + mSelinuxAutidLogsCollector.setStopRequested(true); boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG); assertThat(done).isFalse(); verify( @@ -495,7 +498,7 @@ public class SelinuxAuditLogsCollectorTest { anyBoolean()), never()); - mSelinuxAutidLogsCollector.mStopRequested.set(false); + mSelinuxAutidLogsCollector.setStopRequested(false); done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG); assertThat(done).isTrue(); verify( @@ -516,8 +519,8 @@ public class SelinuxAuditLogsCollectorTest { @Test public void testAuditLogs_resumeJobDoesNotExceedLimit() { - writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass"); - mSelinuxAutidLogsCollector.mStopRequested.set(true); + writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass"); + mSelinuxAutidLogsCollector.setStopRequested(true); boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG); diff --git a/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsJobTest.java b/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsJobTest.java new file mode 100644 index 000000000000..2aea8a033f87 --- /dev/null +++ b/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsJobTest.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.selinux; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.job.JobParameters; +import android.app.job.JobService; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.concurrent.Semaphore; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +@RunWith(AndroidJUnit4.class) +public class SelinuxAuditLogsJobTest { + + private final JobService mJobService = mock(JobService.class); + private final SelinuxAuditLogsCollector mAuditLogsCollector = + mock(SelinuxAuditLogsCollector.class); + private final JobParameters mParams = createJobParameters(666); + private final SelinuxAuditLogsJob mAuditLogsJob = new SelinuxAuditLogsJob(mAuditLogsCollector); + + @Before + public void setUp() { + mAuditLogsCollector.mStopRequested = new AtomicBoolean(); + } + + @Test + public void testFinishSuccessfully() { + when(mAuditLogsCollector.collect(anyInt())).thenReturn(true); + + mAuditLogsJob.start(mJobService, mParams); + + verify(mJobService).jobFinished(mParams, /* wantsReschedule= */ false); + assertThat(mAuditLogsJob.isRunning()).isFalse(); + } + + @Test + public void testInterrupt() { + when(mAuditLogsCollector.collect(anyInt())).thenReturn(false); + + mAuditLogsJob.start(mJobService, mParams); + + verify(mJobService, never()).jobFinished(any(), anyBoolean()); + assertThat(mAuditLogsJob.isRunning()).isFalse(); + } + + @Test + public void testInterruptAndResume() { + when(mAuditLogsCollector.collect(anyInt())).thenReturn(false); + mAuditLogsJob.start(mJobService, mParams); + verify(mJobService, never()).jobFinished(any(), anyBoolean()); + + when(mAuditLogsCollector.collect(anyInt())).thenReturn(true); + mAuditLogsJob.start(mJobService, mParams); + verify(mJobService).jobFinished(mParams, /* wantsReschedule= */ false); + assertThat(mAuditLogsJob.isRunning()).isFalse(); + } + + @Test + public void testRequestStop() throws InterruptedException { + Semaphore isRunning = new Semaphore(0); + Semaphore stopRequested = new Semaphore(0); + AtomicReference<Throwable> uncaughtException = new AtomicReference<>(); + + // Set up a logs collector that runs in a worker thread until a stop is requested. + when(mAuditLogsCollector.collect(anyInt())) + .thenAnswer( + invocation -> { + assertThat(mAuditLogsCollector.mStopRequested.get()).isFalse(); + isRunning.release(); + stopRequested.acquire(); + assertThat(mAuditLogsCollector.mStopRequested.get()).isTrue(); + return true; + }); + Thread jobThread = + new Thread( + () -> { + mAuditLogsJob.start(mJobService, mParams); + }); + jobThread.setUncaughtExceptionHandler( + (thread, exception) -> uncaughtException.set(exception)); + assertThat(mAuditLogsJob.isRunning()).isFalse(); + jobThread.start(); + + // Wait until the worker thread is running. + isRunning.acquire(); + assertThat(mAuditLogsJob.isRunning()).isTrue(); + + // Request for the worker thread to stop, and wait to verify. + mAuditLogsJob.requestStop(); + stopRequested.release(); + jobThread.join(); + assertThat(uncaughtException.get()).isNull(); + assertThat(mAuditLogsJob.isRunning()).isFalse(); + } + + private static JobParameters createJobParameters(int jobId) { + JobParameters jobParameters = mock(JobParameters.class); + when(jobParameters.getJobId()).thenReturn(jobId); + return jobParameters; + } +} |