Directly communicate with Activity

Since Android Q does not allow launching activities from the background
(Services/BroadcastReceivers) and our native process is root, directly
launch activities and use it for communication between native and app.

The target activity is not exported, so non-root apps cannot send an
intent to fool Magisk Manager. This is as safe as the previous
implementation, which uses protected system broadcasts.

This also workaround broadcast limitations in many ROMs (especially
in Chinese ROMs) which blocks the su request dialog if the app is
frozen/force stopped by the system.

Close #1326
This commit is contained in:
topjohnwu
2019-04-10 23:35:31 -04:00
parent fdeede23f7
commit 8d4c407201
3 changed files with 114 additions and 119 deletions

View File

@@ -24,6 +24,7 @@ import com.topjohnwu.magisk.components.BaseActivity;
import com.topjohnwu.magisk.container.Policy;
import com.topjohnwu.magisk.utils.FingerprintHelper;
import com.topjohnwu.magisk.utils.SuConnector;
import com.topjohnwu.magisk.utils.SuLogger;
import java.io.IOException;
import java.util.ArrayList;
@@ -33,6 +34,7 @@ import butterknife.BindView;
import java9.lang.Iterables;
public class SuRequestActivity extends BaseActivity {
@BindView(R.id.su_popup) LinearLayout suPopup;
@BindView(R.id.timeout) Spinner timeout;
@BindView(R.id.app_icon) ImageView appIcon;
@@ -47,6 +49,10 @@ public class SuRequestActivity extends BaseActivity {
private Policy policy;
private SharedPreferences timeoutPrefs;
public static final String REQUEST = "request";
public static final String LOG = "log";
public static final String NOTIFY = "notify";
@Override
public int getDarkTheme() {
return R.style.SuRequest_Dark;
@@ -63,84 +69,97 @@ public class SuRequestActivity extends BaseActivity {
lockOrientation();
supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
app.mDB.clearOutdated();
timeoutPrefs = App.deContext.getSharedPreferences("su_timeout", 0);
// Get policy
Intent intent = getIntent();
String socketName = intent.getStringExtra("socket");
if (socketName != null) {
SuConnector connector;
try {
connector = new SuConnector(socketName) {
@Override
protected void onResponse() throws IOException {
out.writeInt(policy.policy);
}
};
Bundle bundle = connector.readSocketInput();
int uid = Integer.parseInt(bundle.getString("uid"));
policy = app.mDB.getPolicy(uid);
if (policy == null) {
policy = new Policy(uid, getPackageManager());
}
} catch (IOException | PackageManager.NameNotFoundException e) {
e.printStackTrace();
String action = intent.getAction();
if (TextUtils.equals(action, REQUEST)) {
if (!handleRequest())
finish();
return;
}
handler = new ActionHandler() {
@Override
void handleAction() {
connector.response();
done();
}
return;
}
@Override
void handleAction(int action) {
int pos = timeout.getSelectedItemPosition();
timeoutPrefs.edit().putInt(policy.packageName, pos).apply();
handleAction(action, Config.Value.TIMEOUT_LIST[pos]);
}
if (TextUtils.equals(action, LOG))
SuLogger.handleLogs(intent);
else if (TextUtils.equals(action, NOTIFY))
SuLogger.handleNotify(intent);
finish();
}
private boolean handleRequest() {
String socketName = getIntent().getStringExtra("socket");
if (socketName == null)
return false;
SuConnector connector;
try {
connector = new SuConnector(socketName) {
@Override
void handleAction(int action, int time) {
policy.policy = action;
if (time >= 0) {
policy.until = (time == 0) ? 0
: (System.currentTimeMillis() / 1000 + time * 60);
app.mDB.updatePolicy(policy);
}
handleAction();
protected void onResponse() throws IOException {
out.writeInt(policy.policy);
}
};
} else {
finish();
return;
Bundle bundle = connector.readSocketInput();
int uid = Integer.parseInt(bundle.getString("uid"));
app.mDB.clearOutdated();
policy = app.mDB.getPolicy(uid);
if (policy == null) {
policy = new Policy(uid, getPackageManager());
}
} catch (IOException | PackageManager.NameNotFoundException e) {
e.printStackTrace();
return false;
}
handler = new ActionHandler() {
@Override
void handleAction() {
connector.response();
done();
}
@Override
void handleAction(int action) {
int pos = timeout.getSelectedItemPosition();
timeoutPrefs.edit().putInt(policy.packageName, pos).apply();
handleAction(action, Config.Value.TIMEOUT_LIST[pos]);
}
@Override
void handleAction(int action, int time) {
policy.policy = action;
if (time >= 0) {
policy.until = (time == 0) ? 0
: (System.currentTimeMillis() / 1000 + time * 60);
app.mDB.updatePolicy(policy);
}
handleAction();
}
};
// Never allow com.topjohnwu.magisk (could be malware)
if (TextUtils.equals(policy.packageName, BuildConfig.APPLICATION_ID)) {
finish();
return;
}
if (TextUtils.equals(policy.packageName, BuildConfig.APPLICATION_ID))
return false;
// If not interactive, response directly
if (policy.policy != Policy.INTERACTIVE) {
handler.handleAction();
return;
return true;
}
switch ((int) Config.get(Config.Key.SU_AUTO_RESPONSE)) {
case Config.Value.SU_AUTO_DENY:
handler.handleAction(Policy.DENY, 0);
return;
return true;
case Config.Value.SU_AUTO_ALLOW:
handler.handleAction(Policy.ALLOW, 0);
return;
return true;
}
showUI();
return true;
}
@SuppressLint("ClickableViewAccessibility")

View File

@@ -33,36 +33,29 @@ public class GeneralReceiver extends BroadcastReceiver {
case Intent.ACTION_REBOOT:
case Intent.ACTION_BOOT_COMPLETED:
action = intent.getStringExtra("action");
if (action == null)
action = "boot_complete";
if (action == null) {
// Actual boot completed event
Shell.su("mm_patch_dtbo").submit(result -> {
if (result.isSuccess())
Notifications.dtboPatched();
});
break;
}
switch (action) {
case "request":
case SuRequestActivity.REQUEST:
Intent i = new Intent(app, ClassMap.get(SuRequestActivity.class))
.setAction(action)
.putExtra("socket", intent.getStringExtra("socket"))
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
app.startActivity(i);
break;
case "log":
case SuRequestActivity.LOG:
SuLogger.handleLogs(intent);
break;
case "notify":
case SuRequestActivity.NOTIFY:
SuLogger.handleNotify(intent);
break;
case "boot_complete":
default:
/* Devices with DTBO might want to patch dtbo.img.
* However, that is not possible if Magisk is installed by
* patching boot image with Magisk Manager and flashed via
* fastboot, since at that time we do not have root.
* Check for dtbo status every boot time, and prompt user
* to reboot if dtbo wasn't patched and patched by Magisk Manager.
* */
Shell.su("mm_patch_dtbo").submit(result -> {
if (result.isSuccess())
Notifications.dtboPatched();
});
break;
}
break;
case Intent.ACTION_PACKAGE_REPLACED: