Magisk/native/jni/core/db.cpp

404 lines
9.8 KiB
C++
Raw Normal View History

#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <unistd.h>
2020-01-10 20:20:59 +01:00
#include <dlfcn.h>
#include <sys/stat.h>
2020-03-09 09:50:30 +01:00
#include <magisk.hpp>
#include <db.hpp>
#include <daemon.hpp>
#include <utils.hpp>
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
2020-01-10 20:20:59 +01:00
typedef struct sqlite3 sqlite3;
2019-03-06 14:16:12 +01:00
static sqlite3 *mDB = nullptr;
2018-11-05 00:24:08 +01:00
2020-01-10 20:20:59 +01:00
// SQLite APIs
#define SQLITE_OPEN_READWRITE 0x00000002 /* Ok for sqlite3_open_v2() */
#define SQLITE_OPEN_CREATE 0x00000004 /* Ok for sqlite3_open_v2() */
#define SQLITE_OPEN_FULLMUTEX 0x00010000 /* Ok for sqlite3_open_v2() */
static int (*sqlite3_open_v2)(
const char *filename,
sqlite3 **ppDb,
int flags,
const char *zVfs);
static const char *(*sqlite3_errmsg)(sqlite3 *db);
static int (*sqlite3_close)(sqlite3 *db);
static void (*sqlite3_free)(void *v);
static int (*sqlite3_exec)(
sqlite3 *db,
const char *sql,
int (*callback)(void*, int, char**, char**),
void *v,
char **errmsg);
// Internal Android linker APIs
static void (*android_get_LD_LIBRARY_PATH)(char *buffer, size_t buffer_size);
static void (*android_update_LD_LIBRARY_PATH)(const char *ld_library_path);
#define DLERR(ptr) if (!(ptr)) { \
LOGE("db: %s\n", dlerror()); \
return false; \
}
#define DLOAD(handle, arg) {\
auto f = dlsym(handle, #arg); \
DLERR(f) \
*(void **) &(arg) = f; \
}
2020-01-21 18:20:14 +01:00
#ifdef __LP64__
constexpr char apex_path[] = "/apex/com.android.runtime/lib64:";
2020-01-10 20:20:59 +01:00
#else
constexpr char apex_path[] = "/apex/com.android.runtime/lib:";
2020-01-10 20:20:59 +01:00
#endif
static int dl_init = 0;
static bool dload_sqlite() {
if (dl_init)
return dl_init > 0;
dl_init = -1;
auto sqlite = dlopen("libsqlite.so", RTLD_LAZY);
if (!sqlite) {
// Should only happen on Android 10+
auto dl = dlopen("libdl_android.so", RTLD_LAZY);
DLERR(dl);
DLOAD(dl, android_get_LD_LIBRARY_PATH);
DLOAD(dl, android_update_LD_LIBRARY_PATH);
// Inject APEX into LD_LIBRARY_PATH
char ld_path[4096];
memcpy(ld_path, apex_path, sizeof(apex_path));
constexpr int len = sizeof(apex_path) - 1;
android_get_LD_LIBRARY_PATH(ld_path + len, sizeof(ld_path) - len);
2020-01-10 20:20:59 +01:00
android_update_LD_LIBRARY_PATH(ld_path);
sqlite = dlopen("libsqlite.so", RTLD_LAZY);
// Revert LD_LIBRARY_PATH just in case
android_update_LD_LIBRARY_PATH(ld_path + len);
2020-01-10 20:20:59 +01:00
}
DLERR(sqlite);
DLOAD(sqlite, sqlite3_open_v2);
DLOAD(sqlite, sqlite3_errmsg);
DLOAD(sqlite, sqlite3_close);
DLOAD(sqlite, sqlite3_exec);
DLOAD(sqlite, sqlite3_free);
dl_init = 1;
return true;
}
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) {
2020-01-10 20:20:59 +01:00
if (!dload_sqlite())
return strdup("Cannot load libsqlite.so");
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) {
2019-11-21 20:40:12 +01:00
sqlite3_exec(db, "DROP TABLE IF EXISTS logs", nullptr, nullptr, &err);
2019-11-14 06:01:06 +01:00
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;
}
bool check_manager(string *pkg) {
db_strings str;
get_db_strings(str, SU_MANAGER);
bool ret = validate_manager(str[SU_MANAGER], 0, nullptr);
if (pkg) {
if (ret)
pkg->swap(str[SU_MANAGER]);
else
*pkg = "xxx"; /* Make sure the return pkg can never exist */
}
return ret;
}
bool validate_manager(string &pkg, int userid, struct stat *st) {
2019-03-06 14:16:12 +01:00
struct stat tmp_st;
if (st == nullptr)
st = &tmp_st;
// Prefer DE storage
char app_path[128];
sprintf(app_path, "%s/%d/%s", APP_DATA_DIR, userid, pkg.data());
if (pkg.empty() || 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));
pkg.clear();
return false;
} else {
// Switch to official package if exists
pkg = JAVA_PACKAGE_NAME;
}
}
return true;
}
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; );
}
2020-01-10 20:20:59 +01:00
bool db_err(char *e) {
if (e) {
LOGE("sqlite3_exec: %s\n", e);
sqlite3_free(e);
return true;
}
return false;
}