1
0
mirror of https://github.com/termux/termux-app synced 2024-06-17 14:47:08 +00:00

Added|Fixed: Added the option to specify the binding priority for callback services, fixed callback services, fixed plugin UID accidentally being set to the PID.

This commit is contained in:
tareksander 2022-08-05 14:59:35 +02:00
parent f459ee481e
commit b1fe382dff
4 changed files with 105 additions and 36 deletions

View File

@ -30,6 +30,7 @@
<permission
android:name="${TERMUX_PACKAGE_NAME}.permission.TERMUX_SIGNATURE"
android:protectionLevel="signature" />
<uses-permission android:name="${TERMUX_PACKAGE_NAME}.permission.TERMUX_SIGNATURE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />

View File

@ -14,7 +14,6 @@ import android.os.NetworkOnMainThreadException;
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.RemoteException;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@ -97,7 +96,8 @@ public class PluginService extends Service
Map<Integer, NativeShell> tasks = Collections.synchronizedMap(new HashMap<>());
@NonNull IPluginCallback callback;
int cachedCallbackVersion;
final int cachedCallbackVersion;
ServiceConnection con = null;
Plugin(int pid, int uid, @NonNull IPluginCallback callback) throws RemoteException {
this.pid = pid;
@ -106,6 +106,11 @@ public class PluginService extends Service
callback.asBinder().linkToDeath(() -> mConnectedPlugins.remove(pid), 0); // remove self when the callback binder dies
cachedCallbackVersion = callback.getCallbackVersion();
}
Plugin(int pid, int uid, @NonNull IPluginCallback callback, ServiceConnection con) throws RemoteException {
this(pid, uid, callback);
this.con = con;
}
@Override
@ -132,6 +137,13 @@ public class PluginService extends Service
@Override
public void onDestroy() {
unbindService(mTermuxServiceConnection);
for (Map.Entry<Integer, Plugin> e : mConnectedPlugins.entrySet()) {
if (e.getValue().con != null) {
try {
unbindService(e.getValue().con);
} catch (IllegalArgumentException ignored) {}
}
}
}
@ -149,7 +161,7 @@ public class PluginService extends Service
* If not already, this creates an entry in the map of connected plugins for the current Binder client.
*/
private void addClient(@NonNull IPluginCallback callback) {
int pid = Binder.getCallingPid(), uid = Binder.getCallingPid();
int pid = Binder.getCallingPid(), uid = Binder.getCallingUid();
if (pid == Process.myPid()) return; // no client connected
if (mConnectedPlugins.get(pid) != null) return; // client already in list
try {
@ -157,6 +169,16 @@ public class PluginService extends Service
} catch (RemoteException ignored) {} // RemoteException is thrown if the callback binder is already dead, plugin isn't added to the list
}
/**
* If not already, this creates an entry in the map of connected plugins for the current Binder client.
*/
private void addClientWithCallbackService(@NonNull IPluginCallback callback, int pid, int uid, @NonNull ServiceConnection con) {
if (mConnectedPlugins.get(pid) != null) return; // client already in list
try {
mConnectedPlugins.put(pid, new Plugin(pid, uid, callback, con));
} catch (RemoteException ignored) {} // RemoteException is thrown if the callback binder is already dead, plugin isn't added to the list
}
@NonNull
private Plugin checkClient() throws IllegalStateException {
Plugin p = mConnectedPlugins.get(Binder.getCallingPid());
@ -212,69 +234,86 @@ public class PluginService extends Service
public void setCallbackBinder(IPluginCallback callback) {
externalAppsOrThrow();
if (callback == null) throw new NullPointerException("Passed callback binder is null");
if (mConnectedPlugins.get(Binder.getCallingPid()) != null) {
throw new IllegalStateException("Callback binder already set (there is only one global connection to the plugin service, all new ones use the already initialized one)");
}
addClient(callback);
}
@Override
public void setCallbackService(String componentNameString) {
public void setCallbackService(String componentNameString, int priority) {
externalAppsOrThrow();
if (componentNameString == null) throw new NullPointerException("Passed componentName is null");
String callerPackageName = BinderUtils.getCallerPackageNameOrNull(PluginService.this);
if (callerPackageName == null) throw new NullPointerException("Caller package is null");
if (priority != PRIORITY_MIN &&
priority != PRIORITY_NORMAL &&
priority != PRIORITY_IMPORTANT &&
priority != PRIORITY_MAX)
throw new IllegalArgumentException("Invalid priority parameter");
if (mConnectedPlugins.get(Binder.getCallingPid()) != null) {
throw new IllegalStateException("Callback binder already set (there is only one global connection to the plugin service, all new ones use the already initialized one)");
}
ComponentName componentName = ComponentName.createRelative(callerPackageName, componentNameString);
Intent callbackStartIntent = new Intent();
callbackStartIntent.setComponent(componentName);
final boolean[] bindingFinished = {false};
final IBinder[] callbackBinder = new IBinder[] {null};
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
ServiceConnection con = new ServiceConnection()
{
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
callbackBinder[0] = service;
bindingFinished[0] = true;
synchronized (callbackBinder) {
callbackBinder.notifyAll();
}
addClientWithCallbackService(IPluginCallback.Stub.asInterface(service), pid, uid, this);
}
@Override
public void onServiceDisconnected(ComponentName name) {
unbindService(this);
try {
PluginService.this.unbindService(this);
} catch (IllegalArgumentException ignored) {}
}
@Override
public void onBindingDied(ComponentName name) {
unbindService(this);
try {
PluginService.this.unbindService(this);
} catch (IllegalArgumentException ignored) {}
}
@Override
public void onNullBinding(ComponentName name) {
bindingFinished[0] = true;
synchronized (callbackBinder) {
callbackBinder.notifyAll();
}
unbindService(this);
Logger.logDebug("Null binding for callback service");
try {
PluginService.this.unbindService(this);
} catch (IllegalArgumentException ignored) {}
}
};
PluginService.this.bindService(callbackStartIntent, con, Context.BIND_ALLOW_OOM_MANAGEMENT);
while (! bindingFinished[0]) {
try {
synchronized (callbackBinder) {
callbackBinder.wait();
}
try {
boolean ret;
switch (priority) {
case PRIORITY_MIN:
ret = PluginService.this.bindService(callbackStartIntent, con, Context.BIND_WAIVE_PRIORITY | Context.BIND_AUTO_CREATE);
break;
case PRIORITY_IMPORTANT:
ret = PluginService.this.bindService(callbackStartIntent, con, Context.BIND_IMPORTANT | Context.BIND_AUTO_CREATE);
break;
case PRIORITY_MAX:
ret = PluginService.this.bindService(callbackStartIntent, con, Context.BIND_ABOVE_CLIENT | Context.BIND_AUTO_CREATE);
break;
case PRIORITY_NORMAL:
default:
ret = PluginService.this.bindService(callbackStartIntent, con, Context.BIND_AUTO_CREATE);
}
catch (InterruptedException ignored) {}
if (! ret) {
throw new IllegalStateException("Cannot start service");
}
} catch (SecurityException e) {
throw new IllegalArgumentException("Service not found or requires permissions");
}
if (callbackBinder[0] == null) {
throw new IllegalArgumentException("Could not bind callback service: "+componentNameString);
}
addClient(IPluginCallback.Stub.asInterface(callbackBinder[0]));
}

View File

@ -27,6 +27,7 @@ interface IPluginCallback {
/**
* This gets called when a connection is made on a socket created with {@link com.termux.plugin_aidl.IPluginService#listenOnSocketFile}.
* <br>Added in version 1.
*
* @param sockname The name of socket file the connection was made on (the relative path to the plugin directory).
* @param connection The connection file descriptor.
@ -36,6 +37,7 @@ interface IPluginCallback {
/**
* Gets called when a started Task exits.
* <br>Added in version 1.
*
* @param pid The pid of the task that exited.
* @param code The exit code of the Task. Negative values indicate the Task was killed by a signal. The signal number is then {@code -code}.

View File

@ -24,6 +24,30 @@ import com.termux.plugin_aidl.Task;
*/
interface IPluginService {
/**
* Binds the service with @{link android.content.Context#BIND_WAIVE_PRIORITY}, meaning the plugins needs other components for the system not to kill it.
*/
const int PRIORITY_MIN = 0;
/**
* Uses no flags to bind the service.
*/
const int PRIORITY_NORMAL = 1;
/**
* Binds the service with @{link android.content.Context#BIND_IMPORTANT}, which should give the plugin the same priority as Termux in terms of OOM killing.
*/
const int PRIORITY_IMPORTANT = 2;
/**
* Binds the service with @{link android.content.Context#BIND_ABOVE_CLIENT}, which would make Termux get killed first on OOM.
* This should be used if you really don't want the plugin killed while a program uses it, and your plugin has a small memory footprint.
* Another solution for that is to make your own foreground service, but the undismissable notification could be annoying for users, so this is given as an option.
*/
const int PRIORITY_MAX = 3;
/**
* This or {@link com.termux.plugin_aidl.IPluginService#setCallbackService} has to be called before any other method.
@ -33,12 +57,15 @@ interface IPluginService {
/**
* This or {@link com.termux.plugin_aidl.IPluginService#setCallbackBinder} has to be called before any other method.
* It initialized the internal representation of the connected plugin and sets the callback binder to the binder returned by the bound service.
* It initializes the internal representation of the connected plugin and sets the callback binder to the binder returned by the bound service.
* You can call other methods shortly after getCallbackVersion has been called in the supplied service.
* If it's too early the methods will throw an {@link IllegalStateException} which can be caught.
*
* @param componentName This is the relative part of a component name string.
* The package name is always taken from the calling binder package for security reasons.
* @param priority This is the priority Termux should bind the service with. See the PRIORITY_* constants for more info.
*/
void setCallbackService(String componentName) = 2;
void setCallbackService(String componentName, int priority) = 2;
/**