Handle magisk.db completely natively

Prevent database corruption due to different Android application sqlite default settings
This commit is contained in:
topjohnwu 2018-10-27 17:54:48 -04:00
parent 2a65c3dc8f
commit dbb8b8a439
10 changed files with 357 additions and 87 deletions

View File

@ -52,6 +52,7 @@ public class Const {
public static final int SEPOL_REFACTOR = 1640; public static final int SEPOL_REFACTOR = 1640;
public static final int FIX_ENV = 1650; public static final int FIX_ENV = 1650;
public static final int DBVER_SIX = 17000; public static final int DBVER_SIX = 17000;
public static final int CMDLINE_DB = 17305;
} }
public static class ID { public static class ID {

View File

@ -252,7 +252,7 @@ public class SuRequestActivity extends BaseActivity {
policy.policy = action; policy.policy = action;
if (time >= 0) { if (time >= 0) {
policy.until = (time == 0) ? 0 : (System.currentTimeMillis() / 1000 + time * 60); policy.until = (time == 0) ? 0 : (System.currentTimeMillis() / 1000 + time * 60);
mm.mDB.addPolicy(policy); mm.mDB.updatePolicy(policy);
} }
handleAction(); handleAction();
} }

View File

@ -1,60 +1,66 @@
package com.topjohnwu.magisk.database; package com.topjohnwu.magisk.database;
import com.topjohnwu.magisk.Const; import com.topjohnwu.magisk.Const;
import com.topjohnwu.magisk.Data;
import com.topjohnwu.magisk.container.Policy; import com.topjohnwu.magisk.container.Policy;
import com.topjohnwu.magisk.container.SuLogEntry; import com.topjohnwu.magisk.container.SuLogEntry;
import com.topjohnwu.magisk.utils.Utils; import com.topjohnwu.magisk.utils.Utils;
import com.topjohnwu.superuser.Shell;
import java.io.File; import java.io.File;
import java.util.Collections;
import java.util.List; import java.util.List;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
public abstract class MagiskDB { public class MagiskDB {
public static final int DATABASE_VER = 6; static final String POLICY_TABLE = "policies";
public static final int OLD_DATABASE_VER = 5; static final String LOG_TABLE = "logs";
public static final String POLICY_TABLE = "policies"; static final String SETTINGS_TABLE = "settings";
public static final String LOG_TABLE = "logs"; static final String STRINGS_TABLE = "strings";
public static final String SETTINGS_TABLE = "settings"; static final File LEGACY_MANAGER_DB =
public static final String STRINGS_TABLE = "strings";
public static final File LEGACY_MANAGER_DB =
new File(Utils.fmt("/sbin/.core/db-%d/magisk.db", Const.USER_ID)); new File(Utils.fmt("/sbin/.core/db-%d/magisk.db", Const.USER_ID));
@NonNull @NonNull
public static MagiskDB getInstance() { public static MagiskDB getInstance() {
return MagiskDBLegacy.newInstance(); if (LEGACY_MANAGER_DB.canWrite()) {
return MagiskDBLegacy.newInstance();
} else if (Shell.rootAccess()) {
return Data.magiskVersionCode >= Const.MAGISK_VER.CMDLINE_DB ?
new MagiskDBCmdline() : MagiskDBLegacy.newInstance();
} else {
return new MagiskDB();
}
} }
public abstract void clearOutdated(); public void clearOutdated() {}
public void deletePolicy(Policy policy) { public void deletePolicy(Policy policy) {
deletePolicy(policy.uid); deletePolicy(policy.uid);
} }
public abstract void deletePolicy(String pkg); public void deletePolicy(String pkg) {}
public abstract void deletePolicy(int uid); public void deletePolicy(int uid) {}
public abstract Policy getPolicy(int uid); public Policy getPolicy(int uid) { return null; }
public abstract void addPolicy(Policy policy); public void updatePolicy(Policy policy) {}
public abstract void updatePolicy(Policy policy); public List<Policy> getPolicyList() { return Collections.emptyList(); }
public abstract List<Policy> getPolicyList(); public List<List<SuLogEntry>> getLogs() { return Collections.emptyList(); }
public abstract List<List<SuLogEntry>> getLogs(); public void addLog(SuLogEntry log) {}
public abstract void addLog(SuLogEntry log); public void clearLogs() {}
public abstract void clearLogs(); public void setSettings(String key, int value) {}
public abstract void setSettings(String key, int value); public int getSettings(String key, int defaultValue) { return defaultValue; }
public abstract int getSettings(String key, int defaultValue); public void setStrings(String key, String value) {}
public abstract void setStrings(String key, String value); public String getStrings(String key, String defaultValue) { return defaultValue; }
public abstract String getStrings(String key, String defaultValue);
} }

View File

@ -0,0 +1,185 @@
package com.topjohnwu.magisk.database;
import android.content.ContentValues;
import android.content.pm.PackageManager;
import android.text.TextUtils;
import com.topjohnwu.magisk.Const;
import com.topjohnwu.magisk.Data;
import com.topjohnwu.magisk.container.Policy;
import com.topjohnwu.magisk.container.SuLogEntry;
import com.topjohnwu.magisk.utils.LocaleManager;
import com.topjohnwu.magisk.utils.Utils;
import com.topjohnwu.superuser.Shell;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
public class MagiskDBCmdline extends MagiskDB {
private PackageManager pm;
public MagiskDBCmdline() {
pm = Data.MM().getPackageManager();
}
private List<String> rawSQL(String fmt, Object... args) {
return Shell.su("magisk --sqlite '" + Utils.fmt(fmt, args) + "'").exec().getOut();
}
private List<ContentValues> SQL(String fmt, Object... args) {
List<ContentValues> list = new ArrayList<>();
for (String raw : rawSQL(fmt, args)) {
ContentValues values = new ContentValues();
String[] cols = raw.split("\\|");
for (String col : cols) {
String[] pair = col.split("=", 2);
values.put(pair[0], pair[1]);
}
list.add(values);
}
return list;
}
private String toSQL(ContentValues values) {
StringBuilder keys = new StringBuilder(), vals = new StringBuilder();
keys.append('(');
vals.append("VALUES(");
boolean first = true;
for (Map.Entry<String, Object> entry : values.valueSet()) {
if (!first) {
keys.append(',');
vals.append(',');
} else {
first = false;
}
keys.append(entry.getKey());
vals.append('"');
vals.append(entry.getValue());
vals.append('"');
}
keys.append(')');
vals.append(')');
keys.append(vals);
return keys.toString();
}
@Override
public void clearOutdated() {
rawSQL(
"DELETE FROM %s WHERE until > 0 AND until < %d;" +
"DELETE FROM %s WHERE time < %d",
POLICY_TABLE, System.currentTimeMillis() / 1000,
LOG_TABLE, System.currentTimeMillis() - Data.suLogTimeout * 86400000
);
}
@Override
public void deletePolicy(String pkg) {
rawSQL("DELETE FROM %s WHERE package_name=\"%s\"", POLICY_TABLE, pkg);
}
@Override
public void deletePolicy(int uid) {
rawSQL("DELETE FROM %s WHERE uid=%d", POLICY_TABLE, uid);
}
@Override
public Policy getPolicy(int uid) {
List<ContentValues> res =
SQL("SELECT * FROM %s WHERE uid=%d", POLICY_TABLE, uid);
if (!res.isEmpty()) {
try {
return new Policy(res.get(0), pm);
} catch (PackageManager.NameNotFoundException e) {
deletePolicy(uid);
}
}
return null;
}
@Override
public void updatePolicy(Policy policy) {
rawSQL("REPLACE INTO %s %s", POLICY_TABLE, toSQL(policy.getContentValues()));
}
@Override
public List<Policy> getPolicyList() {
List<Policy> list = new ArrayList<>();
for (ContentValues values : SQL("SELECT * FROM %s WHERE uid/100000=%d", POLICY_TABLE, Const.USER_ID)) {
try {
list.add(new Policy(values, pm));
} catch (PackageManager.NameNotFoundException e) {
deletePolicy(values.getAsInteger("uid"));
}
}
return list;
}
@Override
public List<List<SuLogEntry>> getLogs() {
List<List<SuLogEntry>> ret = new ArrayList<>();
List<SuLogEntry> list = null;
String dateString = null, newString;
for (ContentValues values : SQL("SELECT * FROM %s ORDER BY time DESC", LOG_TABLE)) {
Date date = new Date(values.getAsLong("time"));
newString = DateFormat.getDateInstance(DateFormat.MEDIUM, LocaleManager.locale).format(date);
if (!TextUtils.equals(dateString, newString)) {
dateString = newString;
list = new ArrayList<>();
ret.add(list);
}
list.add(new SuLogEntry(values));
}
return ret;
}
@Override
public void addLog(SuLogEntry log) {
rawSQL("INSERT INTO %s %s", LOG_TABLE, toSQL(log.getContentValues()));
}
@Override
public void clearLogs() {
rawSQL("DELETE FROM %s", LOG_TABLE);
}
@Override
public void setSettings(String key, int value) {
ContentValues data = new ContentValues();
data.put("key", key);
data.put("value", value);
rawSQL("REPLACE INTO %s %s", SETTINGS_TABLE, toSQL(data));
}
@Override
public int getSettings(String key, int defaultValue) {
List<ContentValues> res = SQL("SELECT value FROM %s WHERE key=\"%s\"", SETTINGS_TABLE, key);
if (res.isEmpty())
return defaultValue;
return res.get(0).getAsInteger("value");
}
@Override
public void setStrings(String key, String value) {
if (value == null) {
rawSQL("DELETE FROM %s WHERE key=\"%s\"", STRINGS_TABLE);
return;
}
ContentValues data = new ContentValues();
data.put("key", key);
data.put("value", value);
rawSQL("REPLACE INTO %s %s", STRINGS_TABLE, toSQL(data));
}
@Override
public String getStrings(String key, String defaultValue) {
List<ContentValues> res = SQL("SELECT value FROM %s WHERE key=\"%s\"", STRINGS_TABLE, key);
if (res.isEmpty())
return defaultValue;
return res.get(0).getAsString("value");
}
}

View File

@ -30,6 +30,9 @@ import java.util.List;
public class MagiskDBLegacy extends MagiskDB { public class MagiskDBLegacy extends MagiskDB {
private static final int DATABASE_VER = 6;
private static final int OLD_DATABASE_VER = 5;
private PackageManager pm; private PackageManager pm;
private SQLiteDatabase db; private SQLiteDatabase db;
@ -193,14 +196,9 @@ public class MagiskDBLegacy extends MagiskDB {
return policy; return policy;
} }
@Override
public void addPolicy(Policy policy) {
db.replace(POLICY_TABLE, null, policy.getContentValues());
}
@Override @Override
public void updatePolicy(Policy policy) { public void updatePolicy(Policy policy) {
db.update(POLICY_TABLE, policy.getContentValues(), Utils.fmt("uid=%d", policy.uid), null); db.replace(POLICY_TABLE, null, policy.getContentValues());
} }
@Override @Override

View File

@ -942,13 +942,16 @@ core_only:
} else { } else {
// Check whether we have a valid manager installed // Check whether we have a valid manager installed
sqlite3 *db = get_magiskdb(); sqlite3 *db = get_magiskdb();
struct db_strings str; if (db) {
memset(&str, 0, sizeof(str)); struct db_strings str;
get_db_strings(db, SU_MANAGER, &str); memset(&str, 0, sizeof(str));
if (validate_manager(str.s[SU_MANAGER], 0, NULL)) { get_db_strings(db, SU_MANAGER, &str);
// There is no manager installed, install the stub if (validate_manager(str.s[SU_MANAGER], 0, NULL)) {
exec_command_sync("/sbin/magiskinit", "-x", "manager", "/data/magisk.apk", NULL); // There is no manager installed, install the stub
install_apk("/data/magisk.apk"); exec_command_sync("/sbin/magiskinit", "-x", "manager", "/data/magisk.apk", NULL);
install_apk("/data/magisk.apk");
}
sqlite3_close_v2(db);
} }
} }

View File

@ -8,20 +8,67 @@
#include "magisk.h" #include "magisk.h"
#include "db.h" #include "db.h"
static int policy_cb(void *v, int col_num, char **data, char **col_name) { static int ver_cb(void *v, int col_num, char **data, char **col_name) {
struct su_access *su = v; *((int *) v) = atoi(data[0]);
for (int i = 0; i < col_num; i++) {
if (strcmp(col_name[i], "policy") == 0)
su->policy = (policy_t) atoi(data[i]);
else if (strcmp(col_name[i], "logging") == 0)
su->log = atoi(data[i]);
else if (strcmp(col_name[i], "notification") == 0)
su->notify = atoi(data[i]);
}
LOGD("magiskdb: query policy=[%d] log=[%d] notify=[%d]\n", su->policy, su->log, su->notify);
return 0; return 0;
} }
sqlite3 *get_magiskdb() {
sqlite3 *db;
char *err;
int ret = sqlite3_open(MAGISKDB, &db);
if (ret) {
LOGE("sqlite3 open failure: %s\n", sqlite3_errstr(ret));
return NULL;
}
int ver, upgrade = 0;
sqlite3_exec(db, "PRAGMA user_version", ver_cb, &ver, &err);
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))",
NULL, NULL, &err);
// Logs
sqlite3_exec(db,
"CREATE TABLE IF NOT EXISTS logs "
"(from_uid INT, package_name TEXT, app_name TEXT, from_pid INT, "
"to_uid INT, action INT, time INT, command TEXT)",
NULL, NULL, &err);
// Settings
sqlite3_exec(db,
"CREATE TABLE IF NOT EXISTS settings "
"(key TEXT, value INT, PRIMARY KEY(key))",
NULL, NULL, &err);
ver = 3;
upgrade = 1;
}
if (ver == 3) {
// Strings
sqlite3_exec(db,
"CREATE TABLE IF NOT EXISTS strings "
"(key TEXT, value TEXT, PRIMARY KEY(key))",
NULL, NULL, &err);
ver = 4;
upgrade = 1;
}
if (ver == 4) {
sqlite3_exec(db, "UPDATE policies SET uid=uid%100000", NULL, NULL, &err);
/* Skip version 5 */
ver = 6;
upgrade = 1;
}
if (upgrade) {
// Set version
char query[32];
sprintf(query, "PRAGMA user_version=%d", ver);
sqlite3_exec(db, query, NULL, NULL, &err);
}
return db;
}
static int settings_cb(void *v, int col_num, char **data, char **col_name) { static int settings_cb(void *v, int col_num, char **data, char **col_name) {
struct db_settings *dbs = v; struct db_settings *dbs = v;
int key = -1, value; int key = -1, value;
@ -42,6 +89,24 @@ static int settings_cb(void *v, int col_num, char **data, char **col_name) {
return 0; return 0;
} }
int get_db_settings(sqlite3 *db, int key, struct db_settings *dbs) {
if (db == NULL)
return 1;
char *err;
if (key > 0) {
char query[128];
sprintf(query, "SELECT key, value FROM settings WHERE key=%d", key);
sqlite3_exec(db, query, settings_cb, dbs, &err);
} else {
sqlite3_exec(db, "SELECT key, value FROM settings", settings_cb, dbs, &err);
}
if (err) {
LOGE("sqlite3_exec: %s\n", err);
return 1;
}
return 0;
}
static int strings_cb(void *v, int col_num, char **data, char **col_name) { static int strings_cb(void *v, int col_num, char **data, char **col_name) {
struct db_strings *dbs = v; struct db_strings *dbs = v;
int key = -1; int key = -1;
@ -63,38 +128,6 @@ static int strings_cb(void *v, int col_num, char **data, char **col_name) {
return 0; return 0;
} }
sqlite3 *get_magiskdb() {
sqlite3 *db = NULL;
if (access(MAGISKDB, R_OK) == 0) {
// Open database
int ret = sqlite3_open_v2(MAGISKDB, &db, SQLITE_OPEN_READONLY, NULL);
if (ret) {
LOGE("sqlite3 open failure: %s\n", sqlite3_errstr(ret));
sqlite3_close(db);
db = NULL;
}
}
return db;
}
int get_db_settings(sqlite3 *db, int key, struct db_settings *dbs) {
if (db == NULL)
return 1;
char *err;
if (key > 0) {
char query[128];
sprintf(query, "SELECT key, value FROM settings WHERE key=%d", key);
sqlite3_exec(db, query, settings_cb, dbs, &err);
} else {
sqlite3_exec(db, "SELECT key, value FROM settings", settings_cb, dbs, &err);
}
if (err) {
LOGE("sqlite3_exec: %s\n", err);
return 1;
}
return 0;
}
int get_db_strings(sqlite3 *db, int key, struct db_strings *str) { int get_db_strings(sqlite3 *db, int key, struct db_strings *str) {
if (db == NULL) if (db == NULL)
return 1; return 1;
@ -113,6 +146,20 @@ int get_db_strings(sqlite3 *db, int key, struct db_strings *str) {
return 0; return 0;
} }
static int policy_cb(void *v, int col_num, char **data, char **col_name) {
struct su_access *su = v;
for (int i = 0; i < col_num; i++) {
if (strcmp(col_name[i], "policy") == 0)
su->policy = (policy_t) atoi(data[i]);
else if (strcmp(col_name[i], "logging") == 0)
su->log = atoi(data[i]);
else if (strcmp(col_name[i], "notification") == 0)
su->notify = atoi(data[i]);
}
LOGD("magiskdb: query policy=[%d] log=[%d] notify=[%d]\n", su->policy, su->log, su->notify);
return 0;
}
int get_uid_policy(sqlite3 *db, int uid, struct su_access *su) { int get_uid_policy(sqlite3 *db, int uid, struct su_access *su) {
if (db == NULL) if (db == NULL)
return 1; return 1;
@ -127,7 +174,7 @@ int get_uid_policy(sqlite3 *db, int uid, struct su_access *su) {
return 0; return 0;
} }
int validate_manager(char *pkg, int userid, struct stat *st) { int validate_manager(char *alt_pkg, int userid, struct stat *st) {
if (st == NULL) { if (st == NULL) {
struct stat stat; struct stat stat;
st = &stat; st = &stat;
@ -135,19 +182,44 @@ int validate_manager(char *pkg, int userid, struct stat *st) {
// Prefer DE storage // Prefer DE storage
const char *base = access("/data/user_de", F_OK) == 0 ? "/data/user_de" : "/data/user"; const char *base = access("/data/user_de", F_OK) == 0 ? "/data/user_de" : "/data/user";
char app_path[128]; char app_path[128];
sprintf(app_path, "%s/%d/%s", base, userid, pkg[0] ? pkg : "xxx"); sprintf(app_path, "%s/%d/%s", base, userid, alt_pkg[0] ? alt_pkg : "xxx");
if (stat(app_path, st)) { if (stat(app_path, st)) {
// Check the official package name // Check the official package name
sprintf(app_path, "%s/%d/"JAVA_PACKAGE_NAME, base, userid); sprintf(app_path, "%s/%d/"JAVA_PACKAGE_NAME, base, userid);
if (stat(app_path, st)) { if (stat(app_path, st)) {
LOGE("su: cannot find manager"); LOGE("su: cannot find manager");
memset(st, 0, sizeof(*st)); memset(st, 0, sizeof(*st));
pkg[0] = '\0'; alt_pkg[0] = '\0';
return 1; return 1;
} else { } else {
// Switch to official package if exists // Switch to official package if exists
strcpy(pkg, JAVA_PACKAGE_NAME); strcpy(alt_pkg, JAVA_PACKAGE_NAME);
} }
} }
return 0; return 0;
} }
static int print_cb(void *v, int col_num, char **data, char **col_name) {
for (int i = 0; i < col_num; ++i) {
if (i) printf("|");
printf("%s=%s", col_name[i], data[i]);
}
printf("\n");
return 0;
}
int exec_sql(const char *sql) {
sqlite3 *db = get_magiskdb();
if (db) {
char *err;
sqlite3_exec(db, sql, print_cb, NULL, &err);
sqlite3_close_v2(db);
if (err) {
fprintf(stderr, "sql_err: %s\n", err);
sqlite3_free(err);
return 1;
}
return 0;
}
return 1;
}

View File

@ -8,6 +8,7 @@
#include "magisk.h" #include "magisk.h"
#include "daemon.h" #include "daemon.h"
#include "selinux.h" #include "selinux.h"
#include "db.h"
#include "flags.h" #include "flags.h"
static int create_links(const char *bin, const char *path) { static int create_links(const char *bin, const char *path) {
@ -43,6 +44,7 @@ static void usage() {
" --unlock-blocks set BLKROSET flag to OFF for all block devices\n" " --unlock-blocks set BLKROSET flag to OFF for all block devices\n"
" --restorecon fix selinux context on Magisk files and folders\n" " --restorecon fix selinux context on Magisk files and folders\n"
" --clone-attr SRC DEST clone permission, owner, and selinux context\n" " --clone-attr SRC DEST clone permission, owner, and selinux context\n"
" --sqlite SQL exec SQL to Magisk database\n"
"\n" "\n"
"Supported init triggers:\n" "Supported init triggers:\n"
" startup, post-fs-data, service, boot-complete\n" " startup, post-fs-data, service, boot-complete\n"
@ -110,6 +112,8 @@ int magisk_main(int argc, char *argv[]) {
int fd = connect_daemon(); int fd = connect_daemon();
write_int(fd, BOOT_COMPLETE); write_int(fd, BOOT_COMPLETE);
return read_int(fd); return read_int(fd);
} else if (strcmp(argv[1], "--sqlite") == 0) {
return exec_sql(argv[2]);
} }
usage(); usage();

View File

@ -119,6 +119,7 @@ sqlite3 *get_magiskdb();
int get_db_settings(sqlite3 *db, int key, struct db_settings *dbs); int get_db_settings(sqlite3 *db, int key, struct db_settings *dbs);
int get_db_strings(sqlite3 *db, int key, struct db_strings *str); int get_db_strings(sqlite3 *db, int key, struct db_strings *str);
int get_uid_policy(sqlite3 *db, int uid, struct su_access *su); int get_uid_policy(sqlite3 *db, int uid, struct su_access *su);
int validate_manager(char *pkg, int userid, struct stat *st); int validate_manager(char *alt_pkg, int userid, struct stat *st);
int exec_sql(const char *sql);
#endif //DB_H #endif //DB_H

View File

@ -75,7 +75,7 @@ static void database_check(struct su_info *info) {
if (uid > 0) if (uid > 0)
get_uid_policy(db, uid, &info->access); get_uid_policy(db, uid, &info->access);
sqlite3_close(db); sqlite3_close_v2(db);
} }
// We need to check our manager // We need to check our manager