forked from jens/Automation
244 lines
8.0 KiB
Java
244 lines
8.0 KiB
Java
/*
|
|
* Copyright (C) 2012-2019 Jorrit "Chainfire" Jongma
|
|
*
|
|
* 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 eu.chainfire.libsuperuser;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
|
|
import androidx.annotation.AnyThread;
|
|
import androidx.annotation.NonNull;
|
|
import androidx.annotation.Nullable;
|
|
import androidx.annotation.WorkerThread;
|
|
|
|
/**
|
|
* Helper class for modifying SELinux policies, reducing the number of calls to a minimum.
|
|
*
|
|
* Example usage:
|
|
*
|
|
* <pre>
|
|
* <code>
|
|
*
|
|
* private class Policy extends eu.chainfire.libsuperuser.Policy {
|
|
* {@literal @}Override protected String[] getPolicies() {
|
|
* return new String[] {
|
|
* "allow sdcardd unlabeled dir { append create execute write relabelfrom link unlink ioctl getattr setattr read rename lock mounton quotaon swapon rmdir audit_access remove_name add_name reparent execmod search open }",
|
|
* "allow sdcardd unlabeled file { append create write relabelfrom link unlink ioctl getattr setattr read rename lock mounton quotaon swapon audit_access open }",
|
|
* "allow unlabeled unlabeled filesystem associate"
|
|
* };
|
|
* }
|
|
* };
|
|
* private Policy policy = new Policy();
|
|
*
|
|
* public void someFunctionNotCalledOnMainThread() {
|
|
* policy.inject();
|
|
* }
|
|
*
|
|
* </code>
|
|
* </pre>
|
|
*/
|
|
@SuppressWarnings({"WeakerAccess", "UnusedReturnValue", "unused"})
|
|
public abstract class Policy {
|
|
/**
|
|
* supolicy should be called as little as possible. We batch policies together. The command
|
|
* line is guaranteed to be able to take 4096 characters. Reduce by a bit for supolicy itself.
|
|
*/
|
|
private static final int MAX_POLICY_LENGTH = 4096 - 32;
|
|
|
|
private static final Object synchronizer = new Object();
|
|
@Nullable
|
|
private static volatile Boolean canInject = null;
|
|
private static volatile boolean injected = false;
|
|
|
|
/**
|
|
* @return Have we injected our policies already?
|
|
*/
|
|
@AnyThread
|
|
public static boolean haveInjected() {
|
|
return injected;
|
|
}
|
|
|
|
/**
|
|
* Reset policies-have-been-injected state, if you really need to inject them again. Extremely
|
|
* rare, you will probably never need this.
|
|
*/
|
|
@AnyThread
|
|
public static void resetInjected() {
|
|
synchronized (synchronizer) {
|
|
injected = false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Override this method to return a array of strings containing the policies you want to inject.
|
|
*
|
|
* @return Policies to inject
|
|
*/
|
|
@NonNull
|
|
protected abstract String[] getPolicies();
|
|
|
|
/**
|
|
* Detects availability of the supolicy tool. Only useful if Shell.SU.isSELinuxEnforcing()
|
|
* returns true. Caches return value, can safely be called from the UI thread <b>if</b> a
|
|
* a cached value exists.
|
|
*
|
|
* @see #resetCanInject()
|
|
*
|
|
* @return canInject?
|
|
*/
|
|
@SuppressWarnings({"deprecation", "ConstantConditions"})
|
|
@WorkerThread // first call only
|
|
public static boolean canInject() {
|
|
synchronized (synchronizer) {
|
|
if (canInject != null) return canInject;
|
|
|
|
canInject = false;
|
|
|
|
// We are making the assumption here that if supolicy is called without parameters,
|
|
// it will return output (such as a usage notice) on STDOUT (not STDERR) that contains
|
|
// at least the word "supolicy". This is true at least for SuperSU.
|
|
|
|
List<String> result = Shell.run("sh", new String[] { "supolicy" }, null, false);
|
|
if (result != null) {
|
|
for (String line : result) {
|
|
if (line.contains("supolicy")) {
|
|
canInject = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return canInject;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Reset cached can-inject state and force redetection on nect canInject() call
|
|
*/
|
|
@AnyThread
|
|
public static void resetCanInject() {
|
|
synchronized (synchronizer) {
|
|
canInject = null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Transform the policies defined by getPolicies() into a set of shell commands
|
|
*
|
|
* @return Possibly empty List of commands, or null
|
|
*/
|
|
@Nullable
|
|
protected List<String> getInjectCommands() {
|
|
return getInjectCommands(true);
|
|
}
|
|
|
|
/**
|
|
* Transform the policies defined by getPolicies() into a set of shell commands
|
|
*
|
|
* @param allowBlocking allow method to perform blocking I/O for extra checks
|
|
* @return Possibly empty List of commands, or null
|
|
*/
|
|
@Nullable
|
|
@SuppressWarnings("all")
|
|
@WorkerThread // if allowBlocking
|
|
protected List<String> getInjectCommands(boolean allowBlocking) {
|
|
synchronized (synchronizer) {
|
|
// No reason to bother if we're in permissive mode
|
|
if (!Shell.SU.isSELinuxEnforcing()) return null;
|
|
|
|
// If we can't inject, no use continuing
|
|
if (allowBlocking && !canInject()) return null;
|
|
|
|
// Been there, done that
|
|
if (injected) return null;
|
|
|
|
// Retrieve policies
|
|
String[] policies = getPolicies();
|
|
if ((policies != null) && (policies.length > 0)) {
|
|
List<String> commands = new ArrayList<String>();
|
|
|
|
// Combine the policies into a minimal number of commands
|
|
String command = "";
|
|
for (String policy : policies) {
|
|
if ((command.length() == 0) || (command.length() + policy.length() + 3 < MAX_POLICY_LENGTH)) {
|
|
command = command + " \"" + policy + "\"";
|
|
} else {
|
|
commands.add("supolicy --live" + command);
|
|
command = "";
|
|
}
|
|
}
|
|
if (command.length() > 0) {
|
|
commands.add("supolicy --live" + command);
|
|
}
|
|
|
|
return commands;
|
|
}
|
|
|
|
// No policies
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Inject the policies defined by getPolicies(). Throws an exception if called from
|
|
* the main thread in debug mode.
|
|
*/
|
|
@SuppressWarnings({"deprecation"})
|
|
@WorkerThread
|
|
public void inject() {
|
|
synchronized (synchronizer) {
|
|
// Get commands that inject our policies
|
|
List<String> commands = getInjectCommands();
|
|
|
|
// Execute them, if any
|
|
if ((commands != null) && (commands.size() > 0)) {
|
|
Shell.SU.run(commands);
|
|
}
|
|
|
|
// We survived without throwing
|
|
injected = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Inject the policies defined by getPolicies(). Throws an exception if called from
|
|
* the main thread in debug mode if waitForIdle is true. If waitForIdle is false
|
|
* however, it cannot be guaranteed the command was executed and the policies injected
|
|
* upon return.
|
|
*
|
|
* @param shell Interactive shell to execute commands on
|
|
* @param waitForIdle wait for the command to complete before returning?
|
|
*/
|
|
@WorkerThread // if waitForIdle
|
|
public void inject(@NonNull Shell.Interactive shell, boolean waitForIdle) {
|
|
synchronized (synchronizer) {
|
|
// Get commands that inject our policies
|
|
List<String> commands = getInjectCommands(waitForIdle);
|
|
|
|
// Execute them, if any
|
|
if ((commands != null) && (commands.size() > 0)) {
|
|
shell.addCommand(commands);
|
|
if (waitForIdle) {
|
|
shell.waitForIdle();
|
|
}
|
|
}
|
|
|
|
// We survived without throwing
|
|
injected = true;
|
|
}
|
|
}
|
|
}
|