mirror of
https://github.com/topjohnwu/Magisk
synced 2024-11-12 19:15:23 +01:00
New method of communication
Introduce a new communication method between Magisk and Magisk Manager. Magisk used to hardcode classnames and send broadcast/start activities to specific components. This new method makes no assumption of any class names, so Magisk Manager can easily be fully obfuscated. In addition, the new method connects Magisk and Magisk Manager with random abstract Linux sockets instead of socket files in filesystems, bypassing file system complexities (selinux, permissions and such)
This commit is contained in:
parent
4cf8d41f6a
commit
906b4aad9e
@ -47,12 +47,19 @@
|
||||
<!-- Superuser -->
|
||||
|
||||
<activity
|
||||
android:name=".superuser.RequestActivity"
|
||||
android:name="a.p"
|
||||
android:excludeFromRecents="true"
|
||||
android:launchMode="singleTask"
|
||||
android:taskAffinity="internal.superuser"
|
||||
android:theme="@style/SuRequest" />
|
||||
|
||||
<activity
|
||||
android:name=".superuser.RequestActivity"
|
||||
android:excludeFromRecents="true"
|
||||
android:launchMode="singleTask"
|
||||
android:taskAffinity="internal.superuser"
|
||||
android:theme="@style/AppTheme.Translucent" />
|
||||
|
||||
<receiver android:name=".superuser.SuReceiver" />
|
||||
|
||||
<!-- Receiver -->
|
||||
|
7
app/src/full/java/a/p.java
Normal file
7
app/src/full/java/a/p.java
Normal file
@ -0,0 +1,7 @@
|
||||
package a;
|
||||
|
||||
import com.topjohnwu.magisk.SuRequestActivity;
|
||||
|
||||
public class p extends SuRequestActivity {
|
||||
/* stub */
|
||||
}
|
@ -141,9 +141,6 @@ public class Const {
|
||||
public static final int NAMESPACE_MODE_ISOLATE = 2;
|
||||
public static final int NO_NOTIFICATION = 0;
|
||||
public static final int NOTIFICATION_TOAST = 1;
|
||||
public static final int NOTIFY_NORMAL_LOG = 0;
|
||||
public static final int NOTIFY_USER_TOASTS = 1;
|
||||
public static final int NOTIFY_USER_TO_OWNER = 2;
|
||||
public static final int SU_PROMPT = 0;
|
||||
public static final int SU_AUTO_DENY = 1;
|
||||
public static final int SU_AUTO_ALLOW = 2;
|
||||
|
@ -87,6 +87,7 @@ public class Data {
|
||||
classMap.put(OnBootService.class, a.m.class);
|
||||
classMap.put(UpdateCheckService.class, a.n.class);
|
||||
classMap.put(AboutCardRow.class, a.o.class);
|
||||
classMap.put(SuRequestActivity.class, a.p.class);
|
||||
|
||||
}
|
||||
|
||||
|
258
app/src/full/java/com/topjohnwu/magisk/SuRequestActivity.java
Normal file
258
app/src/full/java/com/topjohnwu/magisk/SuRequestActivity.java
Normal file
@ -0,0 +1,258 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.hardware.fingerprint.FingerprintManager;
|
||||
import android.net.LocalSocketAddress;
|
||||
import android.os.Bundle;
|
||||
import android.os.CountDownTimer;
|
||||
import android.os.FileObserver;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
|
||||
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 java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
public class SuRequestActivity extends BaseActivity {
|
||||
LinearLayout suPopup;
|
||||
Spinner timeout;
|
||||
ImageView appIcon;
|
||||
TextView appNameView;
|
||||
TextView packageNameView;
|
||||
Button grant_btn;
|
||||
Button deny_btn;
|
||||
ImageView fingerprintImg;
|
||||
TextView warning;
|
||||
|
||||
private SuConnector connector;
|
||||
private Policy policy;
|
||||
private CountDownTimer timer;
|
||||
private FingerprintHelper fingerprintHelper;
|
||||
|
||||
class SuConnectorV1 extends SuConnector {
|
||||
|
||||
SuConnectorV1(String name) throws IOException {
|
||||
socket.connect(new LocalSocketAddress(name, LocalSocketAddress.Namespace.FILESYSTEM));
|
||||
new FileObserver(name) {
|
||||
@Override
|
||||
public void onEvent(int fileEvent, String path) {
|
||||
if (fileEvent == FileObserver.DELETE_SELF) {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
}.startWatching();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void response() {
|
||||
try (OutputStream out = getOutputStream()) {
|
||||
out.write((policy.policy == Policy.ALLOW ? "socket:ALLOW" : "socket:DENY").getBytes());
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SuConnectorV2 extends SuConnector {
|
||||
|
||||
SuConnectorV2(String name) throws IOException {
|
||||
socket.connect(new LocalSocketAddress(name, LocalSocketAddress.Namespace.ABSTRACT));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void response() {
|
||||
try (DataOutputStream out = getOutputStream()) {
|
||||
out.writeInt(policy.policy);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDarkTheme() {
|
||||
return R.style.SuRequest_Dark;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish() {
|
||||
if (timer != null)
|
||||
timer.cancel();
|
||||
if (fingerprintHelper != null)
|
||||
fingerprintHelper.cancel();
|
||||
super.finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (policy != null) {
|
||||
handleAction(Policy.DENY);
|
||||
} else {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
|
||||
PackageManager pm = getPackageManager();
|
||||
mm.mDB.clearOutdated();
|
||||
|
||||
// Get policy
|
||||
Intent intent = getIntent();
|
||||
try {
|
||||
connector = intent.getIntExtra("version", 1) == 1 ?
|
||||
new SuConnectorV1(intent.getStringExtra("socket")) :
|
||||
new SuConnectorV2(intent.getStringExtra("socket"));
|
||||
Bundle bundle = connector.readSocketInput();
|
||||
int uid = Integer.parseInt(bundle.getString("uid"));
|
||||
policy = mm.mDB.getPolicy(uid);
|
||||
if (policy == null) {
|
||||
policy = new Policy(uid, pm);
|
||||
}
|
||||
} catch (IOException | PackageManager.NameNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
// Never allow com.topjohnwu.magisk (could be malware)
|
||||
if (TextUtils.equals(policy.packageName, Const.ORIG_PKG_NAME)) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
switch (Data.suResponseType) {
|
||||
case Const.Value.SU_AUTO_DENY:
|
||||
handleAction(Policy.DENY, 0);
|
||||
return;
|
||||
case Const.Value.SU_AUTO_ALLOW:
|
||||
handleAction(Policy.ALLOW, 0);
|
||||
return;
|
||||
case Const.Value.SU_PROMPT:
|
||||
default:
|
||||
}
|
||||
|
||||
// If not interactive, response directly
|
||||
if (policy.policy != Policy.INTERACTIVE) {
|
||||
handleAction();
|
||||
return;
|
||||
}
|
||||
|
||||
setContentView(R.layout.activity_request);
|
||||
ViewBinder.bind(this);
|
||||
|
||||
appIcon.setImageDrawable(policy.info.loadIcon(pm));
|
||||
appNameView.setText(policy.appName);
|
||||
packageNameView.setText(policy.packageName);
|
||||
|
||||
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this,
|
||||
R.array.allow_timeout, android.R.layout.simple_spinner_item);
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
timeout.setAdapter(adapter);
|
||||
|
||||
timer = new CountDownTimer(Data.suRequestTimeout * 1000, 1000) {
|
||||
@Override
|
||||
public void onTick(long millisUntilFinished) {
|
||||
deny_btn.setText(getString(R.string.deny_with_str, "(" + millisUntilFinished / 1000 + ")"));
|
||||
}
|
||||
@Override
|
||||
public void onFinish() {
|
||||
deny_btn.setText(getString(R.string.deny_with_str, "(0)"));
|
||||
handleAction(Policy.DENY);
|
||||
}
|
||||
};
|
||||
|
||||
boolean useFingerprint = Data.suFingerprint && FingerprintHelper.canUseFingerprint();
|
||||
|
||||
if (useFingerprint) {
|
||||
try {
|
||||
fingerprintHelper = new FingerprintHelper() {
|
||||
@Override
|
||||
public void onAuthenticationError(int errorCode, CharSequence errString) {
|
||||
warning.setText(errString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
|
||||
warning.setText(helpString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
|
||||
handleAction(Policy.ALLOW);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationFailed() {
|
||||
warning.setText(R.string.auth_fail);
|
||||
}
|
||||
};
|
||||
fingerprintHelper.authenticate();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
useFingerprint = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!useFingerprint) {
|
||||
grant_btn.setOnClickListener(v -> {
|
||||
handleAction(Policy.ALLOW);
|
||||
timer.cancel();
|
||||
});
|
||||
grant_btn.requestFocus();
|
||||
}
|
||||
|
||||
grant_btn.setVisibility(useFingerprint ? View.GONE : View.VISIBLE);
|
||||
fingerprintImg.setVisibility(useFingerprint ? View.VISIBLE : View.GONE);
|
||||
|
||||
deny_btn.setOnClickListener(v -> {
|
||||
handleAction(Policy.DENY);
|
||||
timer.cancel();
|
||||
});
|
||||
suPopup.setOnClickListener(v -> cancelTimeout());
|
||||
timeout.setOnTouchListener((v, event) -> cancelTimeout());
|
||||
timer.start();
|
||||
}
|
||||
|
||||
private boolean cancelTimeout() {
|
||||
timer.cancel();
|
||||
deny_btn.setText(getString(R.string.deny));
|
||||
return false;
|
||||
}
|
||||
|
||||
private void handleAction() {
|
||||
connector.response();
|
||||
finish();
|
||||
}
|
||||
|
||||
private void handleAction(int action) {
|
||||
handleAction(action, Const.Value.timeoutList[timeout.getSelectedItemPosition()]);
|
||||
}
|
||||
|
||||
private void handleAction(int action, int time) {
|
||||
policy.policy = action;
|
||||
if (time >= 0) {
|
||||
policy.until = (time == 0) ? 0 : (System.currentTimeMillis() / 1000 + time * 60);
|
||||
mm.mDB.addPolicy(policy);
|
||||
}
|
||||
handleAction();
|
||||
}
|
||||
}
|
@ -18,7 +18,6 @@ import com.topjohnwu.magisk.fragments.ModulesFragment;
|
||||
import com.topjohnwu.magisk.fragments.ReposFragment;
|
||||
import com.topjohnwu.magisk.fragments.SuLogFragment;
|
||||
import com.topjohnwu.magisk.fragments.SuperuserFragment;
|
||||
import com.topjohnwu.magisk.superuser.RequestActivity;
|
||||
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
@ -56,8 +55,8 @@ public class ViewBinder {
|
||||
target.findViewById(R.id.no_thanks).setOnClickListener(v -> target.finish());
|
||||
target.findViewById(R.id.save_logs).setOnClickListener(v -> target.saveLogs());
|
||||
}
|
||||
|
||||
public static void bind(RequestActivity target) {
|
||||
|
||||
public static void bind(SuRequestActivity target) {
|
||||
target.suPopup = target.findViewById(R.id.su_popup);
|
||||
target.timeout = target.findViewById(R.id.timeout);
|
||||
target.appIcon = target.findViewById(R.id.app_icon);
|
||||
|
@ -5,14 +5,31 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import a.m;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.SuRequestActivity;
|
||||
import com.topjohnwu.magisk.services.OnBootService;
|
||||
import com.topjohnwu.magisk.utils.SuConnector;
|
||||
|
||||
public class BootReceiver extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (TextUtils.equals(intent.getAction(), Intent.ACTION_BOOT_COMPLETED))
|
||||
m.enqueueWork(context);
|
||||
if (TextUtils.equals(intent.getAction(), Intent.ACTION_BOOT_COMPLETED)) {
|
||||
switch (intent.getExtras().getString("action", "boot")) {
|
||||
case "request":
|
||||
Intent i = new Intent(context, Data.classMap.get(SuRequestActivity.class))
|
||||
.putExtra("socket", intent.getStringExtra("socket"))
|
||||
.putExtra("version", 2)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
context.startActivity(i);
|
||||
break;
|
||||
case "log":
|
||||
SuConnector.handleLogs(intent, 2);
|
||||
break;
|
||||
case "boot":
|
||||
OnBootService.enqueueWork(context);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,295 +1,21 @@
|
||||
package com.topjohnwu.magisk.superuser;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.hardware.fingerprint.FingerprintManager;
|
||||
import android.net.LocalSocket;
|
||||
import android.net.LocalSocketAddress;
|
||||
import android.os.Bundle;
|
||||
import android.os.CountDownTimer;
|
||||
import android.os.FileObserver;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.ViewBinder;
|
||||
import com.topjohnwu.magisk.asyncs.ParallelTask;
|
||||
import com.topjohnwu.magisk.SuRequestActivity;
|
||||
import com.topjohnwu.magisk.components.BaseActivity;
|
||||
import com.topjohnwu.magisk.container.Policy;
|
||||
import com.topjohnwu.magisk.utils.FingerprintHelper;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
public class RequestActivity extends BaseActivity {
|
||||
|
||||
public LinearLayout suPopup;
|
||||
public Spinner timeout;
|
||||
public ImageView appIcon;
|
||||
public TextView appNameView;
|
||||
public TextView packageNameView;
|
||||
public Button grant_btn;
|
||||
public Button deny_btn;
|
||||
public ImageView fingerprintImg;
|
||||
public TextView warning;
|
||||
|
||||
private String socketPath;
|
||||
private LocalSocket socket;
|
||||
private PackageManager pm;
|
||||
|
||||
private boolean hasTimeout;
|
||||
private Policy policy;
|
||||
private CountDownTimer timer;
|
||||
private FingerprintHelper fingerprintHelper;
|
||||
|
||||
@Override
|
||||
public int getDarkTheme() {
|
||||
return R.style.SuRequest_Dark;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
|
||||
pm = getPackageManager();
|
||||
mm.mDB.clearOutdated();
|
||||
|
||||
Intent intent = getIntent();
|
||||
socketPath = intent.getStringExtra("socket");
|
||||
hasTimeout = intent.getBooleanExtra("timeout", true);
|
||||
|
||||
new FileObserver(socketPath) {
|
||||
@Override
|
||||
public void onEvent(int fileEvent, String path) {
|
||||
if (fileEvent == FileObserver.DELETE_SELF) {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
}.startWatching();
|
||||
|
||||
new SocketManager(this).exec();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish() {
|
||||
if (timer != null)
|
||||
timer.cancel();
|
||||
if (fingerprintHelper != null)
|
||||
fingerprintHelper.cancel();
|
||||
super.finish();
|
||||
}
|
||||
|
||||
private boolean cancelTimeout() {
|
||||
timer.cancel();
|
||||
deny_btn.setText(getString(R.string.deny));
|
||||
return false;
|
||||
}
|
||||
|
||||
private void showRequest() {
|
||||
switch (Data.suResponseType) {
|
||||
case Const.Value.SU_AUTO_DENY:
|
||||
handleAction(Policy.DENY, 0);
|
||||
return;
|
||||
case Const.Value.SU_AUTO_ALLOW:
|
||||
handleAction(Policy.ALLOW, 0);
|
||||
return;
|
||||
case Const.Value.SU_PROMPT:
|
||||
default:
|
||||
}
|
||||
|
||||
// If not interactive, response directly
|
||||
if (policy.policy != Policy.INTERACTIVE) {
|
||||
handleAction();
|
||||
return;
|
||||
}
|
||||
|
||||
setContentView(R.layout.activity_request);
|
||||
ViewBinder.bind(this);
|
||||
|
||||
appIcon.setImageDrawable(policy.info.loadIcon(pm));
|
||||
appNameView.setText(policy.appName);
|
||||
packageNameView.setText(policy.packageName);
|
||||
|
||||
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this,
|
||||
R.array.allow_timeout, android.R.layout.simple_spinner_item);
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
timeout.setAdapter(adapter);
|
||||
|
||||
timer = new CountDownTimer(Data.suRequestTimeout * 1000, 1000) {
|
||||
@Override
|
||||
public void onTick(long millisUntilFinished) {
|
||||
deny_btn.setText(getString(R.string.deny_with_str, "(" + millisUntilFinished / 1000 + ")"));
|
||||
}
|
||||
@Override
|
||||
public void onFinish() {
|
||||
deny_btn.setText(getString(R.string.deny_with_str, "(0)"));
|
||||
handleAction(Policy.DENY);
|
||||
}
|
||||
};
|
||||
|
||||
boolean useFingerprint = Data.suFingerprint && FingerprintHelper.canUseFingerprint();
|
||||
|
||||
if (useFingerprint) {
|
||||
try {
|
||||
fingerprintHelper = new FingerprintHelper() {
|
||||
@Override
|
||||
public void onAuthenticationError(int errorCode, CharSequence errString) {
|
||||
warning.setText(errString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
|
||||
warning.setText(helpString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
|
||||
handleAction(Policy.ALLOW);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationFailed() {
|
||||
warning.setText(R.string.auth_fail);
|
||||
}
|
||||
};
|
||||
fingerprintHelper.authenticate();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
useFingerprint = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!useFingerprint) {
|
||||
grant_btn.setOnClickListener(v -> {
|
||||
handleAction(Policy.ALLOW);
|
||||
timer.cancel();
|
||||
});
|
||||
grant_btn.requestFocus();
|
||||
}
|
||||
|
||||
grant_btn.setVisibility(useFingerprint ? View.GONE : View.VISIBLE);
|
||||
fingerprintImg.setVisibility(useFingerprint ? View.VISIBLE : View.GONE);
|
||||
|
||||
deny_btn.setOnClickListener(v -> {
|
||||
handleAction(Policy.DENY);
|
||||
timer.cancel();
|
||||
});
|
||||
suPopup.setOnClickListener(v -> cancelTimeout());
|
||||
timeout.setOnTouchListener((v, event) -> cancelTimeout());
|
||||
|
||||
if (hasTimeout) {
|
||||
timer.start();
|
||||
} else {
|
||||
cancelTimeout();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (policy != null) {
|
||||
handleAction(Policy.DENY);
|
||||
} else {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
void handleAction() {
|
||||
String response;
|
||||
if (policy.policy == Policy.ALLOW) {
|
||||
response = "socket:ALLOW";
|
||||
} else {
|
||||
response = "socket:DENY";
|
||||
}
|
||||
try {
|
||||
socket.getOutputStream().write((response).getBytes());
|
||||
socket.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
Intent intent = new Intent(this, Data.classMap.get(SuRequestActivity.class))
|
||||
.putExtra("socket", getIntent().getStringExtra("socket"))
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
|
||||
void handleAction(int action) {
|
||||
handleAction(action, Const.Value.timeoutList[timeout.getSelectedItemPosition()]);
|
||||
}
|
||||
|
||||
void handleAction(int action, int time) {
|
||||
policy.policy = action;
|
||||
if (time >= 0) {
|
||||
policy.until = (time == 0) ? 0 : (System.currentTimeMillis() / 1000 + time * 60);
|
||||
mm.mDB.addPolicy(policy);
|
||||
}
|
||||
handleAction();
|
||||
}
|
||||
|
||||
private class SocketManager extends ParallelTask<Void, Void, Boolean> {
|
||||
|
||||
SocketManager(BaseActivity context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Boolean doInBackground(Void... params) {
|
||||
try {
|
||||
socket = new LocalSocket();
|
||||
socket.connect(new LocalSocketAddress(socketPath, LocalSocketAddress.Namespace.FILESYSTEM));
|
||||
|
||||
DataInputStream is = new DataInputStream(socket.getInputStream());
|
||||
ContentValues payload = new ContentValues();
|
||||
|
||||
while (true) {
|
||||
int nameLen = is.readInt();
|
||||
byte[] nameBytes = new byte[nameLen];
|
||||
is.readFully(nameBytes);
|
||||
String name = new String(nameBytes);
|
||||
if (TextUtils.equals(name, "eof"))
|
||||
break;
|
||||
|
||||
int dataLen = is.readInt();
|
||||
byte[] dataBytes = new byte[dataLen];
|
||||
is.readFully(dataBytes);
|
||||
String data = new String(dataBytes);
|
||||
payload.put(name, data);
|
||||
}
|
||||
|
||||
if (payload.getAsInteger("uid") == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int uid = payload.getAsInteger("uid");
|
||||
policy = mm.mDB.getPolicy(uid);
|
||||
if (policy == null) {
|
||||
policy = new Policy(uid, pm);
|
||||
}
|
||||
|
||||
/* Never allow com.topjohnwu.magisk (could be malware) */
|
||||
if (TextUtils.equals(policy.packageName, Const.ORIG_PKG_NAME))
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Boolean result) {
|
||||
if (result) {
|
||||
showRequest();
|
||||
} else {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,88 +3,13 @@ package com.topjohnwu.magisk.superuser;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Process;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.container.Policy;
|
||||
import com.topjohnwu.magisk.container.SuLogEntry;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
|
||||
import java.util.Date;
|
||||
import com.topjohnwu.magisk.utils.SuConnector;
|
||||
|
||||
public class SuReceiver extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
int fromUid, toUid, pid, mode;
|
||||
String command, action;
|
||||
Policy policy;
|
||||
|
||||
MagiskManager mm = Data.MM();
|
||||
|
||||
if (intent == null) return;
|
||||
|
||||
mode = intent.getIntExtra("mode", -1);
|
||||
if (mode < 0) return;
|
||||
|
||||
if (mode == Const.Value.NOTIFY_USER_TO_OWNER) {
|
||||
Utils.toast(R.string.multiuser_hint_owner_request, Toast.LENGTH_LONG);
|
||||
return;
|
||||
}
|
||||
|
||||
fromUid = intent.getIntExtra("from.uid", -1);
|
||||
if (fromUid < 0) return;
|
||||
if (fromUid == Process.myUid()) return; // Don't show anything if it's Magisk Manager
|
||||
|
||||
action = intent.getStringExtra("action");
|
||||
if (action == null) return;
|
||||
|
||||
policy = mm.mDB.getPolicy(fromUid);
|
||||
if (policy == null) {
|
||||
try {
|
||||
policy = new Policy(fromUid, mm.getPackageManager());
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
SuLogEntry log = new SuLogEntry(policy);
|
||||
|
||||
String message;
|
||||
switch (action) {
|
||||
case "allow":
|
||||
message = mm.getString(R.string.su_allow_toast, policy.appName);
|
||||
log.action = true;
|
||||
break;
|
||||
case "deny":
|
||||
message = mm.getString(R.string.su_deny_toast, policy.appName);
|
||||
log.action = false;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
if (policy.notification && Data.suNotificationType == Const.Value.NOTIFICATION_TOAST)
|
||||
Utils.toast(message, Toast.LENGTH_SHORT);
|
||||
|
||||
if (mode == Const.Value.NOTIFY_NORMAL_LOG && policy.logging) {
|
||||
toUid = intent.getIntExtra("to.uid", -1);
|
||||
if (toUid < 0) return;
|
||||
pid = intent.getIntExtra("pid", -1);
|
||||
if (pid < 0) return;
|
||||
command = intent.getStringExtra("command");
|
||||
if (command == null) return;
|
||||
log.toUid = toUid;
|
||||
log.fromPid = pid;
|
||||
log.command = command;
|
||||
log.date = new Date();
|
||||
mm.mDB.addLog(log);
|
||||
}
|
||||
SuConnector.handleLogs(intent, 1);
|
||||
}
|
||||
}
|
||||
|
118
app/src/full/java/com/topjohnwu/magisk/utils/SuConnector.java
Normal file
118
app/src/full/java/com/topjohnwu/magisk/utils/SuConnector.java
Normal file
@ -0,0 +1,118 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.LocalSocket;
|
||||
import android.os.Bundle;
|
||||
import android.os.Process;
|
||||
import android.text.TextUtils;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.container.Policy;
|
||||
import com.topjohnwu.magisk.container.SuLogEntry;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
|
||||
public abstract class SuConnector {
|
||||
|
||||
protected LocalSocket socket = new LocalSocket();
|
||||
|
||||
private String readString(DataInputStream is) throws IOException {
|
||||
int len = is.readInt();
|
||||
byte[] buf = new byte[len];
|
||||
is.readFully(buf);
|
||||
return new String(buf);
|
||||
}
|
||||
|
||||
public Bundle readSocketInput() throws IOException {
|
||||
Bundle bundle = new Bundle();
|
||||
DataInputStream is = new DataInputStream(socket.getInputStream());
|
||||
while (true) {
|
||||
String name = readString(is);
|
||||
if (TextUtils.equals(name, "eof"))
|
||||
break;
|
||||
bundle.putString(name, readString(is));
|
||||
}
|
||||
return bundle;
|
||||
}
|
||||
|
||||
protected DataOutputStream getOutputStream() throws IOException {
|
||||
return new DataOutputStream(socket.getOutputStream());
|
||||
}
|
||||
|
||||
public abstract void response();
|
||||
|
||||
public static void handleLogs(Intent intent, int version) {
|
||||
MagiskManager mm = Data.MM();
|
||||
|
||||
if (intent == null) return;
|
||||
|
||||
int fromUid = intent.getIntExtra("from.uid", -1);
|
||||
if (fromUid < 0) return;
|
||||
if (fromUid == Process.myUid()) return;
|
||||
|
||||
Policy policy = mm.mDB.getPolicy(fromUid);
|
||||
if (policy == null) {
|
||||
try {
|
||||
policy = new Policy(fromUid, mm.getPackageManager());
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
SuLogEntry log = new SuLogEntry(policy);
|
||||
if (version == 1) {
|
||||
String action = intent.getStringExtra("action");
|
||||
if (action == null) return;
|
||||
switch (action) {
|
||||
case "allow":
|
||||
log.action = true;
|
||||
break;
|
||||
case "deny":
|
||||
log.action = false;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
switch (intent.getIntExtra("policy", -1)) {
|
||||
case Policy.ALLOW:
|
||||
log.action = true;
|
||||
break;
|
||||
case Policy.DENY:
|
||||
log.action = false;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
String message = mm.getString(log.action ?
|
||||
R.string.su_allow_toast : R.string.su_deny_toast, policy.appName);
|
||||
|
||||
if (policy.notification && Data.suNotificationType == Const.Value.NOTIFICATION_TOAST)
|
||||
Utils.toast(message, Toast.LENGTH_SHORT);
|
||||
|
||||
if (policy.logging) {
|
||||
int toUid = intent.getIntExtra("to.uid", -1);
|
||||
if (toUid < 0) return;
|
||||
int pid = intent.getIntExtra("pid", -1);
|
||||
if (pid < 0) return;
|
||||
String command = intent.getStringExtra("command");
|
||||
if (command == null) return;
|
||||
log.toUid = toUid;
|
||||
log.fromPid = pid;
|
||||
log.command = command;
|
||||
log.date = new Date();
|
||||
mm.mDB.addLog(log);
|
||||
}
|
||||
}
|
||||
}
|
@ -3,7 +3,7 @@
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/su_popup"
|
||||
tools:context=".superuser.RequestActivity"
|
||||
tools:context=".SuRequestActivity"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"
|
||||
android:minWidth="350dp"
|
||||
|
@ -52,10 +52,9 @@ LOCAL_SRC_FILES := \
|
||||
resetprop/system_property_api.cpp \
|
||||
resetprop/system_property_set.cpp \
|
||||
su/su.c \
|
||||
su/activity.c \
|
||||
su/connect.c \
|
||||
su/pts.c \
|
||||
su/su_daemon.c \
|
||||
su/su_socket.c \
|
||||
utils/img.c \
|
||||
$(COMMON_UTILS)
|
||||
|
||||
|
@ -111,9 +111,9 @@ void main_daemon() {
|
||||
check_and_start_logger();
|
||||
|
||||
struct sockaddr_un sun;
|
||||
fd = setup_socket(&sun, MAIN_DAEMON);
|
||||
|
||||
if (xbind(fd, (struct sockaddr*) &sun, sizeof(sun.sun_family) + strlen(sun.sun_path + 1) + 1))
|
||||
socklen_t len = setup_sockaddr(&sun, MAIN_DAEMON);
|
||||
fd = xsocket(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0);
|
||||
if (xbind(fd, (struct sockaddr*) &sun, len))
|
||||
exit(1);
|
||||
xlisten(fd, 10);
|
||||
LOGI("Magisk v" xstr(MAGISK_VERSION) "(" xstr(MAGISK_VER_CODE) ") daemon started\n");
|
||||
@ -148,8 +148,8 @@ void main_daemon() {
|
||||
/* Connect the daemon, set sockfd, and return if new daemon is spawned */
|
||||
int connect_daemon2(daemon_t d, int *sockfd) {
|
||||
struct sockaddr_un sun;
|
||||
*sockfd = setup_socket(&sun, d);
|
||||
socklen_t len = sizeof(sun.sun_family) + strlen(sun.sun_path + 1) + 1;
|
||||
socklen_t len = setup_sockaddr(&sun, d);
|
||||
*sockfd = xsocket(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0);
|
||||
if (connect(*sockfd, (struct sockaddr*) &sun, len)) {
|
||||
if (getuid() != UID_ROOT || getgid() != UID_ROOT) {
|
||||
fprintf(stderr, "No daemon is currently running!\n");
|
||||
|
@ -123,8 +123,9 @@ int check_and_start_logger() {
|
||||
void log_daemon() {
|
||||
setsid();
|
||||
struct sockaddr_un sun;
|
||||
sockfd = setup_socket(&sun, LOG_DAEMON);
|
||||
if (xbind(sockfd, (struct sockaddr*) &sun, sizeof(sun.sun_family) + strlen(sun.sun_path + 1) + 1))
|
||||
socklen_t len = setup_sockaddr(&sun, LOG_DAEMON);
|
||||
sockfd = xsocket(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0);
|
||||
if (xbind(sockfd, (struct sockaddr*) &sun, len))
|
||||
exit(1);
|
||||
xlisten(sockfd, 10);
|
||||
LOGI("Magisk v" xstr(MAGISK_VERSION) "(" xstr(MAGISK_VER_CODE) ") logger started\n");
|
||||
|
@ -2,18 +2,18 @@
|
||||
*/
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <endian.h>
|
||||
|
||||
#include "daemon.h"
|
||||
#include "logging.h"
|
||||
#include "utils.h"
|
||||
#include "magisk.h"
|
||||
|
||||
/* Setup the address and return socket fd */
|
||||
int setup_socket(struct sockaddr_un *sun, daemon_t d) {
|
||||
int fd = xsocket(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0);
|
||||
#define ABS_SOCKET_LEN(sun) (sizeof(sun->sun_family) + strlen(sun->sun_path + 1) + 1)
|
||||
|
||||
socklen_t setup_sockaddr(struct sockaddr_un *sun, daemon_t d) {
|
||||
memset(sun, 0, sizeof(*sun));
|
||||
sun->sun_family = AF_LOCAL;
|
||||
sun->sun_path[0] = '\0';
|
||||
const char *name;
|
||||
switch (d) {
|
||||
case MAIN_DAEMON:
|
||||
@ -24,9 +24,39 @@ int setup_socket(struct sockaddr_un *sun, daemon_t d) {
|
||||
break;
|
||||
}
|
||||
strcpy(sun->sun_path + 1, name);
|
||||
return ABS_SOCKET_LEN(sun);
|
||||
}
|
||||
|
||||
int create_rand_socket(struct sockaddr_un *sun) {
|
||||
memset(sun, 0, sizeof(*sun));
|
||||
sun->sun_family = AF_LOCAL;
|
||||
gen_rand_str(sun->sun_path + 1, 9);
|
||||
int fd = xsocket(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0);
|
||||
xbind(fd, (struct sockaddr*) sun, ABS_SOCKET_LEN(sun));
|
||||
xlisten(fd, 1);
|
||||
return fd;
|
||||
}
|
||||
|
||||
int socket_accept(int serv_fd, int timeout) {
|
||||
struct timeval tv;
|
||||
fd_set fds;
|
||||
int rc;
|
||||
|
||||
tv.tv_sec = timeout;
|
||||
tv.tv_usec = 0;
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(serv_fd, &fds);
|
||||
do {
|
||||
rc = select(serv_fd + 1, &fds, NULL, NULL, &tv);
|
||||
} while (rc < 0 && errno == EINTR);
|
||||
if (rc < 1) {
|
||||
PLOGE("select");
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
return xaccept4(serv_fd, NULL, NULL, SOCK_CLOEXEC);
|
||||
}
|
||||
|
||||
/*
|
||||
* Receive a file descriptor from a Unix socket.
|
||||
* Contributed by @mkasick
|
||||
@ -136,26 +166,59 @@ int read_int(int fd) {
|
||||
return val;
|
||||
}
|
||||
|
||||
int read_int_be(int fd) {
|
||||
uint32_t val;
|
||||
xxread(fd, &val, sizeof(val));
|
||||
return ntohl(val);
|
||||
}
|
||||
|
||||
void write_int(int fd, int val) {
|
||||
if (fd < 0) return;
|
||||
xwrite(fd, &val, sizeof(int));
|
||||
}
|
||||
|
||||
char* read_string(int fd) {
|
||||
int len = read_int(fd);
|
||||
if (len > PATH_MAX || len < 0) {
|
||||
LOGE("invalid string length %d", len);
|
||||
exit(1);
|
||||
}
|
||||
void write_int_be(int fd, int val) {
|
||||
uint32_t nl = htonl(val);
|
||||
xwrite(fd, &nl, sizeof(nl));
|
||||
}
|
||||
|
||||
static char *rd_str(int fd, int len) {
|
||||
char* val = xmalloc(sizeof(char) * (len + 1));
|
||||
xxread(fd, val, len);
|
||||
val[len] = '\0';
|
||||
return val;
|
||||
}
|
||||
|
||||
void write_string(int fd, const char* val) {
|
||||
char* read_string(int fd) {
|
||||
int len = read_int(fd);
|
||||
return rd_str(fd, len);
|
||||
}
|
||||
|
||||
char* read_string_be(int fd) {
|
||||
int len = read_int_be(fd);
|
||||
return rd_str(fd, len);
|
||||
}
|
||||
|
||||
void write_string(int fd, const char *val) {
|
||||
if (fd < 0) return;
|
||||
int len = strlen(val);
|
||||
write_int(fd, len);
|
||||
xwrite(fd, val, len);
|
||||
}
|
||||
|
||||
void write_string_be(int fd, const char *val) {
|
||||
int len = strlen(val);
|
||||
write_int_be(fd, len);
|
||||
xwrite(fd, val, len);
|
||||
}
|
||||
|
||||
void write_key_value(int fd, const char *key, const char *val) {
|
||||
write_string_be(fd, key);
|
||||
write_string_be(fd, val);
|
||||
}
|
||||
|
||||
void write_key_token(int fd, const char *key, int tok) {
|
||||
char val[16];
|
||||
sprintf(val, "%d", tok);
|
||||
write_key_value(fd, key, val);
|
||||
}
|
||||
|
@ -59,13 +59,21 @@ int check_and_start_logger();
|
||||
|
||||
// socket.c
|
||||
|
||||
int setup_socket(struct sockaddr_un *sun, daemon_t d);
|
||||
socklen_t setup_sockaddr(struct sockaddr_un *sun, daemon_t d);
|
||||
int create_rand_socket(struct sockaddr_un *sun);
|
||||
int socket_accept(int serv_fd, int timeout);
|
||||
int recv_fd(int sockfd);
|
||||
void send_fd(int sockfd, int fd);
|
||||
int read_int(int fd);
|
||||
int read_int_be(int fd);
|
||||
void write_int(int fd, int val);
|
||||
char* read_string(int fd);
|
||||
void write_string(int fd, const char* val);
|
||||
void write_int_be(int fd, int val);
|
||||
char *read_string(int fd);
|
||||
char *read_string_be(int fd);
|
||||
void write_string(int fd, const char *val);
|
||||
void write_string_be(int fd, const char *val);
|
||||
void write_key_value(int fd, const char *key, const char *val);
|
||||
void write_key_token(int fd, const char *key, int tok);
|
||||
|
||||
/***************
|
||||
* Boot Stages *
|
||||
|
@ -1,141 +0,0 @@
|
||||
/*
|
||||
** Copyright 2017, John Wu (@topjohnwu)
|
||||
** Copyright 2010, Adam Shanks (@ChainsDD)
|
||||
** Copyright 2008, Zinx Verituse (@zinxv)
|
||||
**
|
||||
*/
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "magisk.h"
|
||||
#include "su.h"
|
||||
|
||||
/* intent actions */
|
||||
#define ACTION_REQUEST "%s/" REQUESTOR_PREFIX ".RequestActivity"
|
||||
#define ACTION_RESULT "%s/" REQUESTOR_PREFIX ".SuReceiver"
|
||||
|
||||
#define AM_PATH "/system/bin/app_process", "/system/bin", "com.android.commands.am.Am"
|
||||
|
||||
static char *get_command(const struct su_request *to) {
|
||||
if (to->command)
|
||||
return to->command;
|
||||
if (to->shell)
|
||||
return to->shell;
|
||||
return DEFAULT_SHELL;
|
||||
}
|
||||
|
||||
static void silent_run(char* const args[]) {
|
||||
set_identity(0);
|
||||
if (fork())
|
||||
return;
|
||||
int zero = open("/dev/zero", O_RDONLY | O_CLOEXEC);
|
||||
dup2(zero, 0);
|
||||
int null = open("/dev/null", O_WRONLY | O_CLOEXEC);
|
||||
dup2(null, 1);
|
||||
dup2(null, 2);
|
||||
setenv("CLASSPATH", "/system/framework/am.jar", 1);
|
||||
execv(args[0], args);
|
||||
PLOGE("exec am");
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
static int setup_user(struct su_context *ctx, char* user) {
|
||||
switch (ctx->info->dbs.v[SU_MULTIUSER_MODE]) {
|
||||
case MULTIUSER_MODE_OWNER_ONLY: /* Should already be denied if not owner */
|
||||
case MULTIUSER_MODE_OWNER_MANAGED:
|
||||
sprintf(user, "%d", 0);
|
||||
return ctx->info->uid / 100000;
|
||||
case MULTIUSER_MODE_USER:
|
||||
sprintf(user, "%d", ctx->info->uid / 100000);
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void app_send_result(struct su_context *ctx, policy_t policy) {
|
||||
char fromUid[16];
|
||||
if (ctx->info->dbs.v[SU_MULTIUSER_MODE] == MULTIUSER_MODE_OWNER_MANAGED)
|
||||
sprintf(fromUid, "%d", ctx->info->uid % 100000);
|
||||
else
|
||||
sprintf(fromUid, "%d", ctx->info->uid);
|
||||
|
||||
char toUid[16];
|
||||
sprintf(toUid, "%d", ctx->to.uid);
|
||||
|
||||
char pid[16];
|
||||
sprintf(pid, "%d", ctx->pid);
|
||||
|
||||
char user[16];
|
||||
int notify = setup_user(ctx, user);
|
||||
|
||||
char activity[128];
|
||||
sprintf(activity, ACTION_RESULT, ctx->info->str.s[SU_MANAGER]);
|
||||
|
||||
// Send notice to manager, enable logging
|
||||
char *result_command[] = {
|
||||
AM_PATH, "broadcast", "-n",
|
||||
activity,
|
||||
"--user", user,
|
||||
"--ei", "mode", "0",
|
||||
"--ei", "from.uid", fromUid,
|
||||
"--ei", "to.uid", toUid,
|
||||
"--ei", "pid", pid,
|
||||
"--es", "command", get_command(&ctx->to),
|
||||
"--es", "action", policy == ALLOW ? "allow" : "deny",
|
||||
NULL
|
||||
};
|
||||
silent_run(result_command);
|
||||
|
||||
// Send notice to user (if needed) to create toasts
|
||||
if (notify) {
|
||||
sprintf(fromUid, "%d", ctx->info->uid);
|
||||
sprintf(user, "%d", notify);
|
||||
char *notify_command[] = {
|
||||
AM_PATH, "broadcast", "-n",
|
||||
activity,
|
||||
"--user", user,
|
||||
"--ei", "mode", "1",
|
||||
"--ei", "from.uid", fromUid,
|
||||
"--es", "action", policy == ALLOW ? "allow" : "deny",
|
||||
NULL
|
||||
};
|
||||
silent_run(notify_command);
|
||||
}
|
||||
}
|
||||
|
||||
void app_send_request(struct su_context *ctx) {
|
||||
char user[16];
|
||||
int notify = setup_user(ctx, user);
|
||||
|
||||
char activity[128];
|
||||
sprintf(activity, ACTION_REQUEST, ctx->info->str.s[SU_MANAGER]);
|
||||
|
||||
char *request_command[] = {
|
||||
AM_PATH, "start", "-n",
|
||||
activity,
|
||||
"--user", user,
|
||||
"--es", "socket", ctx->sock_path,
|
||||
"--ez", "timeout", notify ? "false" : "true",
|
||||
NULL
|
||||
};
|
||||
silent_run(request_command);
|
||||
|
||||
// Send notice to user to tell them root is managed by owner
|
||||
if (notify) {
|
||||
sprintf(user, "%d", notify);
|
||||
sprintf(activity, ACTION_RESULT, ctx->info->str.s[SU_MANAGER]);
|
||||
char *notify_command[] = {
|
||||
AM_PATH, "broadcast", "-n",
|
||||
activity,
|
||||
"--user", user,
|
||||
"--ei", "mode", "2",
|
||||
NULL
|
||||
};
|
||||
silent_run(notify_command);
|
||||
}
|
||||
}
|
109
native/jni/su/connect.c
Normal file
109
native/jni/su/connect.c
Normal file
@ -0,0 +1,109 @@
|
||||
/*
|
||||
** Copyright 2018, John Wu (@topjohnwu)
|
||||
** Copyright 2010, Adam Shanks (@ChainsDD)
|
||||
** Copyright 2008, Zinx Verituse (@zinxv)
|
||||
**
|
||||
*/
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "magisk.h"
|
||||
#include "daemon.h"
|
||||
#include "su.h"
|
||||
|
||||
#define AM_PATH "/system/bin/app_process", "/system/bin", "com.android.commands.am.Am"
|
||||
|
||||
static char *get_command(const struct su_request *to) {
|
||||
if (to->command)
|
||||
return to->command;
|
||||
if (to->shell)
|
||||
return to->shell;
|
||||
return DEFAULT_SHELL;
|
||||
}
|
||||
|
||||
static void silent_run(char * const args[]) {
|
||||
set_identity(0);
|
||||
if (fork())
|
||||
return;
|
||||
int zero = open("/dev/zero", O_RDONLY | O_CLOEXEC);
|
||||
dup2(zero, 0);
|
||||
int null = open("/dev/null", O_WRONLY | O_CLOEXEC);
|
||||
dup2(null, 1);
|
||||
dup2(null, 2);
|
||||
setenv("CLASSPATH", "/system/framework/am.jar", 1);
|
||||
execv(args[0], args);
|
||||
PLOGE("exec am");
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
static void setup_user(char *user) {
|
||||
switch (su_ctx->info->dbs.v[SU_MULTIUSER_MODE]) {
|
||||
case MULTIUSER_MODE_OWNER_ONLY:
|
||||
case MULTIUSER_MODE_OWNER_MANAGED:
|
||||
sprintf(user, "%d", 0);
|
||||
break;
|
||||
case MULTIUSER_MODE_USER:
|
||||
sprintf(user, "%d", su_ctx->info->uid / 100000);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void app_log() {
|
||||
char user[8];
|
||||
setup_user(user);
|
||||
|
||||
char fromUid[8];
|
||||
sprintf(fromUid, "%d",
|
||||
su_ctx->info->dbs.v[SU_MULTIUSER_MODE] == MULTIUSER_MODE_OWNER_MANAGED ?
|
||||
su_ctx->info->uid % 100000 : su_ctx->info->uid);
|
||||
|
||||
char toUid[8];
|
||||
sprintf(toUid, "%d", su_ctx->to.uid);
|
||||
|
||||
char pid[8];
|
||||
sprintf(pid, "%d", su_ctx->pid);
|
||||
|
||||
char policy[2];
|
||||
sprintf(policy, "%d", su_ctx->info->access.policy);
|
||||
|
||||
char *cmd[] = {
|
||||
AM_PATH, "broadcast",
|
||||
"-a", "android.intent.action.BOOT_COMPLETED",
|
||||
"-p", su_ctx->info->str.s[SU_MANAGER],
|
||||
"--user", user,
|
||||
"--es", "action", "log",
|
||||
"--ei", "from.uid", fromUid,
|
||||
"--ei", "to.uid", toUid,
|
||||
"--ei", "pid", pid,
|
||||
"--ei", "policy", policy,
|
||||
"--es", "command", get_command(&su_ctx->to),
|
||||
NULL
|
||||
};
|
||||
silent_run(cmd);
|
||||
}
|
||||
|
||||
void app_connect(const char *socket) {
|
||||
char user[8];
|
||||
setup_user(user);
|
||||
char *cmd[] = {
|
||||
AM_PATH, "broadcast",
|
||||
"-a", "android.intent.action.BOOT_COMPLETED",
|
||||
"-p", su_ctx->info->str.s[SU_MANAGER],
|
||||
"--user", user,
|
||||
"--es", "action", "request",
|
||||
"--es", "socket", (char *) socket,
|
||||
NULL
|
||||
};
|
||||
silent_run(cmd);
|
||||
}
|
||||
|
||||
void socket_send_request(int fd) {
|
||||
write_key_token(fd, "uid", su_ctx->info->uid);
|
||||
write_string_be(fd, "eof");
|
||||
}
|
||||
|
@ -25,6 +25,7 @@
|
||||
#include <selinux/selinux.h>
|
||||
|
||||
#include "magisk.h"
|
||||
#include "daemon.h"
|
||||
#include "utils.h"
|
||||
#include "su.h"
|
||||
|
||||
@ -44,11 +45,9 @@ static void usage(int status) {
|
||||
" --preserve-environment preserve the entire environment\n"
|
||||
" -s, --shell SHELL use SHELL instead of the default " DEFAULT_SHELL "\n"
|
||||
" -v, --version display version number and exit\n"
|
||||
" -V display version code and exit,\n"
|
||||
" this is used almost exclusively by Superuser.apk\n"
|
||||
" -V display version code and exit\n"
|
||||
" -mm, -M,\n"
|
||||
" --mount-master run in the global mount namespace,\n"
|
||||
" use if you need to publicly apply mounts\n");
|
||||
" --mount-master force run in the global mount namespace\n");
|
||||
exit2(status);
|
||||
}
|
||||
|
||||
@ -64,20 +63,20 @@ static char *concat_commands(int argc, char *argv[]) {
|
||||
return strdup(command);
|
||||
}
|
||||
|
||||
static void populate_environment(const struct su_context *ctx) {
|
||||
static void populate_environment() {
|
||||
struct passwd *pw;
|
||||
|
||||
if (ctx->to.keepenv)
|
||||
if (su_ctx->to.keepenv)
|
||||
return;
|
||||
|
||||
pw = getpwuid(ctx->to.uid);
|
||||
pw = getpwuid(su_ctx->to.uid);
|
||||
if (pw) {
|
||||
setenv("HOME", pw->pw_dir, 1);
|
||||
if (ctx->to.shell)
|
||||
setenv("SHELL", ctx->to.shell, 1);
|
||||
if (su_ctx->to.shell)
|
||||
setenv("SHELL", su_ctx->to.shell, 1);
|
||||
else
|
||||
setenv("SHELL", DEFAULT_SHELL, 1);
|
||||
if (ctx->to.login || ctx->to.uid) {
|
||||
if (su_ctx->to.login || su_ctx->to.uid) {
|
||||
setenv("USER", pw->pw_name, 1);
|
||||
setenv("LOGNAME", pw->pw_name, 1);
|
||||
}
|
||||
@ -103,9 +102,6 @@ void set_identity(unsigned uid) {
|
||||
static __attribute__ ((noreturn)) void allow() {
|
||||
char* argv[] = { NULL, NULL, NULL, NULL };
|
||||
|
||||
if (su_ctx->info->access.notify || su_ctx->info->access.log)
|
||||
app_send_result(su_ctx, ALLOW);
|
||||
|
||||
if (su_ctx->to.login)
|
||||
argv[0] = "-";
|
||||
else
|
||||
@ -118,9 +114,12 @@ static __attribute__ ((noreturn)) void allow() {
|
||||
|
||||
// Setup shell
|
||||
umask(022);
|
||||
populate_environment(su_ctx);
|
||||
populate_environment();
|
||||
set_identity(su_ctx->to.uid);
|
||||
|
||||
if (su_ctx->info->access.notify || su_ctx->info->access.log)
|
||||
app_log();
|
||||
|
||||
execvp(su_ctx->to.shell, argv);
|
||||
fprintf(stderr, "Cannot execute %s: %s\n", su_ctx->to.shell, strerror(errno));
|
||||
PLOGE("exec");
|
||||
@ -129,25 +128,13 @@ static __attribute__ ((noreturn)) void allow() {
|
||||
|
||||
static __attribute__ ((noreturn)) void deny() {
|
||||
if (su_ctx->info->access.notify || su_ctx->info->access.log)
|
||||
app_send_result(su_ctx, DENY);
|
||||
app_log();
|
||||
|
||||
LOGW("su: request rejected (%u->%u)", su_ctx->info->uid, su_ctx->to.uid);
|
||||
fprintf(stderr, "%s\n", strerror(EACCES));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
static void socket_cleanup() {
|
||||
if (su_ctx && su_ctx->sock_path[0]) {
|
||||
unlink(su_ctx->sock_path);
|
||||
su_ctx->sock_path[0] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
static void cleanup_signal(int sig) {
|
||||
socket_cleanup();
|
||||
exit2(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
__attribute__ ((noreturn)) void exit2(int status) {
|
||||
// Handle the pipe, or the daemon will get stuck
|
||||
if (su_ctx->pipefd[0] >= 0) {
|
||||
@ -159,8 +146,7 @@ __attribute__ ((noreturn)) void exit2(int status) {
|
||||
}
|
||||
|
||||
int su_daemon_main(int argc, char **argv) {
|
||||
int c, socket_serv_fd, fd;
|
||||
char result[64];
|
||||
int c;
|
||||
struct option long_opts[] = {
|
||||
{ "command", required_argument, NULL, 'c' },
|
||||
{ "help", no_argument, NULL, 'h' },
|
||||
@ -247,28 +233,20 @@ int su_daemon_main(int argc, char **argv) {
|
||||
// Change directory to cwd
|
||||
chdir(su_ctx->cwd);
|
||||
|
||||
// New request or no db exist, notify user for response
|
||||
if (su_ctx->pipefd[0] >= 0) {
|
||||
socket_serv_fd = socket_create_temp(su_ctx->sock_path, sizeof(su_ctx->sock_path));
|
||||
setup_sighandlers(cleanup_signal);
|
||||
// Create random socket
|
||||
struct sockaddr_un addr;
|
||||
int sockfd = create_rand_socket(&addr);
|
||||
|
||||
// Start activity
|
||||
app_send_request(su_ctx);
|
||||
// Connect Magisk Manager
|
||||
app_connect(addr.sun_path + 1);
|
||||
int fd = socket_accept(sockfd, 60);
|
||||
|
||||
atexit(socket_cleanup);
|
||||
|
||||
fd = socket_accept(socket_serv_fd);
|
||||
socket_send_request(fd, su_ctx);
|
||||
socket_receive_result(fd, result, sizeof(result));
|
||||
socket_send_request(fd);
|
||||
su_ctx->info->access.policy = read_int_be(fd);
|
||||
|
||||
close(fd);
|
||||
close(socket_serv_fd);
|
||||
socket_cleanup();
|
||||
|
||||
if (strcmp(result, "socket:ALLOW") == 0)
|
||||
su_ctx->info->access.policy = ALLOW;
|
||||
else
|
||||
su_ctx->info->access.policy = DENY;
|
||||
close(sockfd);
|
||||
|
||||
// Report the policy to main daemon
|
||||
xwrite(su_ctx->pipefd[1], &su_ctx->info->access.policy, sizeof(policy_t));
|
||||
@ -276,9 +254,6 @@ int su_daemon_main(int argc, char **argv) {
|
||||
close(su_ctx->pipefd[1]);
|
||||
}
|
||||
|
||||
if (su_ctx->info->access.policy == ALLOW)
|
||||
allow();
|
||||
else
|
||||
deny();
|
||||
su_ctx->info->access.policy == ALLOW ? allow() : deny();
|
||||
}
|
||||
|
||||
|
@ -12,11 +12,6 @@
|
||||
#include "list.h"
|
||||
|
||||
#define MAGISKSU_VER_STR xstr(MAGISK_VERSION) ":MAGISKSU (topjohnwu)"
|
||||
|
||||
// This is used if wrapping the fragment classes and activities
|
||||
// with classes in another package.
|
||||
#define REQUESTOR_PREFIX JAVA_PACKAGE_NAME ".superuser"
|
||||
|
||||
#define DEFAULT_SHELL "/system/bin/sh"
|
||||
|
||||
struct su_info {
|
||||
@ -49,7 +44,6 @@ struct su_context {
|
||||
struct su_request to;
|
||||
pid_t pid;
|
||||
char cwd[PATH_MAX];
|
||||
char sock_path[PATH_MAX];
|
||||
int pipefd[2];
|
||||
};
|
||||
|
||||
@ -61,16 +55,11 @@ int su_daemon_main(int argc, char **argv);
|
||||
__attribute__ ((noreturn)) void exit2(int status);
|
||||
void set_identity(unsigned uid);
|
||||
|
||||
// su_client.c
|
||||
// connect.c
|
||||
|
||||
int socket_create_temp(char *path, size_t len);
|
||||
int socket_accept(int serv_fd);
|
||||
void socket_send_request(int fd, const struct su_context *ctx);
|
||||
void socket_receive_result(int fd, char *result, ssize_t result_len);
|
||||
void app_log();
|
||||
|
||||
// activity.c
|
||||
|
||||
void app_send_result(struct su_context *ctx, policy_t policy);
|
||||
void app_send_request(struct su_context *ctx);
|
||||
void app_connect(const char *socket);
|
||||
void socket_send_request(int fd);
|
||||
|
||||
#endif
|
||||
|
@ -1,108 +0,0 @@
|
||||
/* su_socket.c - Functions for communication to client
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <endian.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <selinux/selinux.h>
|
||||
|
||||
#include "magisk.h"
|
||||
#include "utils.h"
|
||||
#include "su.h"
|
||||
#include "magiskpolicy.h"
|
||||
|
||||
int socket_create_temp(char *path, size_t len) {
|
||||
int fd;
|
||||
struct sockaddr_un sun;
|
||||
|
||||
fd = xsocket(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0);
|
||||
if (fcntl(fd, F_SETFD, FD_CLOEXEC)) {
|
||||
PLOGE("fcntl FD_CLOEXEC");
|
||||
}
|
||||
|
||||
memset(&sun, 0, sizeof(sun));
|
||||
sun.sun_family = AF_LOCAL;
|
||||
snprintf(path, len, "/dev/.socket%d", getpid());
|
||||
strcpy(sun.sun_path, path);
|
||||
|
||||
/*
|
||||
* Delete the socket to protect from situations when
|
||||
* something bad occured previously and the kernel reused pid from that process.
|
||||
* Small probability, isn't it.
|
||||
*/
|
||||
unlink(path);
|
||||
|
||||
xbind(fd, (struct sockaddr*) &sun, sizeof(sun));
|
||||
xlisten(fd, 1);
|
||||
|
||||
// Set attributes so requester can access it
|
||||
setfilecon(path, "u:object_r:"SEPOL_FILE_DOMAIN":s0");
|
||||
chown(path, su_ctx->info->manager_stat.st_uid, su_ctx->info->manager_stat.st_gid);
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
int socket_accept(int serv_fd) {
|
||||
struct timeval tv;
|
||||
fd_set fds;
|
||||
int rc;
|
||||
|
||||
/* Wait 60 seconds for a connection, then give up. */
|
||||
tv.tv_sec = 60;
|
||||
tv.tv_usec = 0;
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(serv_fd, &fds);
|
||||
do {
|
||||
rc = select(serv_fd + 1, &fds, NULL, NULL, &tv);
|
||||
} while (rc < 0 && errno == EINTR);
|
||||
if (rc < 1) {
|
||||
PLOGE("select");
|
||||
}
|
||||
|
||||
return xaccept4(serv_fd, NULL, NULL, SOCK_CLOEXEC);
|
||||
}
|
||||
|
||||
#define write_data(fd, data, data_len) \
|
||||
do { \
|
||||
uint32_t __len = htonl(data_len); \
|
||||
__len = write((fd), &__len, sizeof(__len)); \
|
||||
if (__len != sizeof(__len)) { \
|
||||
PLOGE("write(" #data ")"); \
|
||||
} \
|
||||
__len = write((fd), data, data_len); \
|
||||
if (__len != data_len) { \
|
||||
PLOGE("write(" #data ")"); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define write_string_data(fd, name, data) \
|
||||
do { \
|
||||
write_data(fd, name, strlen(name)); \
|
||||
write_data(fd, data, strlen(data)); \
|
||||
} while (0)
|
||||
|
||||
// stringify everything.
|
||||
#define write_token(fd, name, data) \
|
||||
do { \
|
||||
char buf[16]; \
|
||||
snprintf(buf, sizeof(buf), "%d", data); \
|
||||
write_string_data(fd, name, buf); \
|
||||
} while (0)
|
||||
|
||||
void socket_send_request(int fd, const struct su_context *ctx) {
|
||||
write_token(fd, "uid", ctx->info->uid);
|
||||
write_token(fd, "eof", 1);
|
||||
}
|
||||
|
||||
void socket_receive_result(int fd, char *result, ssize_t result_len) {
|
||||
ssize_t len;
|
||||
len = xread(fd, result, result_len - 1);
|
||||
result[len] = '\0';
|
||||
}
|
Loading…
Reference in New Issue
Block a user