Magisk/native/jni/core/db.cpp

294 lines
7.2 KiB
C++
Raw Normal View History

#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
2019-02-10 09:57:51 +01:00
#include <magisk.h>
#include <db.h>
#include <daemon.h>
2019-03-08 02:31:35 +01:00
#include <utils.h>
2019-11-14 06:01:06 +01:00
#define DB_VERSION 10
2019-03-06 14:16:12 +01:00
using namespace std;
2018-11-05 00:24:08 +01:00
2019-03-06 14:16:12 +01:00
static sqlite3 *mDB = nullptr;
2018-11-05 00:24:08 +01:00
2019-03-06 14:16:12 +01:00
int db_strings::getKeyIdx(string_view key) const {
2018-11-05 00:24:08 +01:00
int idx = DB_STRING_NUM;
for (int i = 0; i < DB_STRING_NUM; ++i) {
2019-03-06 14:16:12 +01:00
if (key == DB_STRING_KEYS[i])
2018-11-05 00:24:08 +01:00
idx = i;
}
return idx;
}
2019-03-06 14:16:12 +01:00
db_settings::db_settings() {
// Default settings
data[ROOT_ACCESS] = ROOT_ACCESS_APPS_AND_ADB;
data[SU_MULTIUSER_MODE] = MULTIUSER_MODE_OWNER_ONLY;
data[SU_MNT_NS] = NAMESPACE_MODE_REQUESTER;
2019-06-27 09:28:34 +02:00
data[HIDE_CONFIG] = true;
2018-11-05 00:24:08 +01:00
}
2019-03-06 14:16:12 +01:00
int db_settings::getKeyIdx(string_view key) const {
2018-11-05 00:24:08 +01:00
int idx = DB_SETTINGS_NUM;
for (int i = 0; i < DB_SETTINGS_NUM; ++i) {
2019-03-06 14:16:12 +01:00
if (key == DB_SETTING_KEYS[i])
2018-11-05 00:24:08 +01:00
idx = i;
}
return idx;
}
2018-11-04 09:38:06 +01:00
static int ver_cb(void *ver, int, char **data, char **) {
2019-03-08 02:31:35 +01:00
*((int *) ver) = parse_int(data[0]);
return 0;
}
#define err_ret(e) if (e) return e;
static char *open_and_init_db(sqlite3 *&db) {
int ret = sqlite3_open_v2(MAGISKDB, &db,
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX, nullptr);
if (ret)
return strdup(sqlite3_errmsg(db));
int ver;
bool upgrade = false;
char *err;
sqlite3_exec(db, "PRAGMA user_version", ver_cb, &ver, &err);
err_ret(err);
if (ver > DB_VERSION) {
// Don't support downgrading database
sqlite3_close(db);
2018-11-04 09:38:06 +01:00
return nullptr;
}
if (ver < 3) {
// Policies
sqlite3_exec(db,
"CREATE TABLE IF NOT EXISTS policies "
"(uid INT, package_name TEXT, policy INT, until INT, "
"logging INT, notification INT, PRIMARY KEY(uid))",
nullptr, nullptr, &err);
err_ret(err);
// Settings
sqlite3_exec(db,
"CREATE TABLE IF NOT EXISTS settings "
"(key TEXT, value INT, PRIMARY KEY(key))",
nullptr, nullptr, &err);
err_ret(err);
ver = 3;
upgrade = true;
}
if (ver < 4) {
// Strings
sqlite3_exec(db,
"CREATE TABLE IF NOT EXISTS strings "
"(key TEXT, value TEXT, PRIMARY KEY(key))",
nullptr, nullptr, &err);
err_ret(err);
ver = 4;
upgrade = true;
}
if (ver < 5) {
2018-11-04 09:38:06 +01:00
sqlite3_exec(db, "UPDATE policies SET uid=uid%100000", nullptr, nullptr, &err);
err_ret(err);
/* Directly jump to version 6 */
ver = 6;
upgrade = true;
}
if (ver < 7) {
2018-11-01 18:23:12 +01:00
sqlite3_exec(db,
"CREATE TABLE IF NOT EXISTS hidelist "
"(package_name TEXT, process TEXT, PRIMARY KEY(package_name, process));",
nullptr, nullptr, &err);
err_ret(err);
/* Directly jump to version 9 */
ver = 9;
upgrade = true;
}
if (ver < 8) {
sqlite3_exec(db,
"BEGIN TRANSACTION;"
"ALTER TABLE hidelist RENAME TO hidelist_tmp;"
"CREATE TABLE IF NOT EXISTS hidelist "
"(package_name TEXT, process TEXT, PRIMARY KEY(package_name, process));"
"INSERT INTO hidelist SELECT process as package_name, process FROM hidelist_tmp;"
"DROP TABLE hidelist_tmp;"
"COMMIT;",
nullptr, nullptr, &err);
err_ret(err);
/* Directly jump to version 9 */
ver = 9;
upgrade = true;
}
if (ver < 9) {
sqlite3_exec(db,
"BEGIN TRANSACTION;"
"ALTER TABLE hidelist RENAME TO hidelist_tmp;"
"CREATE TABLE IF NOT EXISTS hidelist "
"(package_name TEXT, process TEXT, PRIMARY KEY(package_name, process));"
"INSERT INTO hidelist SELECT * FROM hidelist_tmp;"
"DROP TABLE hidelist_tmp;"
"COMMIT;",
nullptr, nullptr, &err);
err_ret(err);
ver = 9;
upgrade = true;
2018-11-01 18:23:12 +01:00
}
2019-11-14 06:01:06 +01:00
if (ver < 10) {
sqlite3_exec(db, "DROP TABLE logs", nullptr, nullptr, &err);
err_ret(err);
ver = 10;
upgrade = true;
}
if (upgrade) {
// Set version
char query[32];
sprintf(query, "PRAGMA user_version=%d", ver);
2018-11-04 09:38:06 +01:00
sqlite3_exec(db, query, nullptr, nullptr, &err);
err_ret(err);
}
return nullptr;
}
2019-03-06 14:16:12 +01:00
char *db_exec(const char *sql) {
char *err;
if (mDB == nullptr) {
err = open_and_init_db(mDB);
db_err_cmd(err,
// Open fails, remove and reconstruct
unlink(MAGISKDB);
err = open_and_init_db(mDB);
err_ret(err);
);
}
if (mDB) {
2019-03-06 14:16:12 +01:00
sqlite3_exec(mDB, sql, nullptr, nullptr, &err);
return err;
}
return nullptr;
}
2019-03-06 14:16:12 +01:00
char *db_exec(const char *sql, const db_row_cb &fn) {
char *err;
if (mDB == nullptr) {
err = open_and_init_db(mDB);
db_err_cmd(err,
// Open fails, remove and reconstruct
unlink(MAGISKDB);
err = open_and_init_db(mDB);
err_ret(err);
);
}
2019-03-06 14:16:12 +01:00
if (mDB) {
sqlite3_exec(mDB, sql, [](void *cb, int col_num, char **data, char **col_name) -> int {
auto &func = *reinterpret_cast<const db_row_cb*>(cb);
db_row row;
for (int i = 0; i < col_num; ++i)
row[col_name[i]] = data[i];
return func(row) ? 0 : 1;
}, (void *) &fn, &err);
return err;
}
2019-03-06 14:16:12 +01:00
return nullptr;
}
2019-03-06 14:16:12 +01:00
int get_db_settings(db_settings &cfg, int key) {
char *err;
2019-03-06 14:16:12 +01:00
auto settings_cb = [&](db_row &row) -> bool {
2019-03-08 02:31:35 +01:00
cfg[row["key"]] = parse_int(row["value"]);
2019-03-06 14:16:12 +01:00
LOGD("magiskdb: query %s=[%s]\n", row["key"].data(), row["value"].data());
return true;
};
2018-11-20 07:50:31 +01:00
if (key >= 0) {
char query[128];
sprintf(query, "SELECT key, value FROM settings WHERE key='%s'", DB_SETTING_KEYS[key]);
2019-03-06 14:16:12 +01:00
err = db_exec(query, settings_cb);
} else {
2019-03-06 14:16:12 +01:00
err = db_exec("SELECT key, value FROM settings", settings_cb);
}
db_err_cmd(err, return 1);
return 0;
}
2019-03-06 14:16:12 +01:00
int get_db_strings(db_strings &str, int key) {
char *err;
2019-03-06 14:16:12 +01:00
auto string_cb = [&](db_row &row) -> bool {
str[row["key"]] = row["value"];
return true;
};
2018-11-20 07:50:31 +01:00
if (key >= 0) {
char query[128];
sprintf(query, "SELECT key, value FROM strings WHERE key='%s'", DB_STRING_KEYS[key]);
2019-03-06 14:16:12 +01:00
err = db_exec(query, string_cb);
} else {
2019-03-06 14:16:12 +01:00
err = db_exec("SELECT key, value FROM strings", string_cb);
}
2019-03-06 14:16:12 +01:00
db_err_cmd(err, return 1);
return 0;
}
int get_uid_policy(su_access &su, int uid) {
char query[256], *err;
sprintf(query, "SELECT policy, logging, notification FROM policies "
2018-11-04 09:38:06 +01:00
"WHERE uid=%d AND (until=0 OR until>%li)", uid, time(nullptr));
2019-03-06 14:16:12 +01:00
err = db_exec(query, [&](db_row &row) -> bool {
2019-03-08 02:31:35 +01:00
su.policy = (policy_t) parse_int(row["policy"]);
su.log = parse_int(row["logging"]);
su.notify = parse_int(row["notification"]);
2019-03-06 14:16:12 +01:00
LOGD("magiskdb: query policy=[%d] log=[%d] notify=[%d]\n", su.policy, su.log, su.notify);
return true;
});
db_err_cmd(err, return 1);
return 0;
}
2019-03-06 14:16:12 +01:00
int validate_manager(string &alt_pkg, int userid, struct stat *st) {
struct stat tmp_st;
if (st == nullptr)
st = &tmp_st;
// Prefer DE storage
char app_path[128];
2019-06-04 08:32:49 +02:00
sprintf(app_path, "%s/%d/%s", APP_DATA_DIR, userid, alt_pkg.empty() ? "xxx" : alt_pkg.data());
if (stat(app_path, st)) {
// Check the official package name
2019-06-04 08:32:49 +02:00
sprintf(app_path, "%s/%d/" JAVA_PACKAGE_NAME, APP_DATA_DIR, userid);
if (stat(app_path, st)) {
LOGE("su: cannot find manager");
memset(st, 0, sizeof(*st));
2019-03-06 14:16:12 +01:00
alt_pkg.clear();
return 1;
} else {
// Switch to official package if exists
2019-03-06 14:16:12 +01:00
alt_pkg = JAVA_PACKAGE_NAME;
}
}
return 0;
}
void exec_sql(int client) {
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 19:59:04 +02:00
run_finally f([=]{ close(client); });
char *sql = read_string(client);
2019-03-06 14:16:12 +01:00
char *err = db_exec(sql, [&](db_row &row) -> bool {
2019-09-01 07:58:50 +02:00
string out;
bool first = true;
2019-03-06 14:16:12 +01:00
for (auto it : row) {
2019-09-01 07:58:50 +02:00
if (first) first = false;
else out += '|';
out += it.first;
out += '=';
out += it.second;
2019-03-06 14:16:12 +01:00
}
2019-09-01 07:58:50 +02:00
write_int(client, out.length());
xwrite(client, out.data(), out.length());
2019-03-06 14:16:12 +01:00
return true;
});
free(sql);
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 19:59:04 +02:00
write_int(client, 0);
db_err_cmd(err, return; );
}