2018-09-16 08:16:18 +00:00
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/wait.h>
|
|
|
|
|
2020-03-09 08:50:30 +00:00
|
|
|
#include <utils.hpp>
|
2020-09-10 07:38:29 +00:00
|
|
|
#include <selinux.hpp>
|
2019-02-10 08:57:51 +00:00
|
|
|
|
2020-03-09 08:50:30 +00:00
|
|
|
#include "su.hpp"
|
2018-09-16 08:16:18 +00:00
|
|
|
|
2019-07-07 07:31:49 +00:00
|
|
|
using namespace std;
|
|
|
|
|
2019-11-07 22:41:59 +00:00
|
|
|
enum {
|
2020-12-31 06:11:24 +00:00
|
|
|
NAMED_ACTIVITY,
|
|
|
|
PKG_ACTIVITY,
|
|
|
|
CONTENT_PROVIDER
|
2019-11-07 22:41:59 +00:00
|
|
|
};
|
|
|
|
|
Use ContentProvider call method for communication
Previously, we use either BroadcastReceivers or Activities to receive
messages from our native daemon, but both have their own downsides.
Some OEMs blocks broadcasts if the app is not running in the background,
regardless of who the caller is. Activities on the other hand, despite
working 100% of the time, will steal the focus of the current foreground
app, even though we are just doing some logging and showing a toast.
In addition, since stubs for hiding Magisk Manager is introduced, our
only communication method is left with the broadcast option, as
only broadcasting allows targeting a specific package name, not a
component name (which will be obfuscated in the case of stubs).
To make sure root requests will work on all devices, Magisk had to do
some experiments every boot to test whether broadcast is deliverable or
not. This makes the whole thing even more complicated then ever.
So lets take a look at another kind of component in Android apps:
ContentProviders. It is a vital part of Android's ecosystem, and as far
as I know no OEMs will block requests to ContentProviders (or else
tons of functionality will break catastrophically). Starting at API 11,
the system supports calling a specific method in ContentProviders,
optionally sending extra data along with the method call. This is
perfect for the native daemon to start a communication with Magisk
Manager. Another cool thing is that we no longer need to know the
component name of the reciever, as ContentProviders identify themselves
with an "authority" name, which in Magisk Manager's case is tied to the
package name. We already have a mechanism to keep track of our current
manager package name, so this works out of the box.
So yay! No more flaky broadcast tests, no more stupid OEMs blocking
broadcasts for some bizzare reasons. This method should in theory
work on almost all devices and situations.
2019-11-04 19:32:28 +00:00
|
|
|
#define CALL_PROVIDER \
|
|
|
|
"/system/bin/app_process", "/system/bin", "com.android.commands.content.Content", \
|
2019-11-07 22:41:59 +00:00
|
|
|
"call", "--uri", target, "--user", user, "--method", action
|
Introduce component agnostic communication
Usually, the communication between native and the app is done via
sending intents to either broadcast or activity. These communication
channels are for launching root requests dialogs, sending root request
notifications (the toast you see when an app gained root access), and
root request logging.
Sending intents by am (activity manager) usually requires specifying
the component name in the format of <pkg>/<class name>. This means parts
of Magisk Manager cannot be randomized or else the native daemon is
unable to know where to send data to the app.
On modern Android (not sure which API is it introduced), it is possible
to send broadcasts to a package, not a specific component. Which
component will receive the intent depends on the intent filter declared
in AndroidManifest.xml. Since we already have a mechanism in native code
to keep track of the package name of Magisk Manager, this makes it
perfect to pass intents to Magisk Manager that have components being
randomly obfuscated (stub APKs).
There are a few caveats though. Although this broadcasting method works
perfectly fine on AOSP and most systems, there are OEMs out there
shipping ROMs blocking broadcasts unexpectedly. In order to make sure
Magisk works in all kinds of scenarios, we run actual tests every boot
to determine which communication method should be used.
We have 3 methods in total, ordered in preference:
1. Broadcasting to a package
2. Broadcasting to a specific component
3. Starting a specific activity component
Method 3 will always work on any device, but the downside is anytime
a communication happens, Magisk Manager will steal foreground focus
regardless of whether UI is drawn. Method 1 is the only way to support
obfuscated stub APKs. The communication test will test method 1 and 2,
and if Magisk Manager is able to receive the messages, it will then
update the daemon configuration to use whichever is preferable. If none
of the broadcasts can be delivered, then the fallback method 3 will be
used.
2019-10-21 17:59:04 +00:00
|
|
|
|
2019-11-07 22:41:59 +00:00
|
|
|
#define START_ACTIVITY \
|
|
|
|
"/system/bin/app_process", "/system/bin", "com.android.commands.am.Am", \
|
|
|
|
"start", "-p", target, "--user", user, "-a", "android.intent.action.VIEW", \
|
|
|
|
"-f", "0x18000020", "--es", "action", action
|
Use ContentProvider call method for communication
Previously, we use either BroadcastReceivers or Activities to receive
messages from our native daemon, but both have their own downsides.
Some OEMs blocks broadcasts if the app is not running in the background,
regardless of who the caller is. Activities on the other hand, despite
working 100% of the time, will steal the focus of the current foreground
app, even though we are just doing some logging and showing a toast.
In addition, since stubs for hiding Magisk Manager is introduced, our
only communication method is left with the broadcast option, as
only broadcasting allows targeting a specific package name, not a
component name (which will be obfuscated in the case of stubs).
To make sure root requests will work on all devices, Magisk had to do
some experiments every boot to test whether broadcast is deliverable or
not. This makes the whole thing even more complicated then ever.
So lets take a look at another kind of component in Android apps:
ContentProviders. It is a vital part of Android's ecosystem, and as far
as I know no OEMs will block requests to ContentProviders (or else
tons of functionality will break catastrophically). Starting at API 11,
the system supports calling a specific method in ContentProviders,
optionally sending extra data along with the method call. This is
perfect for the native daemon to start a communication with Magisk
Manager. Another cool thing is that we no longer need to know the
component name of the reciever, as ContentProviders identify themselves
with an "authority" name, which in Magisk Manager's case is tied to the
package name. We already have a mechanism to keep track of our current
manager package name, so this works out of the box.
So yay! No more flaky broadcast tests, no more stupid OEMs blocking
broadcasts for some bizzare reasons. This method should in theory
work on almost all devices and situations.
2019-11-04 19:32:28 +00:00
|
|
|
|
2019-11-07 22:41:59 +00:00
|
|
|
// 0x18000020 = FLAG_ACTIVITY_NEW_TASK|FLAG_ACTIVITY_MULTIPLE_TASK|FLAG_INCLUDE_STOPPED_PACKAGES
|
Use ContentProvider call method for communication
Previously, we use either BroadcastReceivers or Activities to receive
messages from our native daemon, but both have their own downsides.
Some OEMs blocks broadcasts if the app is not running in the background,
regardless of who the caller is. Activities on the other hand, despite
working 100% of the time, will steal the focus of the current foreground
app, even though we are just doing some logging and showing a toast.
In addition, since stubs for hiding Magisk Manager is introduced, our
only communication method is left with the broadcast option, as
only broadcasting allows targeting a specific package name, not a
component name (which will be obfuscated in the case of stubs).
To make sure root requests will work on all devices, Magisk had to do
some experiments every boot to test whether broadcast is deliverable or
not. This makes the whole thing even more complicated then ever.
So lets take a look at another kind of component in Android apps:
ContentProviders. It is a vital part of Android's ecosystem, and as far
as I know no OEMs will block requests to ContentProviders (or else
tons of functionality will break catastrophically). Starting at API 11,
the system supports calling a specific method in ContentProviders,
optionally sending extra data along with the method call. This is
perfect for the native daemon to start a communication with Magisk
Manager. Another cool thing is that we no longer need to know the
component name of the reciever, as ContentProviders identify themselves
with an "authority" name, which in Magisk Manager's case is tied to the
package name. We already have a mechanism to keep track of our current
manager package name, so this works out of the box.
So yay! No more flaky broadcast tests, no more stupid OEMs blocking
broadcasts for some bizzare reasons. This method should in theory
work on almost all devices and situations.
2019-11-04 19:32:28 +00:00
|
|
|
|
|
|
|
#define get_user(info) \
|
|
|
|
(info->cfg[SU_MULTIUSER_MODE] == MULTIUSER_MODE_USER \
|
2019-11-07 22:41:59 +00:00
|
|
|
? info->uid / 100000 : 0)
|
Use ContentProvider call method for communication
Previously, we use either BroadcastReceivers or Activities to receive
messages from our native daemon, but both have their own downsides.
Some OEMs blocks broadcasts if the app is not running in the background,
regardless of who the caller is. Activities on the other hand, despite
working 100% of the time, will steal the focus of the current foreground
app, even though we are just doing some logging and showing a toast.
In addition, since stubs for hiding Magisk Manager is introduced, our
only communication method is left with the broadcast option, as
only broadcasting allows targeting a specific package name, not a
component name (which will be obfuscated in the case of stubs).
To make sure root requests will work on all devices, Magisk had to do
some experiments every boot to test whether broadcast is deliverable or
not. This makes the whole thing even more complicated then ever.
So lets take a look at another kind of component in Android apps:
ContentProviders. It is a vital part of Android's ecosystem, and as far
as I know no OEMs will block requests to ContentProviders (or else
tons of functionality will break catastrophically). Starting at API 11,
the system supports calling a specific method in ContentProviders,
optionally sending extra data along with the method call. This is
perfect for the native daemon to start a communication with Magisk
Manager. Another cool thing is that we no longer need to know the
component name of the reciever, as ContentProviders identify themselves
with an "authority" name, which in Magisk Manager's case is tied to the
package name. We already have a mechanism to keep track of our current
manager package name, so this works out of the box.
So yay! No more flaky broadcast tests, no more stupid OEMs blocking
broadcasts for some bizzare reasons. This method should in theory
work on almost all devices and situations.
2019-11-04 19:32:28 +00:00
|
|
|
|
2019-11-07 22:41:59 +00:00
|
|
|
#define get_cmd(to) \
|
2021-01-12 08:07:48 +00:00
|
|
|
(to.command.empty() ? (to.shell.empty() ? DEFAULT_SHELL : to.shell.data()) : to.command.data())
|
2019-11-07 22:41:59 +00:00
|
|
|
|
|
|
|
class Extra {
|
2020-12-31 06:11:24 +00:00
|
|
|
const char *key;
|
|
|
|
enum {
|
|
|
|
INT,
|
|
|
|
BOOL,
|
|
|
|
STRING
|
|
|
|
} type;
|
|
|
|
union {
|
|
|
|
int int_val;
|
|
|
|
bool bool_val;
|
2021-01-12 08:07:48 +00:00
|
|
|
const char *str_val;
|
2020-12-31 06:11:24 +00:00
|
|
|
};
|
|
|
|
char buf[32];
|
2019-11-07 22:41:59 +00:00
|
|
|
public:
|
2020-12-31 06:11:24 +00:00
|
|
|
Extra(const char *k, int v): key(k), type(INT), int_val(v) {}
|
|
|
|
Extra(const char *k, bool v): key(k), type(BOOL), bool_val(v) {}
|
|
|
|
Extra(const char *k, const char *v): key(k), type(STRING), str_val(v) {}
|
|
|
|
|
|
|
|
void add_intent(vector<const char *> &vec) {
|
|
|
|
const char *val;
|
|
|
|
switch (type) {
|
|
|
|
case INT:
|
|
|
|
vec.push_back("--ei");
|
|
|
|
sprintf(buf, "%d", int_val);
|
|
|
|
val = buf;
|
|
|
|
break;
|
|
|
|
case BOOL:
|
|
|
|
vec.push_back("--ez");
|
|
|
|
val = bool_val ? "true" : "false";
|
|
|
|
break;
|
|
|
|
case STRING:
|
|
|
|
vec.push_back("--es");
|
|
|
|
val = str_val;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
vec.push_back(key);
|
|
|
|
vec.push_back(val);
|
|
|
|
}
|
|
|
|
|
|
|
|
void add_bind(vector<const char *> &vec) {
|
|
|
|
switch (type) {
|
|
|
|
case INT:
|
|
|
|
sprintf(buf, "%s:i:%d", key, int_val);
|
|
|
|
break;
|
|
|
|
case BOOL:
|
|
|
|
sprintf(buf, "%s:b:%s", key, bool_val ? "true" : "false");
|
|
|
|
break;
|
|
|
|
case STRING:
|
|
|
|
sprintf(buf, "%s:s:%s", key, str_val);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
vec.push_back("--extra");
|
|
|
|
vec.push_back(buf);
|
|
|
|
}
|
2019-11-07 22:41:59 +00:00
|
|
|
};
|
|
|
|
|
2020-06-19 10:52:25 +00:00
|
|
|
static bool check_no_error(int fd) {
|
2020-12-31 06:11:24 +00:00
|
|
|
char buf[1024];
|
|
|
|
auto out = xopen_file(fd, "r");
|
|
|
|
while (fgets(buf, sizeof(buf), out.get())) {
|
|
|
|
if (strncmp(buf, "Error", 5) == 0)
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
2018-09-16 08:16:18 +00:00
|
|
|
}
|
|
|
|
|
2019-11-07 22:41:59 +00:00
|
|
|
static void exec_cmd(const char *action, vector<Extra> &data,
|
2020-12-31 06:11:24 +00:00
|
|
|
const shared_ptr<su_info> &info, int mode = CONTENT_PROVIDER) {
|
|
|
|
char target[128];
|
|
|
|
char user[4];
|
|
|
|
sprintf(user, "%d", get_user(info));
|
|
|
|
|
|
|
|
// First try content provider call method
|
|
|
|
if (mode >= CONTENT_PROVIDER) {
|
|
|
|
sprintf(target, "content://%s.provider", info->str[SU_MANAGER].data());
|
|
|
|
vector<const char *> args{ CALL_PROVIDER };
|
|
|
|
for (auto &e : data) {
|
|
|
|
e.add_bind(args);
|
|
|
|
}
|
|
|
|
args.push_back(nullptr);
|
|
|
|
exec_t exec {
|
|
|
|
.err = true,
|
|
|
|
.fd = -1,
|
|
|
|
.pre_exec = [] { setenv("CLASSPATH", "/system/framework/content.jar", 1); },
|
|
|
|
.argv = args.data()
|
|
|
|
};
|
|
|
|
exec_command_sync(exec);
|
|
|
|
if (check_no_error(exec.fd))
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
vector<const char *> args{ START_ACTIVITY };
|
|
|
|
for (auto &e : data) {
|
|
|
|
e.add_intent(args);
|
|
|
|
}
|
|
|
|
args.push_back(nullptr);
|
|
|
|
exec_t exec {
|
|
|
|
.err = true,
|
|
|
|
.fd = -1,
|
|
|
|
.pre_exec = [] { setenv("CLASSPATH", "/system/framework/am.jar", 1); },
|
|
|
|
.argv = args.data()
|
|
|
|
};
|
|
|
|
|
|
|
|
if (mode >= PKG_ACTIVITY) {
|
|
|
|
// Then try start activity without component name
|
|
|
|
strcpy(target, info->str[SU_MANAGER].data());
|
|
|
|
exec_command_sync(exec);
|
|
|
|
if (check_no_error(exec.fd))
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Finally, fallback to start activity with component name
|
|
|
|
args[4] = "-n";
|
|
|
|
sprintf(target, "%s/a.m", info->str[SU_MANAGER].data());
|
|
|
|
exec.fd = -2;
|
|
|
|
exec.fork = fork_dont_care;
|
|
|
|
exec_command(exec);
|
2018-09-16 08:16:18 +00:00
|
|
|
}
|
|
|
|
|
2019-07-07 07:31:49 +00:00
|
|
|
void app_log(const su_context &ctx) {
|
2020-12-31 06:11:24 +00:00
|
|
|
if (fork_dont_care() == 0) {
|
|
|
|
vector<Extra> extras;
|
|
|
|
extras.reserve(6);
|
|
|
|
extras.emplace_back("from.uid", ctx.info->uid);
|
|
|
|
extras.emplace_back("to.uid", ctx.req.uid);
|
|
|
|
extras.emplace_back("pid", ctx.pid);
|
|
|
|
extras.emplace_back("policy", ctx.info->access.policy);
|
|
|
|
extras.emplace_back("command", get_cmd(ctx.req));
|
|
|
|
extras.emplace_back("notify", (bool) ctx.info->access.notify);
|
|
|
|
|
|
|
|
exec_cmd("log", extras, ctx.info);
|
|
|
|
exit(0);
|
|
|
|
}
|
2018-09-16 08:16:18 +00:00
|
|
|
}
|
|
|
|
|
2019-07-07 07:31:49 +00:00
|
|
|
void app_notify(const su_context &ctx) {
|
2020-12-31 06:11:24 +00:00
|
|
|
if (fork_dont_care() == 0) {
|
|
|
|
vector<Extra> extras;
|
|
|
|
extras.reserve(2);
|
|
|
|
extras.emplace_back("from.uid", ctx.info->uid);
|
|
|
|
extras.emplace_back("policy", ctx.info->access.policy);
|
|
|
|
|
|
|
|
exec_cmd("notify", extras, ctx.info);
|
|
|
|
exit(0);
|
|
|
|
}
|
2018-10-28 02:06:24 +00:00
|
|
|
}
|
|
|
|
|
2020-09-10 07:38:29 +00:00
|
|
|
int app_request(const shared_ptr<su_info> &info) {
|
2020-12-31 06:11:24 +00:00
|
|
|
// Create FIFO
|
|
|
|
char fifo[64];
|
|
|
|
strcpy(fifo, "/dev/socket/");
|
|
|
|
gen_rand_str(fifo + 12, 32, true);
|
|
|
|
mkfifo(fifo, 0600);
|
|
|
|
chown(fifo, info->mgr_st.st_uid, info->mgr_st.st_gid);
|
|
|
|
setfilecon(fifo, "u:object_r:" SEPOL_FILE_TYPE ":s0");
|
|
|
|
|
|
|
|
// Send request
|
|
|
|
vector<Extra> extras;
|
|
|
|
extras.reserve(2);
|
|
|
|
extras.emplace_back("fifo", fifo);
|
|
|
|
extras.emplace_back("uid", info->uid);
|
|
|
|
exec_cmd("request", extras, info, PKG_ACTIVITY);
|
|
|
|
|
|
|
|
// Wait for data input for at most 70 seconds
|
|
|
|
int fd = xopen(fifo, O_RDONLY | O_CLOEXEC);
|
|
|
|
struct pollfd pfd = {
|
|
|
|
.fd = fd,
|
|
|
|
.events = POLL_IN
|
|
|
|
};
|
|
|
|
if (xpoll(&pfd, 1, 70 * 1000) <= 0) {
|
|
|
|
close(fd);
|
|
|
|
fd = -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
unlink(fifo);
|
|
|
|
return fd;
|
2018-09-16 08:16:18 +00:00
|
|
|
}
|