diff --git a/app/src/full/java/com/topjohnwu/magisk/Const.java b/app/src/full/java/com/topjohnwu/magisk/Const.java index c2a4a70c0..5db6fb5bc 100644 --- a/app/src/full/java/com/topjohnwu/magisk/Const.java +++ b/app/src/full/java/com/topjohnwu/magisk/Const.java @@ -52,6 +52,7 @@ public class Const { public static final int SEPOL_REFACTOR = 1640; public static final int FIX_ENV = 1650; public static final int DBVER_SIX = 17000; + public static final int CMDLINE_DB = 17305; } public static class ID { diff --git a/app/src/full/java/com/topjohnwu/magisk/SuRequestActivity.java b/app/src/full/java/com/topjohnwu/magisk/SuRequestActivity.java index 1e3f4601c..f88ce7467 100644 --- a/app/src/full/java/com/topjohnwu/magisk/SuRequestActivity.java +++ b/app/src/full/java/com/topjohnwu/magisk/SuRequestActivity.java @@ -252,7 +252,7 @@ public class SuRequestActivity extends BaseActivity { policy.policy = action; if (time >= 0) { policy.until = (time == 0) ? 0 : (System.currentTimeMillis() / 1000 + time * 60); - mm.mDB.addPolicy(policy); + mm.mDB.updatePolicy(policy); } handleAction(); } diff --git a/app/src/full/java/com/topjohnwu/magisk/database/MagiskDB.java b/app/src/full/java/com/topjohnwu/magisk/database/MagiskDB.java index c10646f02..da67deaeb 100644 --- a/app/src/full/java/com/topjohnwu/magisk/database/MagiskDB.java +++ b/app/src/full/java/com/topjohnwu/magisk/database/MagiskDB.java @@ -1,60 +1,66 @@ package com.topjohnwu.magisk.database; 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.Utils; +import com.topjohnwu.superuser.Shell; import java.io.File; +import java.util.Collections; import java.util.List; import androidx.annotation.NonNull; -public abstract class MagiskDB { +public class MagiskDB { - public static final int DATABASE_VER = 6; - public static final int OLD_DATABASE_VER = 5; - public static final String POLICY_TABLE = "policies"; - public static final String LOG_TABLE = "logs"; - public static final String SETTINGS_TABLE = "settings"; - public static final String STRINGS_TABLE = "strings"; - public static final File LEGACY_MANAGER_DB = + static final String POLICY_TABLE = "policies"; + static final String LOG_TABLE = "logs"; + static final String SETTINGS_TABLE = "settings"; + static final String STRINGS_TABLE = "strings"; + static final File LEGACY_MANAGER_DB = new File(Utils.fmt("/sbin/.core/db-%d/magisk.db", Const.USER_ID)); @NonNull 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) { 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 getPolicyList() { return Collections.emptyList(); } - public abstract List getPolicyList(); + public List> getLogs() { return Collections.emptyList(); } - public abstract List> 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 abstract String getStrings(String key, String defaultValue); + public String getStrings(String key, String defaultValue) { return defaultValue; } } diff --git a/app/src/full/java/com/topjohnwu/magisk/database/MagiskDBCmdline.java b/app/src/full/java/com/topjohnwu/magisk/database/MagiskDBCmdline.java new file mode 100644 index 000000000..d312f5aed --- /dev/null +++ b/app/src/full/java/com/topjohnwu/magisk/database/MagiskDBCmdline.java @@ -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 rawSQL(String fmt, Object... args) { + return Shell.su("magisk --sqlite '" + Utils.fmt(fmt, args) + "'").exec().getOut(); + } + + private List SQL(String fmt, Object... args) { + List 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 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 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 getPolicyList() { + List 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> getLogs() { + List> ret = new ArrayList<>(); + List 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 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 res = SQL("SELECT value FROM %s WHERE key=\"%s\"", STRINGS_TABLE, key); + if (res.isEmpty()) + return defaultValue; + return res.get(0).getAsString("value"); + } +} diff --git a/app/src/full/java/com/topjohnwu/magisk/database/MagiskDBLegacy.java b/app/src/full/java/com/topjohnwu/magisk/database/MagiskDBLegacy.java index 350267eae..490950eb8 100644 --- a/app/src/full/java/com/topjohnwu/magisk/database/MagiskDBLegacy.java +++ b/app/src/full/java/com/topjohnwu/magisk/database/MagiskDBLegacy.java @@ -30,6 +30,9 @@ import java.util.List; public class MagiskDBLegacy extends MagiskDB { + private static final int DATABASE_VER = 6; + private static final int OLD_DATABASE_VER = 5; + private PackageManager pm; private SQLiteDatabase db; @@ -193,14 +196,9 @@ public class MagiskDBLegacy extends MagiskDB { return policy; } - @Override - public void addPolicy(Policy policy) { - db.replace(POLICY_TABLE, null, policy.getContentValues()); - } - @Override 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 diff --git a/native/jni/daemon/bootstages.c b/native/jni/daemon/bootstages.c index e0a9d390e..0ac684d07 100644 --- a/native/jni/daemon/bootstages.c +++ b/native/jni/daemon/bootstages.c @@ -942,13 +942,16 @@ core_only: } else { // Check whether we have a valid manager installed sqlite3 *db = get_magiskdb(); - struct db_strings str; - memset(&str, 0, sizeof(str)); - get_db_strings(db, SU_MANAGER, &str); - if (validate_manager(str.s[SU_MANAGER], 0, NULL)) { - // There is no manager installed, install the stub - exec_command_sync("/sbin/magiskinit", "-x", "manager", "/data/magisk.apk", NULL); - install_apk("/data/magisk.apk"); + if (db) { + struct db_strings str; + memset(&str, 0, sizeof(str)); + get_db_strings(db, SU_MANAGER, &str); + if (validate_manager(str.s[SU_MANAGER], 0, NULL)) { + // There is no manager installed, install the stub + exec_command_sync("/sbin/magiskinit", "-x", "manager", "/data/magisk.apk", NULL); + install_apk("/data/magisk.apk"); + } + sqlite3_close_v2(db); } } diff --git a/native/jni/daemon/db.c b/native/jni/daemon/db.c index f676f6ec2..cab87a345 100644 --- a/native/jni/daemon/db.c +++ b/native/jni/daemon/db.c @@ -8,20 +8,67 @@ #include "magisk.h" #include "db.h" -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); +static int ver_cb(void *v, int col_num, char **data, char **col_name) { + *((int *) v) = atoi(data[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) { struct db_settings *dbs = v; int key = -1, value; @@ -42,6 +89,24 @@ static int settings_cb(void *v, int col_num, char **data, char **col_name) { 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) { struct db_strings *dbs = v; int key = -1; @@ -63,38 +128,6 @@ static int strings_cb(void *v, int col_num, char **data, char **col_name) { 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) { if (db == NULL) return 1; @@ -113,6 +146,20 @@ int get_db_strings(sqlite3 *db, int key, struct db_strings *str) { 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) { if (db == NULL) return 1; @@ -127,7 +174,7 @@ int get_uid_policy(sqlite3 *db, int uid, struct su_access *su) { 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) { struct stat stat; st = &stat; @@ -135,19 +182,44 @@ int validate_manager(char *pkg, int userid, struct stat *st) { // Prefer DE storage const char *base = access("/data/user_de", F_OK) == 0 ? "/data/user_de" : "/data/user"; 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)) { // Check the official package name sprintf(app_path, "%s/%d/"JAVA_PACKAGE_NAME, base, userid); if (stat(app_path, st)) { LOGE("su: cannot find manager"); memset(st, 0, sizeof(*st)); - pkg[0] = '\0'; + alt_pkg[0] = '\0'; return 1; } else { // Switch to official package if exists - strcpy(pkg, JAVA_PACKAGE_NAME); + strcpy(alt_pkg, JAVA_PACKAGE_NAME); } } 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; +} diff --git a/native/jni/daemon/magisk.c b/native/jni/daemon/magisk.c index cca109570..6520f3727 100644 --- a/native/jni/daemon/magisk.c +++ b/native/jni/daemon/magisk.c @@ -8,6 +8,7 @@ #include "magisk.h" #include "daemon.h" #include "selinux.h" +#include "db.h" #include "flags.h" 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" " --restorecon fix selinux context on Magisk files and folders\n" " --clone-attr SRC DEST clone permission, owner, and selinux context\n" + " --sqlite SQL exec SQL to Magisk database\n" "\n" "Supported init triggers:\n" " startup, post-fs-data, service, boot-complete\n" @@ -110,6 +112,8 @@ int magisk_main(int argc, char *argv[]) { int fd = connect_daemon(); write_int(fd, BOOT_COMPLETE); return read_int(fd); + } else if (strcmp(argv[1], "--sqlite") == 0) { + return exec_sql(argv[2]); } usage(); diff --git a/native/jni/include/db.h b/native/jni/include/db.h index 25a6b3a4c..3c9c92f51 100644 --- a/native/jni/include/db.h +++ b/native/jni/include/db.h @@ -119,6 +119,7 @@ sqlite3 *get_magiskdb(); 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_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 diff --git a/native/jni/su/su_daemon.c b/native/jni/su/su_daemon.c index 61a0a9117..5093f7763 100644 --- a/native/jni/su/su_daemon.c +++ b/native/jni/su/su_daemon.c @@ -75,7 +75,7 @@ static void database_check(struct su_info *info) { if (uid > 0) get_uid_policy(db, uid, &info->access); - sqlite3_close(db); + sqlite3_close_v2(db); } // We need to check our manager