package biz.bokhorst.xprivacy; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.WeakHashMap; import java.util.concurrent.TimeUnit; import android.accounts.Account; import android.accounts.AccountManager; import android.accounts.AccountManagerCallback; import android.accounts.AccountManagerFuture; import android.accounts.AuthenticatorDescription; import android.accounts.AuthenticatorException; import android.accounts.OnAccountsUpdateListener; import android.accounts.OperationCanceledException; import android.os.Binder; import android.os.Bundle; import android.util.Log; public class XAccountManager extends XHook { private Methods mMethod; private String mClassName; private static final String cClassName = "android.accounts.AccountManager"; private static final Map<OnAccountsUpdateListener, XOnAccountsUpdateListener> mListener = new WeakHashMap<OnAccountsUpdateListener, XOnAccountsUpdateListener>(); private XAccountManager(Methods method, String restrictionName) { super(restrictionName, method.name().replace("Srv_", ""), method.name()); mMethod = method; mClassName = "com.android.server.accounts.AccountManagerService"; } private XAccountManager(Methods method, String restrictionName, String className) { super(restrictionName, method.name(), null); mMethod = method; mClassName = className; } public String getClassName() { return mClassName; } // @formatter:off // public void addOnAccountsUpdatedListener(final OnAccountsUpdateListener listener, Handler handler, boolean updateImmediately) // public String blockingGetAuthToken(Account account, String authTokenType, boolean notifyAuthFailure) // public Account[] getAccounts() // public Account[] getAccountsByType(String type) // public Account[] getAccountsByTypeForPackage(String type, String packageName) // public AccountManagerFuture<Account[]> getAccountsByTypeAndFeatures(final String type, final String[] features, AccountManagerCallback<Account[]> callback, Handler handler) // public AuthenticatorDescription[] getAuthenticatorTypes() // public AccountManagerFuture<Bundle> getAuthToken(final Account account, final String authTokenType, final Bundle options, final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) // public AccountManagerFuture<Bundle> getAuthToken(final Account account, final String authTokenType, final boolean notifyAuthFailure, AccountManagerCallback<Bundle> callback, Handler handler) // public AccountManagerFuture<Bundle> getAuthToken(final Account account, final String authTokenType, final Bundle options, final boolean notifyAuthFailure, AccountManagerCallback<Bundle> callback, Handler handler) // public AccountManagerFuture<Bundle> getAuthTokenByFeatures(final String accountType, final String authTokenType, final String[] features, final Activity activity, final Bundle addAccountOptions, final Bundle getAuthTokenOptions, final AccountManagerCallback<Bundle> callback, final Handler handler) // public AccountManagerFuture<Boolean> hasFeatures(final Account account, final String[] features, AccountManagerCallback<Boolean> callback, Handler handler) // public void removeOnAccountsUpdatedListener(OnAccountsUpdateListener listener) // frameworks/base/core/java/android/accounts/AccountManager.java // http://developer.android.com/reference/android/accounts/AccountManager.html // @formatter:on // @formatter:off // public android.accounts.Account[] getAccounts(java.lang.String accountType) throws android.os.RemoteException; // public android.accounts.Account[] getAccountsAsUser(java.lang.String accountType, int userId) throws android.os.RemoteException; // public void getAccountsByFeatures(android.accounts.IAccountManagerResponse response, java.lang.String accountType, java.lang.String[] features) throws android.os.RemoteException; // public android.accounts.Account[] getAccountsForPackage(java.lang.String packageName, int uid) // public android.accounts.Account[] getSharedAccountsAsUser(int userId) throws android.os.RemoteException; // http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.4.2_r1/com/android/server/accounts/AccountManagerService.java // http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/5.0.0_r1/android/accounts/IAccountManager.java // @formatter:on // @formatter:off private enum Methods { addOnAccountsUpdatedListener, blockingGetAuthToken, getAccounts, getAccountsByType, getAccountsByTypeForPackage, getAccountsByTypeAndFeatures, getAuthenticatorTypes, getAuthToken, getAuthTokenByFeatures, hasFeatures, removeOnAccountsUpdatedListener, Srv_getAccounts, Srv_getAccountsAsUser, Srv_getAccountsByFeatures, Srv_getAccountsForPackage, Srv_getSharedAccountsAsUser }; // @formatter:on public static List<XHook> getInstances(String className, boolean server) { List<XHook> listHook = new ArrayList<XHook>(); if (!cClassName.equals(className)) { if (className == null) className = cClassName; if (server) { listHook.add(new XAccountManager(Methods.Srv_getAccounts, PrivacyManager.cAccounts)); listHook.add(new XAccountManager(Methods.Srv_getAccountsAsUser, PrivacyManager.cAccounts)); listHook.add(new XAccountManager(Methods.Srv_getAccountsByFeatures, PrivacyManager.cAccounts)); listHook.add(new XAccountManager(Methods.Srv_getAccountsForPackage, PrivacyManager.cAccounts)); listHook.add(new XAccountManager(Methods.Srv_getSharedAccountsAsUser, PrivacyManager.cAccounts)); } else { listHook.add(new XAccountManager(Methods.addOnAccountsUpdatedListener, PrivacyManager.cAccounts, className)); listHook.add(new XAccountManager(Methods.blockingGetAuthToken, PrivacyManager.cAccounts, className)); listHook.add(new XAccountManager(Methods.getAccounts, PrivacyManager.cAccounts, className)); listHook.add(new XAccountManager(Methods.getAccountsByType, PrivacyManager.cAccounts, className)); listHook.add(new XAccountManager(Methods.getAccountsByTypeForPackage, PrivacyManager.cAccounts, className)); listHook.add(new XAccountManager(Methods.getAccountsByTypeAndFeatures, PrivacyManager.cAccounts, className)); listHook.add(new XAccountManager(Methods.getAuthenticatorTypes, PrivacyManager.cAccounts, className)); listHook.add(new XAccountManager(Methods.getAuthToken, PrivacyManager.cAccounts, className)); listHook.add(new XAccountManager(Methods.getAuthTokenByFeatures, PrivacyManager.cAccounts, className)); listHook.add(new XAccountManager(Methods.hasFeatures, PrivacyManager.cAccounts, className)); listHook.add(new XAccountManager(Methods.removeOnAccountsUpdatedListener, null, className)); } } return listHook; } @Override @SuppressWarnings("unchecked") protected void before(XParam param) throws Throwable { switch (mMethod) { case addOnAccountsUpdatedListener: if (param.args.length > 0 && param.args[0] != null) if (isRestricted(param)) { int uid = Binder.getCallingUid(); OnAccountsUpdateListener listener = (OnAccountsUpdateListener) param.args[0]; XOnAccountsUpdateListener xListener; synchronized (mListener) { xListener = mListener.get(listener); if (xListener == null) { xListener = new XOnAccountsUpdateListener(listener, uid); mListener.put(listener, xListener); Util.log(this, Log.WARN, "Added count=" + mListener.size() + " uid=" + uid); } } param.args[0] = xListener; } break; case blockingGetAuthToken: case getAccounts: case getAccountsByType: case getAccountsByTypeForPackage: // Do nothing break; case getAccountsByTypeAndFeatures: if (param.args.length > 2 && param.args[2] != null) if (isRestrictedExtra(param, (String) param.args[0])) { AccountManagerCallback<Account[]> callback = (AccountManagerCallback<Account[]>) param.args[2]; param.args[2] = new XAccountManagerCallbackAccount(callback, Binder.getCallingUid()); } break; case getAuthenticatorTypes: // Do nothing break; case getAuthToken: if (param.args.length > 0) { Account account = (Account) param.args[0]; for (int i = 1; i < param.args.length; i++) if (param.args[i] instanceof AccountManagerCallback<?>) if (isRestrictedExtra(param, account == null ? null : account.name)) { AccountManagerCallback<Bundle> callback = (AccountManagerCallback<Bundle>) param.args[i]; param.args[i] = new XAccountManagerCallbackBundle(callback, Binder.getCallingUid()); } } break; case getAuthTokenByFeatures: if (param.args.length > 0) for (int i = 1; i < param.args.length; i++) if (param.args[i] instanceof AccountManagerCallback<?>) if (isRestrictedExtra(param, (String) param.args[0])) { AccountManagerCallback<Bundle> callback = (AccountManagerCallback<Bundle>) param.args[i]; param.args[i] = new XAccountManagerCallbackBundle(callback, Binder.getCallingUid()); } break; case hasFeatures: if (param.args.length > 0) { Account account = (Account) param.args[0]; for (int i = 1; i < param.args.length; i++) if (param.args[i] instanceof AccountManagerCallback<?>) if (isRestrictedExtra(param, account == null ? null : account.name)) { AccountManagerCallback<Boolean> callback = (AccountManagerCallback<Boolean>) param.args[i]; param.args[i] = new XAccountManagerCallbackBoolean(callback); } } break; case removeOnAccountsUpdatedListener: if (param.args.length > 0 && param.args[0] != null) synchronized (mListener) { OnAccountsUpdateListener listener = (OnAccountsUpdateListener) param.args[0]; XOnAccountsUpdateListener xListener = mListener.get(listener); if (xListener != null) { param.args[0] = xListener; Util.log(this, Log.WARN, "Removed count=" + mListener.size() + " uid=" + Binder.getCallingUid()); } } break; case Srv_getAccounts: case Srv_getAccountsAsUser: case Srv_getAccountsForPackage: // Do nothing break; case Srv_getAccountsByFeatures: if (param.args.length > 1 && (param.args[1] == null || param.args[1] instanceof String)) { if (isRestrictedExtra(param, (String) param.args[1])) param.setResult(null); } else { if (isRestricted(param)) param.setResult(null); } break; case Srv_getSharedAccountsAsUser: // Do nothing break; } } @Override @SuppressWarnings("unchecked") protected void after(XParam param) throws Throwable { int uid = Binder.getCallingUid(); switch (mMethod) { case addOnAccountsUpdatedListener: // Do nothing break; case blockingGetAuthToken: if (param.getResult() != null && param.args.length > 0 && param.args[0] != null) { Account account = (Account) param.args[0]; if (isRestrictedExtra(param, account == null ? null : account.name)) if (!isAccountAllowed(account, uid)) param.setResult(null); } break; case getAccounts: if (param.getResult() != null && isRestricted(param)) { Account[] accounts = (Account[]) param.getResult(); param.setResult(filterAccounts(accounts, uid)); } break; case getAccountsByType: case getAccountsByTypeForPackage: if (param.getResult() != null && param.args.length > 0) if (isRestrictedExtra(param, (String) param.args[0])) { Account[] accounts = (Account[]) param.getResult(); param.setResult(filterAccounts(accounts, uid)); } break; case getAccountsByTypeAndFeatures: if (param.getResult() != null && param.args.length > 0) if (isRestrictedExtra(param, (String) param.args[0])) { AccountManagerFuture<Account[]> future = (AccountManagerFuture<Account[]>) param.getResult(); param.setResult(new XFutureAccount(future, uid)); } break; case getAuthenticatorTypes: if (param.getResult() != null && isRestricted(param)) param.setResult(new AuthenticatorDescription[0]); break; case getAuthToken: if (param.getResult() != null && param.args.length > 0) { Account account = (Account) param.args[0]; if (isRestrictedExtra(param, account == null ? null : account.name)) { AccountManagerFuture<Bundle> future = (AccountManagerFuture<Bundle>) param.getResult(); param.setResult(new XFutureBundle(future, uid)); } } break; case getAuthTokenByFeatures: if (param.getResult() != null) if (isRestrictedExtra(param, (String) param.args[0])) { AccountManagerFuture<Bundle> future = (AccountManagerFuture<Bundle>) param.getResult(); param.setResult(new XFutureBundle(future, uid)); } break; case hasFeatures: if (param.getResult() != null && param.args.length > 0 && param.args[0] != null) { Account account = (Account) param.args[0]; if (isRestrictedExtra(param, account == null ? null : account.name)) if (!isAccountAllowed(account, uid)) param.setResult(new XFutureBoolean()); } break; case removeOnAccountsUpdatedListener: // Do nothing break; case Srv_getAccounts: case Srv_getAccountsAsUser: case Srv_getAccountsForPackage: case Srv_getSharedAccountsAsUser: // Filter account list String extra = null; if (mMethod == Methods.Srv_getAccounts || mMethod == Methods.Srv_getAccountsAsUser || mMethod == Methods.Srv_getAccountsForPackage) if (param.args.length > 0 && param.args[0] instanceof String) extra = (String) param.args[0]; if (param.getResult() instanceof Account[]) if (isRestrictedExtra(param, extra)) { Account[] accounts = (Account[]) param.getResult(); param.setResult(filterAccounts(accounts, uid)); } break; case Srv_getAccountsByFeatures: // Do nothing break; } } private Account[] filterAccounts(Account[] original, int uid) { List<Account> listAccount = new ArrayList<Account>(); for (Account account : original) if (isAccountAllowed(account, uid)) listAccount.add(account); return listAccount.toArray(new Account[0]); } public static boolean isAccountAllowed(Account account, int uid) { return isAccountAllowed(account.name, account.type, uid); } public static boolean isAccountAllowed(String accountName, String accountType, int uid) { try { boolean allowed = PrivacyManager.getSettingBool(-uid, Meta.cTypeAccountHash, accountName + accountType, false); boolean blacklist = PrivacyManager.getSettingBool(-uid, PrivacyManager.cSettingBlacklist, false); if (blacklist) allowed = !allowed; return allowed; } catch (Throwable ex) { Util.bug(null, ex); return false; } } private class XFutureAccount implements AccountManagerFuture<Account[]> { private AccountManagerFuture<Account[]> mFuture; private int mUid; public XFutureAccount(AccountManagerFuture<Account[]> future, int uid) { mFuture = future; mUid = uid; } @Override public boolean cancel(boolean mayInterruptIfRunning) { return mFuture.cancel(mayInterruptIfRunning); } @Override public Account[] getResult() throws OperationCanceledException, IOException, AuthenticatorException { Account[] account = mFuture.getResult(); return XAccountManager.this.filterAccounts(account, mUid); } @Override public Account[] getResult(long timeout, TimeUnit unit) throws OperationCanceledException, IOException, AuthenticatorException { Account[] account = mFuture.getResult(timeout, unit); return XAccountManager.this.filterAccounts(account, mUid); } @Override public boolean isCancelled() { return mFuture.isCancelled(); } @Override public boolean isDone() { return mFuture.isDone(); } } private class XFutureBoolean implements AccountManagerFuture<Boolean> { @Override public boolean cancel(boolean mayInterruptIfRunning) { return false; } @Override public Boolean getResult() throws OperationCanceledException, IOException, AuthenticatorException { return false; } @Override public Boolean getResult(long timeout, TimeUnit unit) throws OperationCanceledException, IOException, AuthenticatorException { return false; } @Override public boolean isCancelled() { return false; } @Override public boolean isDone() { return true; } } private class XFutureBundle implements AccountManagerFuture<Bundle> { private AccountManagerFuture<Bundle> mFuture; private int mUid; public XFutureBundle(AccountManagerFuture<Bundle> future, int uid) { mFuture = future; mUid = uid; } @Override public boolean cancel(boolean mayInterruptIfRunning) { return mFuture.cancel(mayInterruptIfRunning); } @Override public Bundle getResult() throws OperationCanceledException, IOException, AuthenticatorException { Bundle bundle = mFuture.getResult(); String accountName = bundle.getString(AccountManager.KEY_ACCOUNT_NAME); String accountType = bundle.getString(AccountManager.KEY_ACCOUNT_TYPE); if (isAccountAllowed(accountName, accountType, mUid)) return bundle; else throw new OperationCanceledException("XPrivacy"); } @Override public Bundle getResult(long timeout, TimeUnit unit) throws OperationCanceledException, IOException, AuthenticatorException { Bundle bundle = mFuture.getResult(timeout, unit); String accountName = bundle.getString(AccountManager.KEY_ACCOUNT_NAME); String accountType = bundle.getString(AccountManager.KEY_ACCOUNT_TYPE); if (isAccountAllowed(accountName, accountType, mUid)) return bundle; else throw new OperationCanceledException("XPrivacy"); } @Override public boolean isCancelled() { return mFuture.isCancelled(); } @Override public boolean isDone() { return mFuture.isDone(); } } private class XOnAccountsUpdateListener implements OnAccountsUpdateListener { private OnAccountsUpdateListener mListener; private int mUid; public XOnAccountsUpdateListener(OnAccountsUpdateListener listener, int uid) { mListener = listener; mUid = uid; } @Override public void onAccountsUpdated(Account[] accounts) { mListener.onAccountsUpdated(XAccountManager.this.filterAccounts(accounts, mUid)); } } private class XAccountManagerCallbackAccount implements AccountManagerCallback<Account[]> { private AccountManagerCallback<Account[]> mCallback; private int mUid; public XAccountManagerCallbackAccount(AccountManagerCallback<Account[]> callback, int uid) { mCallback = callback; mUid = uid; } @Override public void run(AccountManagerFuture<Account[]> future) { mCallback.run(new XAccountManager.XFutureAccount(future, mUid)); } } private class XAccountManagerCallbackBundle implements AccountManagerCallback<Bundle> { private AccountManagerCallback<Bundle> mCallback; private int mUid; public XAccountManagerCallbackBundle(AccountManagerCallback<Bundle> callback, int uid) { mCallback = callback; mUid = uid; } @Override public void run(AccountManagerFuture<Bundle> future) { mCallback.run(new XAccountManager.XFutureBundle(future, mUid)); } } private class XAccountManagerCallbackBoolean implements AccountManagerCallback<Boolean> { private AccountManagerCallback<Boolean> mCallback; public XAccountManagerCallbackBoolean(AccountManagerCallback<Boolean> callback) { mCallback = callback; } @Override public void run(AccountManagerFuture<Boolean> future) { mCallback.run(new XAccountManager.XFutureBoolean()); } } }