Application Component Granularity MagiskHide
Before switching to the new MagiskHide implementation (APK inotify), logcat parsing provides us lots of information to target a process. We were targeting components so that apps with multi-processes can still be hidden properly. After switching to the new implementation, our granularity is limited to the UID of the process. This is especially dangerous since Android allow apps signed with the same signature to share UIDs, and many system apps utilize this for elevated permissions for some services. This commit introduces process name matching. We could not blanketly target an UID, so the workaround is to verify its process name before unmounting. The tricky thing is that any app developer is allowed to name the process of its component to whatever they want; there is no 'one rule to catch them all' to target a specific package. As a result, Magisk Manager is updated to scan through all components of all apps, and show different processes of the same app, each as a separate hide target in the list. The hide target database also has to be updated accordingly. Each hide target is now a <package name, process name> pair. The magiskhide CLI and Magisk Manager is updated to support this new target format.
This commit is contained in:
parent
885e3c574b
commit
b1afd554fc
@ -37,12 +37,6 @@ public class Const {
|
||||
public static final int MIN_MODULE_VER = 1500;
|
||||
public static final int SNET_EXT_VER = 12;
|
||||
|
||||
/* A list of apps that should not be shown as hide-able */
|
||||
public static final List<String> HIDE_BLACKLIST = Arrays.asList(
|
||||
App.self.getPackageName(),
|
||||
"com.google.android.gms"
|
||||
);
|
||||
|
||||
public static final int USER_ID = Process.myUid() / 100000;
|
||||
|
||||
public static final class MAGISK_VER {
|
||||
|
@ -2,6 +2,7 @@ package com.topjohnwu.magisk.adapters;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.ComponentInfo;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.AsyncTask;
|
||||
@ -12,7 +13,8 @@ import android.widget.CheckBox;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.App;
|
||||
import com.topjohnwu.magisk.Config;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
@ -20,34 +22,40 @@ import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.internal.UiThreadHandler;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.WorkerThread;
|
||||
import androidx.collection.ArraySet;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import butterknife.BindView;
|
||||
|
||||
public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.ViewHolder> {
|
||||
|
||||
private static PackageInfo PLATFORM;
|
||||
/* A list of apps that should not be shown as hide-able */
|
||||
private static final List<String> HIDE_BLACKLIST = Arrays.asList(
|
||||
App.self.getPackageName(),
|
||||
"android",
|
||||
"com.android.chrome"
|
||||
);
|
||||
private static final String SAFETYNET_PROCESS = "com.google.android.gms.unstable";
|
||||
private static final String GMS_PACKAGE = "com.google.android.gms";
|
||||
|
||||
private List<PackageInfo> fullList, showList;
|
||||
private List<String> hideList;
|
||||
private List<HideAppInfo> fullList, showList;
|
||||
private List<HideTarget> hideList;
|
||||
private PackageManager pm;
|
||||
private boolean showSystem;
|
||||
|
||||
public ApplicationAdapter(Context context) {
|
||||
fullList = showList = Collections.emptyList();
|
||||
hideList = Collections.emptyList();
|
||||
showList = Collections.emptyList();
|
||||
fullList = new ArrayList<>();
|
||||
hideList = new ArrayList<>();
|
||||
pm = context.getPackageManager();
|
||||
showSystem = false;
|
||||
if (PLATFORM == null) {
|
||||
try {
|
||||
PLATFORM = pm.getPackageInfo("android", PackageManager.GET_SIGNATURES);
|
||||
} catch (PackageManager.NameNotFoundException ignored) {}
|
||||
}
|
||||
showSystem = Config.get(Config.Key.SHOW_SYSTEM_APP);
|
||||
AsyncTask.SERIAL_EXECUTOR.execute(this::loadApps);
|
||||
}
|
||||
|
||||
@ -58,32 +66,42 @@ public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.
|
||||
return new ViewHolder(v);
|
||||
}
|
||||
|
||||
private void addProcesses(Set<String> set, ComponentInfo[] infos) {
|
||||
if (infos != null)
|
||||
for (ComponentInfo info : infos)
|
||||
set.add(info.processName);
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private void loadApps() {
|
||||
fullList = pm.getInstalledPackages(PackageManager.GET_SIGNATURES);
|
||||
hideList = Shell.su("magiskhide --ls").exec().getOut();
|
||||
for (Iterator<PackageInfo> i = fullList.iterator(); i.hasNext(); ) {
|
||||
PackageInfo info = i.next();
|
||||
if (Const.HIDE_BLACKLIST.contains(info.packageName) ||
|
||||
// Get package info with all components
|
||||
List<PackageInfo> installed = pm.getInstalledPackages(
|
||||
PackageManager.GET_ACTIVITIES | PackageManager.GET_SERVICES |
|
||||
PackageManager.GET_RECEIVERS | PackageManager.GET_PROVIDERS);
|
||||
|
||||
fullList.clear();
|
||||
hideList.clear();
|
||||
|
||||
for (String line : Shell.su("magiskhide --ls").exec().getOut())
|
||||
hideList.add(new HideTarget(line));
|
||||
|
||||
for (PackageInfo pkg : installed) {
|
||||
if (!HIDE_BLACKLIST.contains(pkg.packageName) &&
|
||||
/* Do not show disabled apps */
|
||||
!info.applicationInfo.enabled ||
|
||||
/* Never show platform apps */
|
||||
PLATFORM.signatures[0].equals(info.signatures[0])) {
|
||||
i.remove();
|
||||
pkg.applicationInfo.enabled) {
|
||||
// Add all possible process names
|
||||
Set<String> procSet = new ArraySet<>();
|
||||
addProcesses(procSet, pkg.activities);
|
||||
addProcesses(procSet, pkg.services);
|
||||
addProcesses(procSet, pkg.receivers);
|
||||
addProcesses(procSet, pkg.providers);
|
||||
for (String proc : procSet) {
|
||||
fullList.add(new HideAppInfo(pkg.applicationInfo, proc));
|
||||
}
|
||||
}
|
||||
}
|
||||
Collections.sort(fullList, (a, b) -> {
|
||||
boolean ah = hideList.contains(a.packageName);
|
||||
boolean bh = hideList.contains(b.packageName);
|
||||
if (ah == bh) {
|
||||
return Utils.getAppLabel(a.applicationInfo, pm)
|
||||
.compareToIgnoreCase(Utils.getAppLabel(b.applicationInfo, pm));
|
||||
} else if (ah) {
|
||||
return -1;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
});
|
||||
|
||||
Collections.sort(fullList);
|
||||
Topic.publish(false, Topic.MAGISK_HIDE_DONE);
|
||||
}
|
||||
|
||||
@ -93,23 +111,35 @@ public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
||||
ApplicationInfo info = showList.get(position).applicationInfo;
|
||||
HideAppInfo target = showList.get(position);
|
||||
|
||||
holder.appIcon.setImageDrawable(info.loadIcon(pm));
|
||||
holder.appName.setText(Utils.getAppLabel(info, pm));
|
||||
holder.appPackage.setText(info.packageName);
|
||||
holder.appIcon.setImageDrawable(target.info.loadIcon(pm));
|
||||
holder.appName.setText(target.name);
|
||||
holder.process.setText(target.process);
|
||||
if (!target.info.packageName.equals(target.process)) {
|
||||
holder.appPackage.setVisibility(View.VISIBLE);
|
||||
holder.appPackage.setText("(" + target.info.packageName + ")");
|
||||
} else {
|
||||
holder.appPackage.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
holder.checkBox.setOnCheckedChangeListener(null);
|
||||
holder.checkBox.setChecked(hideList.contains(info.packageName));
|
||||
holder.checkBox.setOnCheckedChangeListener((v, isChecked) -> {
|
||||
if (isChecked) {
|
||||
Shell.su("magiskhide --add " + info.packageName).submit();
|
||||
hideList.add(info.packageName);
|
||||
} else {
|
||||
Shell.su("magiskhide --rm " + info.packageName).submit();
|
||||
hideList.remove(info.packageName);
|
||||
}
|
||||
});
|
||||
holder.checkBox.setChecked(target.hidden);
|
||||
if (target.process.equals(SAFETYNET_PROCESS)) {
|
||||
// Do not allow user to not hide SafetyNet
|
||||
holder.checkBox.setOnCheckedChangeListener((v, c) -> holder.checkBox.setChecked(true));
|
||||
} else {
|
||||
holder.checkBox.setOnCheckedChangeListener((v, isChecked) -> {
|
||||
String pair = Utils.fmt("%s %s", target.info.packageName, target.process);
|
||||
if (isChecked) {
|
||||
Shell.su("magiskhide --add " + pair).submit();
|
||||
target.hidden = true;
|
||||
} else {
|
||||
Shell.su("magiskhide --rm " + pair).submit();
|
||||
target.hidden = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -117,36 +147,36 @@ public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.
|
||||
return showList.size();
|
||||
}
|
||||
|
||||
// True if not system app and have launch intent, or user already hidden it
|
||||
private boolean systemFilter(HideAppInfo target) {
|
||||
return showSystem || target.hidden ||
|
||||
((target.info.flags & ApplicationInfo.FLAG_SYSTEM) == 0 &&
|
||||
pm.getLaunchIntentForPackage(target.info.packageName) != null);
|
||||
}
|
||||
|
||||
private boolean contains(String s, String filter) {
|
||||
return s.toLowerCase().contains(filter);
|
||||
}
|
||||
|
||||
// Show if have launch intent or not system app
|
||||
private boolean systemFilter(PackageInfo info) {
|
||||
if (showSystem)
|
||||
private boolean nameFilter(HideAppInfo target, String filter) {
|
||||
if (filter == null || filter.isEmpty())
|
||||
return true;
|
||||
return (info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0 ||
|
||||
pm.getLaunchIntentForPackage(info.packageName) != null;
|
||||
filter = filter.toLowerCase();
|
||||
return contains(target.name, filter) ||
|
||||
contains(target.process, filter) ||
|
||||
contains(target.info.packageName, filter);
|
||||
}
|
||||
|
||||
public void filter(String constraint) {
|
||||
AsyncTask.SERIAL_EXECUTOR.execute(() -> {
|
||||
showList = new ArrayList<>();
|
||||
if (constraint == null || constraint.length() == 0) {
|
||||
for (PackageInfo info : fullList) {
|
||||
if (systemFilter(info))
|
||||
showList.add(info);
|
||||
}
|
||||
} else {
|
||||
String filter = constraint.toLowerCase();
|
||||
for (PackageInfo info : fullList) {
|
||||
if ((contains(Utils.getAppLabel(info.applicationInfo, pm), filter) ||
|
||||
contains(info.packageName, filter)) && systemFilter(info)) {
|
||||
showList.add(info);
|
||||
}
|
||||
}
|
||||
}
|
||||
UiThreadHandler.run(this::notifyDataSetChanged);
|
||||
List<HideAppInfo> newList = new ArrayList<>();
|
||||
for (HideAppInfo target : fullList)
|
||||
if (systemFilter(target) && nameFilter(target, constraint))
|
||||
newList.add(target);
|
||||
UiThreadHandler.run(() -> {
|
||||
showList = newList;
|
||||
notifyDataSetChanged();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -158,6 +188,7 @@ public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.
|
||||
|
||||
@BindView(R.id.app_icon) ImageView appIcon;
|
||||
@BindView(R.id.app_name) TextView appName;
|
||||
@BindView(R.id.process) TextView process;
|
||||
@BindView(R.id.package_name) TextView appPackage;
|
||||
@BindView(R.id.checkbox) CheckBox checkBox;
|
||||
|
||||
@ -166,4 +197,49 @@ public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.
|
||||
new ApplicationAdapter$ViewHolder_ViewBinding(this, itemView);
|
||||
}
|
||||
}
|
||||
|
||||
class HideAppInfo implements Comparable<HideAppInfo> {
|
||||
String process;
|
||||
String name;
|
||||
ApplicationInfo info;
|
||||
boolean hidden;
|
||||
|
||||
HideAppInfo(ApplicationInfo info, String process) {
|
||||
this.process = process;
|
||||
this.info = info;
|
||||
name = Utils.getAppLabel(info, pm);
|
||||
for (HideTarget tgt : hideList) {
|
||||
if (tgt.process.equals(process)) {
|
||||
hidden = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(HideAppInfo o) {
|
||||
return Comparator.comparing((HideAppInfo t) -> t.hidden)
|
||||
.reversed()
|
||||
.thenComparing((a, b) -> a.name.compareToIgnoreCase(b.name))
|
||||
.thenComparing(t -> t.info.packageName)
|
||||
.thenComparing(t -> t.process)
|
||||
.compare(this, o);
|
||||
}
|
||||
}
|
||||
|
||||
class HideTarget {
|
||||
String pkg;
|
||||
String process;
|
||||
|
||||
HideTarget(String line) {
|
||||
String[] split = line.split("\\|");
|
||||
pkg = split[0];
|
||||
if (split.length >= 2) {
|
||||
process = split[1];
|
||||
} else {
|
||||
// Backwards compatibility
|
||||
process = pkg.equals(GMS_PACKAGE) ? SAFETYNET_PROCESS : pkg;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -72,9 +72,7 @@ public class MagiskHideFragment extends BaseFragment implements Topic.Subscriber
|
||||
inflater.inflate(R.menu.menu_magiskhide, menu);
|
||||
search = (SearchView) menu.findItem(R.id.app_search).getActionView();
|
||||
search.setOnQueryTextListener(searchListener);
|
||||
boolean showSystem = Config.get(Config.Key.SHOW_SYSTEM_APP);
|
||||
menu.findItem(R.id.show_system).setChecked(showSystem);
|
||||
adapter.setShowSystem(showSystem);
|
||||
menu.findItem(R.id.show_system).setChecked(Config.get(Config.Key.SHOW_SYSTEM_APP));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -42,15 +42,30 @@
|
||||
android:maxLines="1"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:textIsSelectable="false"
|
||||
app:layout_constraintBottom_toTopOf="@+id/package_name"
|
||||
app:layout_constraintBottom_toTopOf="@+id/process"
|
||||
app:layout_constraintEnd_toStartOf="@+id/checkbox"
|
||||
app:layout_constraintStart_toEndOf="@+id/app_icon"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/process"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textColor="@android:color/tertiary_text_dark"
|
||||
android:textIsSelectable="false"
|
||||
app:layout_constraintBottom_toTopOf="@+id/package_name"
|
||||
app:layout_constraintEnd_toEndOf="@+id/app_name"
|
||||
app:layout_constraintStart_toStartOf="@+id/app_name"
|
||||
app:layout_constraintTop_toBottomOf="@+id/app_name" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/package_name"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="2dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
@ -59,7 +74,7 @@
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="@+id/app_name"
|
||||
app:layout_constraintStart_toStartOf="@+id/app_name"
|
||||
app:layout_constraintTop_toBottomOf="@+id/app_name" />
|
||||
app:layout_constraintTop_toBottomOf="@+id/process" />
|
||||
|
||||
|
||||
<CheckBox
|
||||
|
@ -9,7 +9,7 @@
|
||||
#include <db.h>
|
||||
#include <daemon.h>
|
||||
|
||||
#define DB_VERSION 7
|
||||
#define DB_VERSION 8
|
||||
|
||||
static sqlite3 *mDB = nullptr;
|
||||
|
||||
@ -86,7 +86,8 @@ static char *open_and_init_db(sqlite3 *&db) {
|
||||
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX, nullptr);
|
||||
if (ret)
|
||||
return strdup(sqlite3_errmsg(db));
|
||||
int ver, upgrade = 0;
|
||||
int ver;
|
||||
bool upgrade = false;
|
||||
char *err;
|
||||
sqlite3_exec(db, "PRAGMA user_version", ver_cb, &ver, &err);
|
||||
err_ret(err);
|
||||
@ -117,9 +118,9 @@ static char *open_and_init_db(sqlite3 *&db) {
|
||||
nullptr, nullptr, &err);
|
||||
err_ret(err);
|
||||
ver = 3;
|
||||
upgrade = 1;
|
||||
upgrade = true;
|
||||
}
|
||||
if (ver == 3) {
|
||||
if (ver < 4) {
|
||||
// Strings
|
||||
sqlite3_exec(db,
|
||||
"CREATE TABLE IF NOT EXISTS strings "
|
||||
@ -127,16 +128,16 @@ static char *open_and_init_db(sqlite3 *&db) {
|
||||
nullptr, nullptr, &err);
|
||||
err_ret(err);
|
||||
ver = 4;
|
||||
upgrade = 1;
|
||||
upgrade = true;
|
||||
}
|
||||
if (ver == 4) {
|
||||
if (ver < 5) {
|
||||
sqlite3_exec(db, "UPDATE policies SET uid=uid%100000", nullptr, nullptr, &err);
|
||||
err_ret(err);
|
||||
/* Skip version 5 */
|
||||
ver = 6;
|
||||
upgrade = 1;
|
||||
upgrade = true;
|
||||
}
|
||||
if (ver == 5 || ver == 6) {
|
||||
if (ver < 7) {
|
||||
// Hide list
|
||||
sqlite3_exec(db,
|
||||
"CREATE TABLE IF NOT EXISTS hidelist "
|
||||
@ -144,7 +145,17 @@ static char *open_and_init_db(sqlite3 *&db) {
|
||||
nullptr, nullptr, &err);
|
||||
err_ret(err);
|
||||
ver = 7;
|
||||
upgrade =1 ;
|
||||
upgrade = true;
|
||||
}
|
||||
if (ver < 8) {
|
||||
sqlite3_exec(db,
|
||||
"ALTER TABLE hidelist ADD COLUMN package_name TEXT;"
|
||||
"SELECT process FROM hidelist;"
|
||||
"UPDATE hidelist SET package_name=process;",
|
||||
nullptr, nullptr, &err);
|
||||
err_ret(err);
|
||||
ver = 8;
|
||||
upgrade = true;
|
||||
}
|
||||
|
||||
if (upgrade) {
|
||||
|
@ -17,13 +17,6 @@
|
||||
|
||||
using namespace std;
|
||||
|
||||
// Protect access to both hide_list and hide_uid
|
||||
pthread_mutex_t list_lock;
|
||||
vector<string> hide_list;
|
||||
|
||||
// Treat GMS separately as we're only interested in one component
|
||||
int gms_uid = -1;
|
||||
|
||||
static pthread_t proc_monitor_thread;
|
||||
|
||||
static const char *prop_key[] =
|
||||
@ -72,7 +65,7 @@ void crawl_procfs(const function<bool (int)> &fn) {
|
||||
}
|
||||
}
|
||||
|
||||
static bool proc_name_match(int pid, const char *name) {
|
||||
bool proc_name_match(int pid, const char *name) {
|
||||
char buf[4019];
|
||||
FILE *f;
|
||||
sprintf(buf, "/proc/%d/comm", pid);
|
||||
@ -106,9 +99,6 @@ static bool proc_name_match(int pid, const char *name) {
|
||||
}
|
||||
|
||||
static void kill_process(const char *name) {
|
||||
// We do NOT want to kill GMS itself
|
||||
if (strcmp(name, SAFETYNET_PKG) == 0)
|
||||
name = SAFETYNET_PROCESS;
|
||||
crawl_procfs([=](int pid) -> bool {
|
||||
if (proc_name_match(pid, name)) {
|
||||
if (kill(pid, SIGTERM) == 0)
|
||||
@ -119,27 +109,6 @@ static void kill_process(const char *name) {
|
||||
});
|
||||
}
|
||||
|
||||
static void kill_process(int uid) {
|
||||
// We do NOT want to kill all GMS processes
|
||||
if (uid == gms_uid) {
|
||||
kill_process(SAFETYNET_PROCESS);
|
||||
return;
|
||||
}
|
||||
crawl_procfs([=](int pid) -> bool {
|
||||
if (get_uid(pid) == uid && kill(pid, SIGTERM) == 0)
|
||||
LOGD("hide_utils: killed PID=[%d]\n", pid);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
static int get_pkg_uid(const char *pkg) {
|
||||
char path[4096];
|
||||
struct stat st;
|
||||
const char *data = SDK_INT >= 24 ? "/data/user_de/0" : "/data/data";
|
||||
sprintf(path, "%s/%s", data, pkg);
|
||||
return stat(path, &st) ? -1 : st.st_uid;
|
||||
}
|
||||
|
||||
void clean_magisk_props() {
|
||||
getprop([](const char *name, auto, auto) -> void {
|
||||
if (strstr(name, "magisk"))
|
||||
@ -147,58 +116,76 @@ void clean_magisk_props() {
|
||||
}, nullptr, false);
|
||||
}
|
||||
|
||||
int add_list(const char *pkg) {
|
||||
for (auto &s : hide_list) {
|
||||
if (s == pkg)
|
||||
return HIDE_ITEM_EXIST;
|
||||
}
|
||||
static int add_list(const char *pkg, const char *proc = "") {
|
||||
if (proc[0] == '\0')
|
||||
proc = pkg;
|
||||
|
||||
if (hide_map.count(proc))
|
||||
return HIDE_ITEM_EXIST;
|
||||
|
||||
// Add to database
|
||||
char sql[4096];
|
||||
snprintf(sql, sizeof(sql), "INSERT INTO hidelist (process) VALUES('%s')", pkg);
|
||||
snprintf(sql, sizeof(sql),
|
||||
"INSERT INTO hidelist (package_name, process) VALUES('%s', '%s')", pkg, proc);
|
||||
char *err = db_exec(sql);
|
||||
db_err_cmd(err, return DAEMON_ERROR);
|
||||
|
||||
LOGI("hide_list add: [%s]\n", pkg);
|
||||
LOGI("hide_list add: [%s]\n", proc);
|
||||
|
||||
// Critical region
|
||||
int uid;
|
||||
{
|
||||
MutexGuard lock(list_lock);
|
||||
hide_list.emplace_back(pkg);
|
||||
uid = get_pkg_uid(pkg);
|
||||
MutexGuard lock(map_lock);
|
||||
hide_map[proc] = pkg;
|
||||
}
|
||||
|
||||
kill_process(uid);
|
||||
kill_process(proc);
|
||||
return DAEMON_SUCCESS;
|
||||
}
|
||||
|
||||
int add_list(int client) {
|
||||
char *pkg = read_string(client);
|
||||
int ret = add_list(pkg);
|
||||
char *proc = read_string(client);
|
||||
int ret = add_list(pkg, proc);
|
||||
free(pkg);
|
||||
free(proc);
|
||||
update_inotify_mask();
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int rm_list(const char *pkg) {
|
||||
// Critical region
|
||||
static int rm_list(const char *pkg, const char *proc = "") {
|
||||
{
|
||||
MutexGuard lock(list_lock);
|
||||
// Critical region
|
||||
MutexGuard lock(map_lock);
|
||||
bool remove = false;
|
||||
for (auto it = hide_list.begin(); it != hide_list.end(); ++it) {
|
||||
if (*it == pkg) {
|
||||
if (proc[0] == '\0') {
|
||||
auto next = hide_map.begin();
|
||||
decltype(next) cur;
|
||||
while (next != hide_map.end()) {
|
||||
cur = next;
|
||||
++next;
|
||||
if (cur->second == pkg) {
|
||||
remove = true;
|
||||
LOGI("hide_list rm: [%s]\n", cur->first.data());
|
||||
hide_map.erase(cur);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
auto it = hide_map.find(proc);
|
||||
if (it != hide_map.end()) {
|
||||
remove = true;
|
||||
LOGI("hide_list rm: [%s]\n", pkg);
|
||||
hide_list.erase(it);
|
||||
break;
|
||||
hide_map.erase(it);
|
||||
LOGI("hide_list rm: [%s]\n", proc);
|
||||
}
|
||||
}
|
||||
if (!remove)
|
||||
return HIDE_ITEM_NOT_EXIST;
|
||||
}
|
||||
|
||||
char sql[4096];
|
||||
snprintf(sql, sizeof(sql), "DELETE FROM hidelist WHERE process='%s'", pkg);
|
||||
if (proc[0] == '\0')
|
||||
snprintf(sql, sizeof(sql), "DELETE FROM hidelist WHERE package_name='%s'", pkg);
|
||||
else
|
||||
snprintf(sql, sizeof(sql), "DELETE FROM hidelist WHERE process='%s'", proc);
|
||||
char *err = db_exec(sql);
|
||||
db_err(err);
|
||||
return DAEMON_SUCCESS;
|
||||
@ -206,25 +193,31 @@ static int rm_list(const char *pkg) {
|
||||
|
||||
int rm_list(int client) {
|
||||
char *pkg = read_string(client);
|
||||
int ret = rm_list(pkg);
|
||||
char *proc = read_string(client);
|
||||
int ret = rm_list(pkg, proc);
|
||||
free(pkg);
|
||||
free(proc);
|
||||
if (ret == DAEMON_SUCCESS)
|
||||
update_inotify_mask();
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int init_list(void *, int, char **data, char**) {
|
||||
LOGI("hide_list init: [%s]\n", *data);
|
||||
hide_list.emplace_back(*data);
|
||||
int uid = get_pkg_uid(*data);
|
||||
if (strcmp(*data, SAFETYNET_PKG) == 0)
|
||||
gms_uid = uid;
|
||||
kill_process(uid);
|
||||
return 0;
|
||||
static void init_list(const char *pkg, const char *proc) {
|
||||
LOGI("hide_list init: [%s]\n", proc);
|
||||
hide_map[proc] = pkg;
|
||||
kill_process(proc);
|
||||
}
|
||||
|
||||
static void init_list(const char *pkg) {
|
||||
init_list(nullptr, 0, (char **) &pkg, nullptr);
|
||||
static int db_init_list(void *, int col_num, char **data, char **cols) {
|
||||
char *pkg, *proc;
|
||||
for (int i = 0; i < col_num; ++i) {
|
||||
if (strcmp(cols[i], "package_name") == 0)
|
||||
pkg = data[i];
|
||||
else if (strcmp(cols[i], "process") == 0)
|
||||
proc = data[i];
|
||||
}
|
||||
init_list(pkg, proc);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define LEGACY_LIST MODULEROOT "/.core/hidelist"
|
||||
@ -232,7 +225,7 @@ static void init_list(const char *pkg) {
|
||||
bool init_list() {
|
||||
LOGD("hide_list: initialize\n");
|
||||
|
||||
char *err = db_exec("SELECT process FROM hidelist", init_list);
|
||||
char *err = db_exec("SELECT * FROM hidelist", db_init_list);
|
||||
db_err_cmd(err, return false);
|
||||
|
||||
// Migrate old hide list into database
|
||||
@ -245,9 +238,8 @@ bool init_list() {
|
||||
}
|
||||
|
||||
// Add SafetyNet by default
|
||||
rm_list(SAFETYNET_PROCESS);
|
||||
rm_list(SAFETYNET_COMPONENT);
|
||||
init_list(SAFETYNET_PKG);
|
||||
init_list(SAFETYNET_PKG, SAFETYNET_PROCESS);
|
||||
|
||||
update_inotify_mask();
|
||||
return true;
|
||||
@ -255,8 +247,8 @@ bool init_list() {
|
||||
|
||||
void ls_list(int client) {
|
||||
FILE *out = fdopen(recv_fd(client), "a");
|
||||
for (auto &s : hide_list)
|
||||
fprintf(out, "%s\n", s.c_str());
|
||||
for (auto &s : hide_map)
|
||||
fprintf(out, "%s|%s\n", s.second.data(), s.first.data());
|
||||
fclose(out);
|
||||
write_int(client, DAEMON_SUCCESS);
|
||||
close(client);
|
||||
@ -306,7 +298,7 @@ void launch_magiskhide(int client) {
|
||||
hide_sensitive_props();
|
||||
|
||||
// Initialize the mutex lock
|
||||
pthread_mutex_init(&list_lock, nullptr);
|
||||
pthread_mutex_init(&map_lock, nullptr);
|
||||
|
||||
// Initialize the hide list
|
||||
if (!init_list())
|
||||
|
@ -20,12 +20,12 @@ bool hide_enabled = false;
|
||||
FULL_VER(MagiskHide) "\n\n"
|
||||
"Usage: %s [--option [arguments...] ]\n\n"
|
||||
"Options:\n"
|
||||
" --status Return the status of magiskhide\n"
|
||||
" --enable Start magiskhide\n"
|
||||
" --disable Stop magiskhide\n"
|
||||
" --add PKG Add PKG to the hide list\n"
|
||||
" --rm PKG Remove PKG from the hide list\n"
|
||||
" --ls List the current hide list\n"
|
||||
" --status Return the status of magiskhide\n"
|
||||
" --enable Start magiskhide\n"
|
||||
" --disable Stop magiskhide\n"
|
||||
" --add PKG [PROC] Add a new target to the hide list\n"
|
||||
" --rm PKG [PROC] Remove from the hide list\n"
|
||||
" --ls List the current hide list\n"
|
||||
, arg0);
|
||||
exit(1);
|
||||
}
|
||||
@ -96,8 +96,10 @@ int magiskhide_main(int argc, char *argv[]) {
|
||||
int fd = connect_daemon();
|
||||
write_int(fd, MAGISKHIDE);
|
||||
write_int(fd, req);
|
||||
if (req == ADD_HIDELIST || req == RM_HIDELIST)
|
||||
if (req == ADD_HIDELIST || req == RM_HIDELIST) {
|
||||
write_string(fd, argv[2]);
|
||||
write_string(fd, argv[3] ? argv[3] : "");
|
||||
}
|
||||
if (req == LS_HIDELIST)
|
||||
send_fd(fd, STDOUT_FILENO);
|
||||
|
||||
@ -113,10 +115,10 @@ int magiskhide_main(int argc, char *argv[]) {
|
||||
fprintf(stderr, "MagiskHide is enabled\n");
|
||||
break;
|
||||
case HIDE_ITEM_EXIST:
|
||||
fprintf(stderr, "[%s] already exists in hide list\n", argv[2]);
|
||||
fprintf(stderr, "Target already exists in hide list\n");
|
||||
break;
|
||||
case HIDE_ITEM_NOT_EXIST:
|
||||
fprintf(stderr, "[%s] does not exist in hide list\n", argv[2]);
|
||||
fprintf(stderr, "Target does not exist in hide list\n");
|
||||
break;
|
||||
case HIDE_NO_NS:
|
||||
fprintf(stderr, "Your kernel doesn't support mount namespace\n");
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include <string>
|
||||
#include <set>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
|
||||
#include "daemon.h"
|
||||
|
||||
@ -35,6 +36,7 @@ void proc_monitor();
|
||||
void manage_selinux();
|
||||
void clean_magisk_props();
|
||||
void crawl_procfs(const std::function<bool (int)> &fn);
|
||||
bool proc_name_match(int pid, const char *name);
|
||||
|
||||
static inline int get_uid(const int pid) {
|
||||
char path[16];
|
||||
@ -64,9 +66,8 @@ static inline int parse_int(const char *s) {
|
||||
}
|
||||
|
||||
extern bool hide_enabled;
|
||||
extern pthread_mutex_t list_lock;
|
||||
extern std::vector<std::string> hide_list;
|
||||
extern int gms_uid;
|
||||
extern pthread_mutex_t map_lock;
|
||||
extern std::map<std::string, std::string> hide_map;
|
||||
|
||||
enum {
|
||||
LAUNCH_MAGISKHIDE,
|
||||
|
@ -33,20 +33,7 @@ using namespace std;
|
||||
extern char *system_block, *vendor_block, *data_block;
|
||||
|
||||
static int inotify_fd = -1;
|
||||
static set<int> hide_uid;
|
||||
|
||||
// Workaround for the lack of pthread_cancel
|
||||
static void term_thread(int) {
|
||||
LOGD("proc_monitor: running cleanup\n");
|
||||
hide_list.clear();
|
||||
hide_uid.clear();
|
||||
hide_enabled = false;
|
||||
pthread_mutex_destroy(&list_lock);
|
||||
close(inotify_fd);
|
||||
inotify_fd = -1;
|
||||
LOGD("proc_monitor: terminating\n");
|
||||
pthread_exit(nullptr);
|
||||
}
|
||||
static void term_thread(int);
|
||||
|
||||
static inline int read_ns(const int pid, struct stat *st) {
|
||||
char path[32];
|
||||
@ -75,25 +62,6 @@ static inline int parse_ppid(const int pid) {
|
||||
return ppid;
|
||||
}
|
||||
|
||||
static bool is_snet(const int pid) {
|
||||
char path[32];
|
||||
char buf[64];
|
||||
int fd;
|
||||
ssize_t len;
|
||||
|
||||
sprintf(path, "/proc/%d/cmdline", pid);
|
||||
fd = open(path, O_RDONLY | O_CLOEXEC);
|
||||
if (fd == -1)
|
||||
return false;
|
||||
|
||||
len = read(fd, buf, sizeof(buf));
|
||||
close(fd);
|
||||
if (len == -1)
|
||||
return false;
|
||||
|
||||
return !strcmp(buf, SAFETYNET_PROCESS);
|
||||
}
|
||||
|
||||
static void hide_daemon(int pid) {
|
||||
RunFinally fin([=]() -> void {
|
||||
// Send resume signal
|
||||
@ -143,10 +111,18 @@ static void hide_daemon(int pid) {
|
||||
lazy_unmount(s.data());
|
||||
}
|
||||
|
||||
// A mapping from pid to namespace inode to avoid time-consuming GC
|
||||
static map<int, uint64_t> pid_ns_map;
|
||||
/********************
|
||||
* All the damn maps
|
||||
********************/
|
||||
|
||||
static bool process_pid(int pid) {
|
||||
map<string, string> hide_map; /* process -> package_name */
|
||||
static map<int, uint64_t> pid_ns_map; /* pid -> last ns inode */
|
||||
static map<int, vector<string_view>> uid_proc_map; /* uid -> list of process */
|
||||
|
||||
// All maps are protected by this lock
|
||||
pthread_mutex_t map_lock;
|
||||
|
||||
static bool check_pid(int pid) {
|
||||
// We're only interested in PIDs > 1000
|
||||
if (pid <= 1000)
|
||||
return true;
|
||||
@ -154,7 +130,8 @@ static bool process_pid(int pid) {
|
||||
struct stat ns, pns;
|
||||
int ppid;
|
||||
int uid = get_uid(pid);
|
||||
if (hide_uid.count(uid)) {
|
||||
auto it = uid_proc_map.find(uid);
|
||||
if (it != uid_proc_map.end()) {
|
||||
// Make sure we can read mount namespace
|
||||
if ((ppid = parse_ppid(pid)) < 0 || read_ns(pid, &ns) || read_ns(ppid, &pns))
|
||||
return true;
|
||||
@ -167,20 +144,21 @@ static bool process_pid(int pid) {
|
||||
if (pos != pid_ns_map.end() && pos->second == ns.st_ino)
|
||||
return true;
|
||||
|
||||
if (uid == gms_uid) {
|
||||
// Check /proc/uid/cmdline to see if it's SAFETYNET_PROCESS
|
||||
if (!is_snet(pid))
|
||||
return true;
|
||||
// Check whether process name match hide list
|
||||
const char *process = nullptr;
|
||||
for (auto &proc : it->second)
|
||||
if (proc_name_match(pid, proc.data()))
|
||||
process = proc.data();
|
||||
|
||||
LOGD("proc_monitor: " SAFETYNET_PROCESS "\n");
|
||||
}
|
||||
if (!process)
|
||||
return true;
|
||||
|
||||
// Send pause signal ASAP
|
||||
if (kill(pid, SIGSTOP) == -1)
|
||||
return true;
|
||||
|
||||
pid_ns_map[pid] = ns.st_ino;
|
||||
LOGI("proc_monitor: UID=[%d] PID=[%d] ns=[%llu]\n", uid, pid, ns.st_ino);
|
||||
LOGI("proc_monitor: [%s] UID=[%d] PID=[%d] ns=[%llu]\n", process, uid, pid, ns.st_ino);
|
||||
|
||||
/*
|
||||
* The setns system call do not support multithread processes
|
||||
@ -202,29 +180,34 @@ static int xinotify_add_watch(int fd, const char* path, uint32_t mask) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int new_inotify;
|
||||
static const string_view APK_EXT(".apk");
|
||||
static vector<string> hide_apks;
|
||||
|
||||
static bool parse_packages_xml(string_view &s) {
|
||||
static const string_view APK_EXT(".apk");
|
||||
if (!str_starts(s, "<package "))
|
||||
return true;
|
||||
/* <package key1="value1" key2="value2"....> */
|
||||
char *start = (char *) s.data();
|
||||
start[s.length() - 2] = '\0'; /* Remove trailing '>' */
|
||||
char key[32], value[1024];
|
||||
char *pkg = nullptr;
|
||||
char *tok;
|
||||
start += 9; /* Skip '<package ' */
|
||||
while ((tok = strtok_r(nullptr, " ", &start))) {
|
||||
sscanf(tok, "%[^=]=\"%[^\"]", key, value);
|
||||
string_view key_view(key);
|
||||
string_view value_view(value);
|
||||
if (strcmp(key, "name") == 0) {
|
||||
if (std::count(hide_list.begin(), hide_list.end(), value_view) == 0)
|
||||
if (key_view == "name") {
|
||||
for (auto &hide : hide_map) {
|
||||
if (hide.second == value_view) {
|
||||
pkg = hide.second.data();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!pkg)
|
||||
return true;
|
||||
} else if (strcmp(key, "codePath") == 0) {
|
||||
} else if (key_view == "codePath") {
|
||||
if (ends_with(value_view, APK_EXT)) {
|
||||
// Directly add to inotify list
|
||||
hide_apks.emplace_back(value);
|
||||
xinotify_add_watch(inotify_fd, value, IN_OPEN);
|
||||
} else {
|
||||
DIR *dir = opendir(value);
|
||||
if (dir == nullptr)
|
||||
@ -232,36 +215,29 @@ static bool parse_packages_xml(string_view &s) {
|
||||
struct dirent *entry;
|
||||
while ((entry = xreaddir(dir))) {
|
||||
if (ends_with(entry->d_name, APK_EXT)) {
|
||||
strcpy(value + value_view.length(), "/");
|
||||
value[value_view.length()] = '/';
|
||||
strcpy(value + value_view.length() + 1, entry->d_name);
|
||||
hide_apks.emplace_back(value);
|
||||
xinotify_add_watch(inotify_fd, value, IN_OPEN);
|
||||
break;
|
||||
}
|
||||
}
|
||||
closedir(dir);
|
||||
}
|
||||
} else if (strcmp(key, "userId") == 0 || strcmp(key, "sharedUserId") == 0) {
|
||||
hide_uid.insert(parse_int(value));
|
||||
} else if (key_view == "userId" || key_view == "sharedUserId") {
|
||||
int uid = parse_int(value);
|
||||
for (auto &hide : hide_map) {
|
||||
if (hide.second == pkg)
|
||||
uid_proc_map[uid].emplace_back(hide.first);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void update_inotify_mask() {
|
||||
new_inotify = inotify_init();
|
||||
if (new_inotify < 0) {
|
||||
LOGE("proc_monitor: Cannot initialize inotify: %s\n", strerror(errno));
|
||||
int new_inotify = xinotify_init1(IN_CLOEXEC);
|
||||
if (new_inotify < 0)
|
||||
term_thread(TERM_THREAD);
|
||||
}
|
||||
fcntl(new_inotify, F_SETFD, FD_CLOEXEC);
|
||||
|
||||
LOGD("proc_monitor: Updating inotify list\n");
|
||||
hide_apks.clear();
|
||||
{
|
||||
MutexGuard lock(list_lock);
|
||||
hide_uid.clear();
|
||||
file_readline("/data/system/packages.xml", parse_packages_xml, true);
|
||||
}
|
||||
|
||||
// Swap out and close old inotify_fd
|
||||
int tmp = inotify_fd;
|
||||
@ -269,11 +245,29 @@ void update_inotify_mask() {
|
||||
if (tmp >= 0)
|
||||
close(tmp);
|
||||
|
||||
for (auto apk : hide_apks)
|
||||
xinotify_add_watch(inotify_fd, apk.data(), IN_OPEN);
|
||||
LOGD("proc_monitor: Updating inotify list\n");
|
||||
{
|
||||
MutexGuard lock(map_lock);
|
||||
uid_proc_map.clear();
|
||||
file_readline("/data/system/packages.xml", parse_packages_xml, true);
|
||||
}
|
||||
xinotify_add_watch(inotify_fd, "/data/system", IN_CLOSE_WRITE);
|
||||
}
|
||||
|
||||
// Workaround for the lack of pthread_cancel
|
||||
static void term_thread(int) {
|
||||
LOGD("proc_monitor: cleaning up\n");
|
||||
hide_map.clear();
|
||||
uid_proc_map.clear();
|
||||
pid_ns_map.clear();
|
||||
hide_enabled = false;
|
||||
pthread_mutex_destroy(&map_lock);
|
||||
close(inotify_fd);
|
||||
inotify_fd = -1;
|
||||
LOGD("proc_monitor: terminate\n");
|
||||
pthread_exit(nullptr);
|
||||
}
|
||||
|
||||
void proc_monitor() {
|
||||
// Unblock user signals
|
||||
sigset_t block_set;
|
||||
@ -296,8 +290,8 @@ void proc_monitor() {
|
||||
if (event->mask & IN_OPEN) {
|
||||
// Since we're just watching files,
|
||||
// extracting file name is not possible from querying event
|
||||
MutexGuard lock(list_lock);
|
||||
crawl_procfs(process_pid);
|
||||
MutexGuard lock(map_lock);
|
||||
crawl_procfs(check_pid);
|
||||
} else if ((event->mask & IN_CLOSE_WRITE) && strcmp(event->name, "packages.xml") == 0) {
|
||||
LOGD("proc_monitor: /data/system/packages.xml updated\n");
|
||||
update_inotify_mask();
|
||||
|
@ -71,6 +71,7 @@ void *xmmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset
|
||||
ssize_t xsendfile(int out_fd, int in_fd, off_t *offset, size_t count);
|
||||
pid_t xfork();
|
||||
int xpoll(struct pollfd *fds, nfds_t nfds, int timeout);
|
||||
int xinotify_init1(int flags);
|
||||
|
||||
// misc.cpp
|
||||
|
||||
|
@ -412,3 +412,11 @@ int xpoll(struct pollfd *fds, nfds_t nfds, int timeout) {
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int xinotify_init1(int flags) {
|
||||
int ret = syscall(__NR_inotify_init1, flags);
|
||||
if (ret == -1) {
|
||||
PLOGE("inotify_init1");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user