diff options
Diffstat (limited to 'cmds')
33 files changed, 2518 insertions, 824 deletions
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java index 38cacdd09b7d..424b70ab8849 100644 --- a/cmds/am/src/com/android/commands/am/Am.java +++ b/cmds/am/src/com/android/commands/am/Am.java @@ -98,6 +98,8 @@ public class Am { runStart(); } else if (op.equals("startservice")) { runStartService(); + } else if (op.equals("force-stop")) { + runForceStop(); } else if (op.equals("instrument")) { runInstrument(); } else if (op.equals("broadcast")) { @@ -365,6 +367,10 @@ public class Am { } } + private void runForceStop() throws Exception { + mAm.forceStopPackage(nextArgRequired()); + } + private void sendBroadcast() throws Exception { Intent intent = makeIntent(); IntentReceiver receiver = new IntentReceiver(); @@ -851,7 +857,7 @@ public class Am { wm.clearForcedDisplaySize(); } } catch (RemoteException e) { - } + } } private class IntentReceiver extends IIntentReceiver.Stub { @@ -1013,6 +1019,8 @@ public class Am { "\n" + " start a Service: am startservice <INTENT>\n" + "\n" + + " force stop everything associated with a package: force-stop <package>\n" + + "\n" + " send a broadcast Intent: am broadcast <INTENT>\n" + "\n" + " start an Instrumentation: am instrument [flags] <COMPONENT>\n" + diff --git a/cmds/app_process/app_main.cpp b/cmds/app_process/app_main.cpp index 0159eddc0965..152a7cb7132b 100644 --- a/cmds/app_process/app_main.cpp +++ b/cmds/app_process/app_main.cpp @@ -1,8 +1,8 @@ /* * Main entry of app process. - * + * * Starts the interpreted runtime, then starts up the application. - * + * */ #define LOG_TAG "appproc" @@ -25,23 +25,13 @@ void app_usage() "Usage: app_process [java-options] cmd-dir start-class-name [options]\n"); } -status_t app_init(const char* className, int argc, const char* const argv[]) -{ - LOGV("Entered app_init()!\n"); - - AndroidRuntime* jr = AndroidRuntime::getRuntime(); - jr->callMain(className, argc, argv); - - LOGV("Exiting app_init()!\n"); - return NO_ERROR; -} - class AppRuntime : public AndroidRuntime { public: AppRuntime() : mParentDir(NULL) , mClassName(NULL) + , mClass(NULL) , mArgC(0) , mArgV(NULL) { @@ -60,6 +50,35 @@ public: return mClassName; } + virtual void onVmCreated(JNIEnv* env) + { + if (mClassName == NULL) { + return; // Zygote. Nothing to do here. + } + + /* + * This is a little awkward because the JNI FindClass call uses the + * class loader associated with the native method we're executing in. + * If called in onStarted (from RuntimeInit.finishInit because we're + * launching "am", for example), FindClass would see that we're calling + * from a boot class' native method, and so wouldn't look for the class + * we're trying to look up in CLASSPATH. Unfortunately it needs to, + * because the "am" classes are not boot classes. + * + * The easiest fix is to call FindClass here, early on before we start + * executing boot class Java code and thereby deny ourselves access to + * non-boot classes. + */ + char* slashClassName = toSlashClassName(mClassName); + mClass = env->FindClass(slashClassName); + if (mClass == NULL) { + LOGE("ERROR: could not find class '%s'\n", mClassName); + } + free(slashClassName); + + mClass = reinterpret_cast<jclass>(env->NewGlobalRef(mClass)); + } + virtual void onStarted() { sp<ProcessState> proc = ProcessState::self(); @@ -67,8 +86,9 @@ public: LOGV("App process: starting thread pool.\n"); proc->startThreadPool(); } - - app_init(mClassName, mArgC, mArgV); + + AndroidRuntime* ar = AndroidRuntime::getRuntime(); + ar->callMain(mClassName, mClass, mArgC, mArgV); if (ProcessState::self()->supportsProcesses()) { IPCThreadState::self()->stopProcess(); @@ -81,7 +101,7 @@ public: if (proc->supportsProcesses()) { LOGV("App process: starting thread pool.\n"); proc->startThreadPool(); - } + } } virtual void onExit(int code) @@ -96,9 +116,10 @@ public: AndroidRuntime::onExit(code); } - + const char* mParentDir; const char* mClassName; + jclass mClass; int mArgC; const char* const* mArgV; }; @@ -120,7 +141,7 @@ int main(int argc, const char* const argv[]) // These are global variables in ProcessState.cpp mArgC = argc; mArgV = argv; - + mArgLen = 0; for (int i=0; i<argc; i++) { mArgLen += strlen(argv[i]) + 1; @@ -128,10 +149,7 @@ int main(int argc, const char* const argv[]) mArgLen--; AppRuntime runtime; - const char *arg; - const char *argv0; - - argv0 = argv[0]; + const char* argv0 = argv[0]; // Process command line arguments // ignore argv[0] @@ -139,42 +157,56 @@ int main(int argc, const char* const argv[]) argv++; // Everything up to '--' or first non '-' arg goes to the vm - - int i = runtime.addVmArguments(argc, argv); - // Next arg is parent directory - if (i < argc) { - runtime.mParentDir = argv[i++]; - } + int i = runtime.addVmArguments(argc, argv); - // Next arg is startup classname or "--zygote" - if (i < argc) { - arg = argv[i++]; - if (0 == strcmp("--zygote", arg)) { - bool startSystemServer = (i < argc) ? - strcmp(argv[i], "--start-system-server") == 0 : false; - setArgv0(argv0, "zygote"); - set_process_name("zygote"); - runtime.start("com.android.internal.os.ZygoteInit", - startSystemServer); + // Parse runtime arguments. Stop at first unrecognized option. + bool zygote = false; + bool startSystemServer = false; + bool application = false; + const char* parentDir = NULL; + const char* niceName = NULL; + const char* className = NULL; + while (i < argc) { + const char* arg = argv[i++]; + if (!parentDir) { + parentDir = arg; + } else if (strcmp(arg, "--zygote") == 0) { + zygote = true; + niceName = "zygote"; + } else if (strcmp(arg, "--start-system-server") == 0) { + startSystemServer = true; + } else if (strcmp(arg, "--application") == 0) { + application = true; + } else if (strncmp(arg, "--nice-name=", 12) == 0) { + niceName = arg + 12; } else { - set_process_name(argv0); - - runtime.mClassName = arg; + className = arg; + break; + } + } - // Remainder of args get passed to startup class main() - runtime.mArgC = argc-i; - runtime.mArgV = argv+i; + if (niceName && *niceName) { + setArgv0(argv0, niceName); + set_process_name(niceName); + } - LOGV("App process is starting with pid=%d, class=%s.\n", - getpid(), runtime.getClassName()); - runtime.start(); - } + runtime.mParentDir = parentDir; + + if (zygote) { + runtime.start("com.android.internal.os.ZygoteInit", + startSystemServer ? "start-system-server" : ""); + } else if (className) { + // Remainder of args get passed to startup class main() + runtime.mClassName = className; + runtime.mArgC = argc - i; + runtime.mArgV = argv + i; + runtime.start("com.android.internal.os.RuntimeInit", + application ? "application" : "tool"); } else { fprintf(stderr, "Error: no class name or --zygote supplied.\n"); app_usage(); LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied."); return 10; } - } diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java index ac0e410fe301..38d0d2aecc56 100644 --- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java +++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java @@ -150,20 +150,13 @@ public final class Bmgr { } private void doBackup() { - boolean isFull = false; String pkg = nextArg(); - if ("-f".equals(pkg)) { - isFull = true; - pkg = nextArg(); - } - - if (pkg == null || pkg.startsWith("-")) { + if (pkg == null) { showUsage(); return; } try { - // !!! TODO: handle full backup mBmgr.dataChanged(pkg); } catch (RemoteException e) { System.err.println(e.toString()); diff --git a/cmds/bootanimation/Android.mk b/cmds/bootanimation/Android.mk index 2b8975943089..a00a212914a9 100644 --- a/cmds/bootanimation/Android.mk +++ b/cmds/bootanimation/Android.mk @@ -22,7 +22,7 @@ LOCAL_SHARED_LIBRARIES := \ libskia \ libEGL \ libGLESv1_CM \ - libsurfaceflinger_client + libgui LOCAL_C_INCLUDES := \ $(call include-path-for, corecg graphics) diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp index e07495d28dcb..69c459705e2e 100644 --- a/cmds/bootanimation/BootAnimation.cpp +++ b/cmds/bootanimation/BootAnimation.cpp @@ -212,7 +212,7 @@ status_t BootAnimation::readyToRun() { // create the native surface sp<SurfaceControl> control = session()->createSurface( - getpid(), 0, dinfo.w, dinfo.h, PIXEL_FORMAT_RGB_565); + 0, dinfo.w, dinfo.h, PIXEL_FORMAT_RGB_565); session()->openTransaction(); control->setLayer(0x40000000); session()->closeTransaction(); @@ -302,6 +302,7 @@ bool BootAnimation::android() glShadeModel(GL_FLAT); glDisable(GL_DITHER); glDisable(GL_SCISSOR_TEST); + glClearColor(0,0,0,1); glClear(GL_COLOR_BUFFER_BIT); eglSwapBuffers(mDisplay, mSurface); @@ -439,6 +440,7 @@ bool BootAnimation::movie() glDisable(GL_DITHER); glDisable(GL_SCISSOR_TEST); glDisable(GL_BLEND); + glClearColor(0,0,0,1); glClear(GL_COLOR_BUFFER_BIT); eglSwapBuffers(mDisplay, mSurface); diff --git a/cmds/bu/Android.mk b/cmds/bu/Android.mk new file mode 100644 index 000000000000..4fd5fecdd8b9 --- /dev/null +++ b/cmds/bu/Android.mk @@ -0,0 +1,18 @@ +# Copyright 2011 The Android Open Source Project +# +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := $(call all-subdir-java-files) +LOCAL_MODULE := bu +LOCAL_MODULE_TAGS := optional +include $(BUILD_JAVA_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := bu +LOCAL_SRC_FILES := bu +LOCAL_MODULE_CLASS := EXECUTABLES +LOCAL_MODULE_TAGS := optional +include $(BUILD_PREBUILT) + + diff --git a/cmds/bu/NOTICE b/cmds/bu/NOTICE new file mode 100644 index 000000000000..becc12079a43 --- /dev/null +++ b/cmds/bu/NOTICE @@ -0,0 +1,190 @@ + + Copyright (c) 2011, The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/cmds/bu/bu b/cmds/bu/bu new file mode 100755 index 000000000000..e8dbc318818f --- /dev/null +++ b/cmds/bu/bu @@ -0,0 +1,6 @@ +# Script to start "bu" on the device +# +base=/system +export CLASSPATH=$base/framework/bu.jar +exec app_process $base/bin com.android.commands.bu.Backup "$@" + diff --git a/cmds/bu/src/com/android/commands/bu/Backup.java b/cmds/bu/src/com/android/commands/bu/Backup.java new file mode 100644 index 000000000000..e81f799fda28 --- /dev/null +++ b/cmds/bu/src/com/android/commands/bu/Backup.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.commands.bu; + +import android.app.backup.IBackupManager; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.Log; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.util.ArrayList; + +public final class Backup { + static final String TAG = "bu"; + + static String[] mArgs; + int mNextArg; + IBackupManager mBackupManager; + + public static void main(String[] args) { + Log.d(TAG, "Beginning: " + args[0]); + mArgs = args; + try { + new Backup().run(); + } catch (Exception e) { + Log.e(TAG, "Error running backup/restore", e); + } + Log.d(TAG, "Finished."); + } + + public void run() { + mBackupManager = IBackupManager.Stub.asInterface(ServiceManager.getService("backup")); + if (mBackupManager == null) { + Log.e(TAG, "Can't obtain Backup Manager binder"); + return; + } + + String arg = nextArg(); + if (arg.equals("backup")) { + doFullBackup(); + } else if (arg.equals("restore")) { + doFullRestore(); + } else { + Log.e(TAG, "Invalid operation '" + arg + "'"); + } + } + + private void doFullBackup() { + ArrayList<String> packages = new ArrayList<String>(); + boolean saveApks = false; + boolean saveShared = false; + boolean doEverything = false; + + String arg; + while ((arg = nextArg()) != null) { + if (arg.startsWith("-")) { + if ("-apk".equals(arg)) { + saveApks = true; + } else if ("-noapk".equals(arg)) { + saveApks = false; + } else if ("-shared".equals(arg)) { + saveShared = true; + } else if ("-noshared".equals(arg)) { + saveShared = false; + } else if ("-all".equals(arg)) { + doEverything = true; + } else { + Log.w(TAG, "Unknown backup flag " + arg); + continue; + } + } else { + // Not a flag; treat as a package name + packages.add(arg); + } + } + + if (doEverything && packages.size() > 0) { + Log.w(TAG, "-all passed for backup along with specific package names"); + } + + if (!doEverything && !saveShared && packages.size() == 0) { + Log.e(TAG, "no backup packages supplied and neither -shared nor -all given"); + return; + } + + try { + ParcelFileDescriptor fd = ParcelFileDescriptor.dup(FileDescriptor.out); + String[] packArray = new String[packages.size()]; + mBackupManager.fullBackup(fd, saveApks, saveShared, doEverything, + packages.toArray(packArray)); + } catch (IOException e) { + Log.e(TAG, "Can't dup out"); + } catch (RemoteException e) { + Log.e(TAG, "Unable to invoke backup manager for backup"); + } + } + + private void doFullRestore() { + // No arguments to restore + try { + ParcelFileDescriptor fd = ParcelFileDescriptor.dup(FileDescriptor.in); + mBackupManager.fullRestore(fd); + } catch (IOException e) { + Log.e(TAG, "Can't dup System.in"); + } catch (RemoteException e) { + Log.e(TAG, "Unable to invoke backup manager for restore"); + } + } + + private String nextArg() { + if (mNextArg >= mArgs.length) { + return null; + } + String arg = mArgs[mNextArg]; + mNextArg++; + return arg; + } +}
\ No newline at end of file diff --git a/cmds/dumpstate/dumpstate.c b/cmds/dumpstate/dumpstate.c index ccc4fd78c1dd..21bb62eeeced 100644 --- a/cmds/dumpstate/dumpstate.c +++ b/cmds/dumpstate/dumpstate.c @@ -117,6 +117,15 @@ static void dumpstate() { run_command("WIFI NETWORKS", 20, "su", "root", "wpa_cli", "list_networks", NULL); + property_get("dhcp.wlan0.gateway", network, ""); + if (network[0]) + run_command("PING GATEWAY", 10, "su", "root", "ping", "-c", "3", "-i", ".5", network, NULL); + property_get("dhcp.wlan0.dns1", network, ""); + if (network[0]) + run_command("PING DNS1", 10, "su", "root", "ping", "-c", "3", "-i", ".5", network, NULL); + property_get("dhcp.wlan0.dns2", network, ""); + if (network[0]) + run_command("PING DNS2", 10, "su", "root", "ping", "-c", "3", "-i", ".5", network, NULL); #ifdef FWDUMP_bcm4329 run_command("DUMP WIFI STATUS", 20, "su", "root", "dhdutil", "-i", "wlan0", "dump", NULL); @@ -124,12 +133,14 @@ static void dumpstate() { "su", "root", "wlutil", "counters", NULL); #endif - char ril_dumpstate_timeout[PROPERTY_VALUE_MAX] = {0}; +#ifdef BROKEN_VRIL_IS_FIXED_B_4442803 + char ril_dumpstate_timeout[PROPERTY_VALUE_MAX] = {0}; property_get("ril.dumpstate.timeout", ril_dumpstate_timeout, "30"); if (strlen(ril_dumpstate_timeout) > 0) { run_command("DUMP VENDOR RIL LOGS", atoi(ril_dumpstate_timeout), "su", "root", "vril-dump", NULL); } +#endif print_properties(); diff --git a/cmds/installd/Android.mk b/cmds/installd/Android.mk index 8641c3090a6c..d7a9ef67df05 100644 --- a/cmds/installd/Android.mk +++ b/cmds/installd/Android.mk @@ -1,13 +1,34 @@ ifneq ($(TARGET_SIMULATOR),true) LOCAL_PATH := $(call my-dir) + +common_src_files := \ + commands.c utils.c + +# +# Static library used in testing and executable +# + include $(CLEAR_VARS) LOCAL_SRC_FILES := \ - installd.c commands.c utils.c + $(common_src_files) + +LOCAL_MODULE := libinstalld + +LOCAL_MODULE_TAGS := eng tests -#LOCAL_C_INCLUDES := \ -# $(call include-path-for, system-core)/cutils +include $(BUILD_STATIC_LIBRARY) + +# +# Executable +# + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + installd.c \ + $(common_src_files) LOCAL_SHARED_LIBRARIES := \ libcutils diff --git a/cmds/installd/commands.c b/cmds/installd/commands.c index ada712de547d..d45ac1925a24 100644 --- a/cmds/installd/commands.c +++ b/cmds/installd/commands.c @@ -17,6 +17,13 @@ #include "installd.h" #include <diskusage/dirsize.h> +/* Directory records that are used in execution of commands. */ +dir_rec_t android_data_dir; +dir_rec_t android_asec_dir; +dir_rec_t android_app_dir; +dir_rec_t android_app_private_dir; +dir_rec_array_t android_system_dirs; + int install(const char *pkgname, uid_t uid, gid_t gid) { char pkgdir[PKG_PATH_MAX]; @@ -27,15 +34,25 @@ int install(const char *pkgname, uid_t uid, gid_t gid) return -1; } - if (create_pkg_path(pkgdir, PKG_DIR_PREFIX, pkgname, PKG_DIR_POSTFIX)) + if (create_pkg_path(pkgdir, pkgname, PKG_DIR_POSTFIX, 0)) { + LOGE("cannot create package path\n"); return -1; - if (create_pkg_path(libdir, PKG_LIB_PREFIX, pkgname, PKG_LIB_POSTFIX)) + } + + if (create_pkg_path(libdir, pkgname, PKG_LIB_POSTFIX, 0)) { + LOGE("cannot create package lib path\n"); return -1; + } if (mkdir(pkgdir, 0751) < 0) { LOGE("cannot create dir '%s': %s\n", pkgdir, strerror(errno)); return -errno; } + if (chmod(pkgdir, 0751) < 0) { + LOGE("cannot chmod dir '%s': %s\n", pkgdir, strerror(errno)); + unlink(pkgdir); + return -errno; + } if (chown(pkgdir, uid, gid) < 0) { LOGE("cannot chown dir '%s': %s\n", pkgdir, strerror(errno)); unlink(pkgdir); @@ -46,6 +63,12 @@ int install(const char *pkgname, uid_t uid, gid_t gid) unlink(pkgdir); return -errno; } + if (chmod(libdir, 0755) < 0) { + LOGE("cannot chmod dir '%s': %s\n", libdir, strerror(errno)); + unlink(libdir); + unlink(pkgdir); + return -errno; + } if (chown(libdir, AID_SYSTEM, AID_SYSTEM) < 0) { LOGE("cannot chown dir '%s': %s\n", libdir, strerror(errno)); unlink(libdir); @@ -55,15 +78,15 @@ int install(const char *pkgname, uid_t uid, gid_t gid) return 0; } -int uninstall(const char *pkgname) +int uninstall(const char *pkgname, uid_t persona) { char pkgdir[PKG_PATH_MAX]; - if (create_pkg_path(pkgdir, PKG_DIR_PREFIX, pkgname, PKG_DIR_POSTFIX)) + if (create_pkg_path(pkgdir, pkgname, PKG_DIR_POSTFIX, persona)) return -1; - /* delete contents AND directory, no exceptions */ - return delete_dir_contents(pkgdir, 1, 0); + /* delete contents AND directory, no exceptions */ + return delete_dir_contents(pkgdir, 1, NULL); } int renamepkg(const char *oldpkgname, const char *newpkgname) @@ -71,9 +94,9 @@ int renamepkg(const char *oldpkgname, const char *newpkgname) char oldpkgdir[PKG_PATH_MAX]; char newpkgdir[PKG_PATH_MAX]; - if (create_pkg_path(oldpkgdir, PKG_DIR_PREFIX, oldpkgname, PKG_DIR_POSTFIX)) + if (create_pkg_path(oldpkgdir, oldpkgname, PKG_DIR_POSTFIX, 0)) return -1; - if (create_pkg_path(newpkgdir, PKG_DIR_PREFIX, newpkgname, PKG_DIR_POSTFIX)) + if (create_pkg_path(newpkgdir, newpkgname, PKG_DIR_POSTFIX, 0)) return -1; if (rename(oldpkgdir, newpkgdir) < 0) { @@ -83,22 +106,53 @@ int renamepkg(const char *oldpkgname, const char *newpkgname) return 0; } -int delete_user_data(const char *pkgname) +int delete_user_data(const char *pkgname, uid_t persona) { char pkgdir[PKG_PATH_MAX]; - if (create_pkg_path(pkgdir, PKG_DIR_PREFIX, pkgname, PKG_DIR_POSTFIX)) + if (create_pkg_path(pkgdir, pkgname, PKG_DIR_POSTFIX, persona)) return -1; - /* delete contents, excluding "lib", but not the directory itself */ + /* delete contents, excluding "lib", but not the directory itself */ return delete_dir_contents(pkgdir, 0, "lib"); } +int make_user_data(const char *pkgname, uid_t uid, uid_t persona) +{ + char pkgdir[PKG_PATH_MAX]; + char real_libdir[PKG_PATH_MAX]; + + // Create the data dir for the package + if (create_pkg_path(pkgdir, pkgname, PKG_DIR_POSTFIX, persona)) { + return -1; + } + if (mkdir(pkgdir, 0751) < 0) { + LOGE("cannot create dir '%s': %s\n", pkgdir, strerror(errno)); + return -errno; + } + if (chown(pkgdir, uid, uid) < 0) { + LOGE("cannot chown dir '%s': %s\n", pkgdir, strerror(errno)); + unlink(pkgdir); + return -errno; + } + return 0; +} + +int delete_persona(uid_t persona) +{ + char pkgdir[PKG_PATH_MAX]; + + if (create_persona_path(pkgdir, persona)) + return -1; + + return delete_dir_contents(pkgdir, 1, NULL); +} + int delete_cache(const char *pkgname) { char cachedir[PKG_PATH_MAX]; - if (create_pkg_path(cachedir, CACHE_DIR_PREFIX, pkgname, CACHE_DIR_POSTFIX)) + if (create_pkg_path(cachedir, pkgname, CACHE_DIR_POSTFIX, 0)) return -1; /* delete contents, not the directory, no exceptions */ @@ -108,10 +162,10 @@ int delete_cache(const char *pkgname) static int64_t disk_free() { struct statfs sfs; - if (statfs(PKG_DIR_PREFIX, &sfs) == 0) { + if (statfs(android_data_dir.path, &sfs) == 0) { return sfs.f_bavail * sfs.f_bsize; } else { - LOGE("Couldn't statfs " PKG_DIR_PREFIX ": %s\n", strerror(errno)); + LOGE("Couldn't statfs %s: %s\n", android_data_dir.path, strerror(errno)); return -1; } } @@ -137,9 +191,9 @@ int free_cache(int64_t free_size) LOGI("free_cache(%" PRId64 ") avail %" PRId64 "\n", free_size, avail); if (avail >= free_size) return 0; - d = opendir(PKG_DIR_PREFIX); + d = opendir(android_data_dir.path); if (d == NULL) { - LOGE("cannot open %s: %s\n", PKG_DIR_PREFIX, strerror(errno)); + LOGE("cannot open %s: %s\n", android_data_dir.path, strerror(errno)); return -1; } dfd = dirfd(d); @@ -172,43 +226,13 @@ int free_cache(int64_t free_size) return -1; } -/* used by move_dex, rm_dex, etc to ensure that the provided paths - * don't point anywhere other than at the APK_DIR_PREFIX - */ -static int is_valid_apk_path(const char *path) -{ - int len = strlen(APK_DIR_PREFIX); -int nosubdircheck = 0; - if (strncmp(path, APK_DIR_PREFIX, len)) { - len = strlen(PROTECTED_DIR_PREFIX); - if (strncmp(path, PROTECTED_DIR_PREFIX, len)) { - len = strlen(SDCARD_DIR_PREFIX); - if (strncmp(path, SDCARD_DIR_PREFIX, len)) { - LOGE("invalid apk path '%s' (bad prefix)\n", path); - return 0; - } else { - nosubdircheck = 1; - } - } - } - if ((nosubdircheck != 1) && strchr(path + len, '/')) { - LOGE("invalid apk path '%s' (subdir?)\n", path); - return 0; - } - if (path[len] == '.') { - LOGE("invalid apk path '%s' (trickery)\n", path); - return 0; - } - return 1; -} - int move_dex(const char *src, const char *dst) { char src_dex[PKG_PATH_MAX]; char dst_dex[PKG_PATH_MAX]; - if (!is_valid_apk_path(src)) return -1; - if (!is_valid_apk_path(dst)) return -1; + if (validate_apk_path(src)) return -1; + if (validate_apk_path(dst)) return -1; if (create_cache_path(src_dex, src)) return -1; if (create_cache_path(dst_dex, dst)) return -1; @@ -226,7 +250,7 @@ int rm_dex(const char *path) { char dex_path[PKG_PATH_MAX]; - if (!is_valid_apk_path(path)) return -1; + if (validate_apk_path(path)) return -1; if (create_cache_path(dex_path, path)) return -1; LOGV("unlink %s\n", dex_path); @@ -245,7 +269,7 @@ int protect(char *pkgname, gid_t gid) if (gid < AID_SYSTEM) return -1; - if (create_pkg_path(pkgpath, PROTECTED_DIR_PREFIX, pkgname, ".apk")) + if (create_pkg_path_in_dir(pkgpath, &android_app_private_dir, pkgname, ".apk")) return -1; if (stat(pkgpath, &s) < 0) return -1; @@ -280,8 +304,8 @@ int get_size(const char *pkgname, const char *apkpath, /* count the source apk as code -- but only if it's not * on the /system partition and its not on the sdcard. */ - if (strncmp(apkpath, "/system", 7) != 0 && - strncmp(apkpath, SDCARD_DIR_PREFIX, 7) != 0) { + if (validate_system_app_path(apkpath) && + strncmp(apkpath, android_asec_dir.path, android_asec_dir.len) != 0) { if (stat(apkpath, &s) == 0) { codesize += stat_size(&s); } @@ -300,7 +324,7 @@ int get_size(const char *pkgname, const char *apkpath, } } - if (create_pkg_path(path, PKG_DIR_PREFIX, pkgname, PKG_DIR_POSTFIX)) { + if (create_pkg_path(path, pkgname, PKG_DIR_POSTFIX, 0)) { goto done; } @@ -310,10 +334,10 @@ int get_size(const char *pkgname, const char *apkpath, } dfd = dirfd(d); - /* most stuff in the pkgdir is data, except for the "cache" - * directory and below, which is cache, and the "lib" directory - * and below, which is code... - */ + /* most stuff in the pkgdir is data, except for the "cache" + * directory and below, which is cache, and the "lib" directory + * and below, which is code... + */ while ((de = readdir(d))) { const char *name = de->d_name; @@ -544,15 +568,15 @@ fail: } int create_move_path(char path[PKG_PATH_MAX], - const char* prefix, const char* pkgname, - const char* leaf) + const char* leaf, + uid_t persona) { - if ((strlen(prefix) + strlen(pkgname) + strlen(leaf) + 1) >= PKG_PATH_MAX) { + if ((android_data_dir.len + strlen(pkgname) + strlen(leaf) + 1) >= PKG_PATH_MAX) { return -1; } - sprintf(path, "%s%s/%s", prefix, pkgname, leaf); + sprintf(path, "%s%s%s/%s", android_data_dir.path, PRIMARY_USER_PREFIX, pkgname, leaf); return 0; } @@ -720,8 +744,8 @@ int movefiles() // Skip -- source package no longer exists. } else { LOGV("Move file: %s (from %s to %s)\n", buf+bufp, srcpkg, dstpkg); - if (!create_move_path(srcpath, PKG_DIR_PREFIX, srcpkg, buf+bufp) && - !create_move_path(dstpath, PKG_DIR_PREFIX, dstpkg, buf+bufp)) { + if (!create_move_path(srcpath, srcpkg, buf+bufp, 0) && + !create_move_path(dstpath, dstpkg, buf+bufp, 0)) { movefileordir(srcpath, dstpath, strlen(dstpath)-strlen(buf+bufp), dstuid, dstgid, &s); @@ -750,8 +774,7 @@ int movefiles() UPDATE_COMMANDS_DIR_PREFIX, name, div); } if (srcpkg[0] != 0) { - if (!create_pkg_path(srcpath, PKG_DIR_PREFIX, srcpkg, - PKG_DIR_POSTFIX)) { + if (!create_pkg_path(srcpath, srcpkg, PKG_DIR_POSTFIX, 0)) { if (lstat(srcpath, &s) < 0) { // Package no longer exists -- skip. srcpkg[0] = 0; @@ -762,8 +785,7 @@ int movefiles() div, UPDATE_COMMANDS_DIR_PREFIX, name); } if (srcpkg[0] != 0) { - if (!create_pkg_path(dstpath, PKG_DIR_PREFIX, dstpkg, - PKG_DIR_POSTFIX)) { + if (!create_pkg_path(dstpath, dstpkg, PKG_DIR_POSTFIX, 0)) { if (lstat(dstpath, &s) == 0) { dstuid = s.st_uid; dstgid = s.st_gid; diff --git a/cmds/installd/installd.c b/cmds/installd/installd.c index d2b2f7f9fc60..c062d36ec93b 100644 --- a/cmds/installd/installd.c +++ b/cmds/installd/installd.c @@ -21,7 +21,6 @@ #define TOKEN_MAX 8 /* max number of arguments in buffer */ #define REPLY_MAX 256 /* largest reply allowed */ - static int do_ping(char **arg, char reply[REPLY_MAX]) { return 0; @@ -50,7 +49,7 @@ static int do_rm_dex(char **arg, char reply[REPLY_MAX]) static int do_remove(char **arg, char reply[REPLY_MAX]) { - return uninstall(arg[0]); /* pkgname */ + return uninstall(arg[0], atoi(arg[1])); /* pkgname, userid */ } static int do_rename(char **arg, char reply[REPLY_MAX]) @@ -93,7 +92,17 @@ static int do_get_size(char **arg, char reply[REPLY_MAX]) static int do_rm_user_data(char **arg, char reply[REPLY_MAX]) { - return delete_user_data(arg[0]); /* pkgname */ + return delete_user_data(arg[0], atoi(arg[1])); /* pkgname, userid */ +} + +static int do_mk_user_data(char **arg, char reply[REPLY_MAX]) +{ + return make_user_data(arg[0], atoi(arg[1]), atoi(arg[2])); /* pkgname, uid, userid */ +} + +static int do_rm_user(char **arg, char reply[REPLY_MAX]) +{ + return delete_persona(atoi(arg[0])); /* userid */ } static int do_movefiles(char **arg, char reply[REPLY_MAX]) @@ -123,16 +132,18 @@ struct cmdinfo cmds[] = { { "dexopt", 3, do_dexopt }, { "movedex", 2, do_move_dex }, { "rmdex", 1, do_rm_dex }, - { "remove", 1, do_remove }, + { "remove", 2, do_remove }, { "rename", 2, do_rename }, { "freecache", 1, do_free_cache }, { "rmcache", 1, do_rm_cache }, { "protect", 2, do_protect }, { "getsize", 3, do_get_size }, - { "rmuserdata", 1, do_rm_user_data }, + { "rmuserdata", 2, do_rm_user_data }, { "movefiles", 0, do_movefiles }, { "linklib", 2, do_linklib }, { "unlinklib", 1, do_unlinklib }, + { "mkuserdata", 3, do_mk_user_data }, + { "rmuser", 1, do_rm_user }, }; static int readx(int s, void *_buf, int count) @@ -235,12 +246,118 @@ done: return 0; } -int main(const int argc, const char *argv[]) { +/** + * Initialize all the global variables that are used elsewhere. Returns 0 upon + * success and -1 on error. + */ +void free_globals() { + size_t i; + + for (i = 0; i < android_system_dirs.count; i++) { + if (android_system_dirs.dirs[i].path != NULL) { + free(android_system_dirs.dirs[i].path); + } + } + + free(android_system_dirs.dirs); +} + +int initialize_globals() { + // Get the android data directory. + if (get_path_from_env(&android_data_dir, "ANDROID_DATA") < 0) { + return -1; + } + + // Get the android app directory. + if (copy_and_append(&android_app_dir, &android_data_dir, APP_SUBDIR) < 0) { + return -1; + } + + // Get the android protected app directory. + if (copy_and_append(&android_app_private_dir, &android_data_dir, PRIVATE_APP_SUBDIR) < 0) { + return -1; + } + + // Get the sd-card ASEC mount point. + if (get_path_from_env(&android_asec_dir, "ASEC_MOUNTPOINT") < 0) { + return -1; + } + + // Take note of the system and vendor directories. + android_system_dirs.count = 2; + + android_system_dirs.dirs = calloc(android_system_dirs.count, sizeof(dir_rec_t)); + if (android_system_dirs.dirs == NULL) { + LOGE("Couldn't allocate array for dirs; aborting\n"); + return -1; + } + + // system + if (get_path_from_env(&android_system_dirs.dirs[0], "ANDROID_ROOT") < 0) { + free_globals(); + return -1; + } + + // append "app/" to dirs[0] + char *system_app_path = build_string2(android_system_dirs.dirs[0].path, APP_SUBDIR); + android_system_dirs.dirs[0].path = system_app_path; + android_system_dirs.dirs[0].len = strlen(system_app_path); + + // vendor + // TODO replace this with an environment variable (doesn't exist yet) + android_system_dirs.dirs[1].path = "/vendor/app/"; + android_system_dirs.dirs[1].len = strlen(android_system_dirs.dirs[1].path); + + return 0; +} + +int initialize_directories() { + // /data/user + char *user_data_dir = build_string2(android_data_dir.path, SECONDARY_USER_PREFIX); + // /data/data + char *legacy_data_dir = build_string2(android_data_dir.path, PRIMARY_USER_PREFIX); + // /data/user/0 + char *primary_data_dir = build_string3(android_data_dir.path, SECONDARY_USER_PREFIX, + "0"); + int ret = -1; + if (user_data_dir != NULL && primary_data_dir != NULL && legacy_data_dir != NULL) { + ret = 0; + // Make the /data/user directory if necessary + if (access(user_data_dir, R_OK) < 0) { + if (mkdir(user_data_dir, 0755) < 0) { + return -1; + } + if (chown(user_data_dir, AID_SYSTEM, AID_SYSTEM) < 0) { + return -1; + } + } + // Make the /data/user/0 symlink to /data/data if necessary + if (access(primary_data_dir, R_OK) < 0) { + ret = symlink(legacy_data_dir, primary_data_dir); + } + free(user_data_dir); + free(legacy_data_dir); + free(primary_data_dir); + } + return ret; +} + +int main(const int argc, const char *argv[]) { char buf[BUFFER_MAX]; struct sockaddr addr; socklen_t alen; int lsocket, s, count; + if (initialize_globals() < 0) { + LOGE("Could not initialize globals; exiting.\n"); + exit(1); + } + + if (initialize_directories() < 0) { + LOGE("Could not create directories; exiting.\n"); + exit(1); + } + lsocket = android_get_control_socket(SOCKET_PATH); if (lsocket < 0) { LOGE("Failed to get socket from environment: %s\n", strerror(errno)); diff --git a/cmds/installd/installd.h b/cmds/installd/installd.h index 77b58ec10692..e5f6739d9081 100644 --- a/cmds/installd/installd.h +++ b/cmds/installd/installd.h @@ -49,37 +49,63 @@ /* elements combined with a valid package name to form paths */ -#define PKG_DIR_PREFIX "/data/data/" +#define PRIMARY_USER_PREFIX "data/" +#define SECONDARY_USER_PREFIX "user/" + #define PKG_DIR_POSTFIX "" -#define PKG_LIB_PREFIX "/data/data/" #define PKG_LIB_POSTFIX "/lib" -#define CACHE_DIR_PREFIX "/data/data/" #define CACHE_DIR_POSTFIX "/cache" -#define APK_DIR_PREFIX "/data/app/" +#define APP_SUBDIR "app/" // sub-directory under ANDROID_DATA /* other handy constants */ -#define PROTECTED_DIR_PREFIX "/data/app-private/" -#define SDCARD_DIR_PREFIX getenv("ASEC_MOUNTPOINT") +#define PRIVATE_APP_SUBDIR "app-private/" // sub-directory under ANDROID_DATA -#define DALVIK_CACHE_PREFIX "/data/dalvik-cache/" -#define DALVIK_CACHE_POSTFIX "/classes.dex" +#define DALVIK_CACHE_PREFIX "/data/dalvik-cache/" +#define DALVIK_CACHE_POSTFIX "/classes.dex" #define UPDATE_COMMANDS_DIR_PREFIX "/system/etc/updatecmds/" #define PKG_NAME_MAX 128 /* largest allowed package name */ #define PKG_PATH_MAX 256 /* max size of any path we use */ +/* data structures */ + +typedef struct { + char* path; + size_t len; +} dir_rec_t; + +typedef struct { + size_t count; + dir_rec_t* dirs; +} dir_rec_array_t; + +extern dir_rec_t android_app_dir; +extern dir_rec_t android_app_private_dir; +extern dir_rec_t android_data_dir; +extern dir_rec_t android_asec_dir; +extern dir_rec_array_t android_system_dirs; /* util.c */ +int create_pkg_path_in_dir(char path[PKG_PATH_MAX], + const dir_rec_t* dir, + const char* pkgname, + const char* postfix); + int create_pkg_path(char path[PKG_PATH_MAX], - const char *prefix, const char *pkgname, - const char *postfix); + const char *postfix, + uid_t persona); + +int create_persona_path(char path[PKG_PATH_MAX], + uid_t persona); + +int is_valid_package_name(const char* pkgname); int create_cache_path(char path[PKG_PATH_MAX], const char *src); @@ -89,12 +115,29 @@ int delete_dir_contents(const char *pathname, int delete_dir_contents_fd(int dfd, const char *name); +int validate_system_app_path(const char* path); + +int get_path_from_env(dir_rec_t* rec, const char* var); + +int get_path_from_string(dir_rec_t* rec, const char* path); + +int copy_and_append(dir_rec_t* dst, const dir_rec_t* src, const char* suffix); + +int validate_apk_path(const char *path); + +int append_and_increment(char** dst, const char* src, size_t* dst_size); + +char *build_string2(char *s1, char *s2); +char *build_string3(char *s1, char *s2, char *s3); + /* commands.c */ int install(const char *pkgname, uid_t uid, gid_t gid); -int uninstall(const char *pkgname); +int uninstall(const char *pkgname, uid_t persona); int renamepkg(const char *oldpkgname, const char *newpkgname); -int delete_user_data(const char *pkgname); +int delete_user_data(const char *pkgname, uid_t persona); +int make_user_data(const char *pkgname, uid_t uid, uid_t persona); +int delete_persona(uid_t persona); int delete_cache(const char *pkgname); int move_dex(const char *src, const char *dst); int rm_dex(const char *path); diff --git a/cmds/installd/tests/Android.mk b/cmds/installd/tests/Android.mk new file mode 100644 index 000000000000..e53378d9f724 --- /dev/null +++ b/cmds/installd/tests/Android.mk @@ -0,0 +1,42 @@ +# Build the unit tests for installd +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +ifneq ($(TARGET_SIMULATOR),true) + +# Build the unit tests. +test_src_files := \ + installd_utils_test.cpp + +shared_libraries := \ + libutils \ + libcutils \ + libstlport + +static_libraries := \ + libinstalld \ + libdiskusage \ + libgtest \ + libgtest_main + +c_includes := \ + frameworks/base/cmds/installd \ + bionic \ + bionic/libstdc++/include \ + external/gtest/include \ + external/stlport/stlport + +module_tags := eng tests + +$(foreach file,$(test_src_files), \ + $(eval include $(CLEAR_VARS)) \ + $(eval LOCAL_SHARED_LIBRARIES := $(shared_libraries)) \ + $(eval LOCAL_STATIC_LIBRARIES := $(static_libraries)) \ + $(eval LOCAL_SRC_FILES := $(file)) \ + $(eval LOCAL_C_INCLUDES := $(c_includes)) \ + $(eval LOCAL_MODULE := $(notdir $(file:%.cpp=%))) \ + $(eval LOCAL_MODULE_TAGS := $(module_tags)) \ + $(eval include $(BUILD_EXECUTABLE)) \ +) + +endif diff --git a/cmds/installd/tests/installd_utils_test.cpp b/cmds/installd/tests/installd_utils_test.cpp new file mode 100644 index 000000000000..1128fceca0af --- /dev/null +++ b/cmds/installd/tests/installd_utils_test.cpp @@ -0,0 +1,383 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdlib.h> +#include <string.h> + +#define LOG_TAG "utils_test" +#include <utils/Log.h> + +#include <gtest/gtest.h> + +extern "C" { +#include "installd.h" +} + +#define TEST_DATA_DIR "/data/" +#define TEST_APP_DIR "/data/app/" +#define TEST_APP_PRIVATE_DIR "/data/app-private/" +#define TEST_ASEC_DIR "/mnt/asec/" + +#define TEST_SYSTEM_DIR1 "/system/app/" +#define TEST_SYSTEM_DIR2 "/vendor/app/" + +namespace android { + +class UtilsTest : public testing::Test { +protected: + virtual void SetUp() { + android_app_dir.path = TEST_APP_DIR; + android_app_dir.len = strlen(TEST_APP_DIR); + + android_app_private_dir.path = TEST_APP_PRIVATE_DIR; + android_app_private_dir.len = strlen(TEST_APP_PRIVATE_DIR); + + android_data_dir.path = TEST_DATA_DIR; + android_data_dir.len = strlen(TEST_DATA_DIR); + + android_asec_dir.path = TEST_ASEC_DIR; + android_asec_dir.len = strlen(TEST_ASEC_DIR); + + android_system_dirs.count = 2; + + android_system_dirs.dirs = (dir_rec_t*) calloc(android_system_dirs.count, sizeof(dir_rec_t)); + android_system_dirs.dirs[0].path = TEST_SYSTEM_DIR1; + android_system_dirs.dirs[0].len = strlen(TEST_SYSTEM_DIR1); + + android_system_dirs.dirs[1].path = TEST_SYSTEM_DIR2; + android_system_dirs.dirs[1].len = strlen(TEST_SYSTEM_DIR2); + } + + virtual void TearDown() { + free(android_system_dirs.dirs); + } +}; + +TEST_F(UtilsTest, IsValidApkPath_BadPrefix) { + // Bad prefixes directories + const char *badprefix1 = "/etc/passwd"; + EXPECT_EQ(-1, validate_apk_path(badprefix1)) + << badprefix1 << " should be allowed as a valid path"; + + const char *badprefix2 = "../.." TEST_APP_DIR "../../../blah"; + EXPECT_EQ(-1, validate_apk_path(badprefix2)) + << badprefix2 << " should be allowed as a valid path"; + + const char *badprefix3 = "init.rc"; + EXPECT_EQ(-1, validate_apk_path(badprefix3)) + << badprefix3 << " should be allowed as a valid path"; + + const char *badprefix4 = "/init.rc"; + EXPECT_EQ(-1, validate_apk_path(badprefix4)) + << badprefix4 << " should be allowed as a valid path"; +} + +TEST_F(UtilsTest, IsValidApkPath_Internal) { + // Internal directories + const char *internal1 = TEST_APP_DIR "example.apk"; + EXPECT_EQ(0, validate_apk_path(internal1)) + << internal1 << " should be allowed as a valid path"; + + const char *badint1 = TEST_APP_DIR "../example.apk"; + EXPECT_EQ(-1, validate_apk_path(badint1)) + << badint1 << " should be rejected as a invalid path"; + + const char *badint2 = TEST_APP_DIR "/../example.apk"; + EXPECT_EQ(-1, validate_apk_path(badint2)) + << badint2 << " should be rejected as a invalid path"; + + const char *badint3 = TEST_APP_DIR "example.com/pkg.apk"; + EXPECT_EQ(-1, validate_apk_path(badint3)) + << badint3 << " should be rejected as a invalid path"; +} + +TEST_F(UtilsTest, IsValidApkPath_Private) { + // Internal directories + const char *private1 = TEST_APP_PRIVATE_DIR "example.apk"; + EXPECT_EQ(0, validate_apk_path(private1)) + << private1 << " should be allowed as a valid path"; + + const char *badpriv1 = TEST_APP_PRIVATE_DIR "../example.apk"; + EXPECT_EQ(-1, validate_apk_path(badpriv1)) + << badpriv1 << " should be rejected as a invalid path"; + + const char *badpriv2 = TEST_APP_PRIVATE_DIR "/../example.apk"; + EXPECT_EQ(-1, validate_apk_path(badpriv2)) + << badpriv2 << " should be rejected as a invalid path"; + + const char *badpriv3 = TEST_APP_PRIVATE_DIR "example.com/pkg.apk"; + EXPECT_EQ(-1, validate_apk_path(badpriv3)) + << badpriv3 << " should be rejected as a invalid path"; +} + + +TEST_F(UtilsTest, IsValidApkPath_AsecGood1) { + const char *asec1 = TEST_ASEC_DIR "example.apk"; + EXPECT_EQ(0, validate_apk_path(asec1)) + << asec1 << " should be allowed as a valid path"; +} + +TEST_F(UtilsTest, IsValidApkPath_AsecGood2) { + const char *asec2 = TEST_ASEC_DIR "com.example.asec/pkg.apk"; + EXPECT_EQ(0, validate_apk_path(asec2)) + << asec2 << " should be allowed as a valid path"; +} + +TEST_F(UtilsTest, IsValidApkPath_EscapeFail) { + const char *badasec1 = TEST_ASEC_DIR "../example.apk"; + EXPECT_EQ(-1, validate_apk_path(badasec1)) + << badasec1 << " should be rejected as a invalid path"; +} + +TEST_F(UtilsTest, IsValidApkPath_DoubleSlashFail) { + const char *badasec2 = TEST_ASEC_DIR "com.example.asec//pkg.apk"; + EXPECT_EQ(-1, validate_apk_path(badasec2)) + << badasec2 << " should be rejected as a invalid path"; +} + +TEST_F(UtilsTest, IsValidApkPath_SubdirEscapeFail) { + const char *badasec3 = TEST_ASEC_DIR "com.example.asec/../../../pkg.apk"; + EXPECT_EQ(-1, validate_apk_path(badasec3)) + << badasec3 << " should be rejected as a invalid path"; +} + +TEST_F(UtilsTest, IsValidApkPath_SlashEscapeFail) { + const char *badasec4 = TEST_ASEC_DIR "/../example.apk"; + EXPECT_EQ(-1, validate_apk_path(badasec4)) + << badasec4 << " should be rejected as a invalid path"; +} + +TEST_F(UtilsTest, IsValidApkPath_CrazyDirFail) { + const char *badasec5 = TEST_ASEC_DIR ".//../.."; + EXPECT_EQ(-1, validate_apk_path(badasec5)) + << badasec5 << " should be rejected as a invalid path"; +} + +TEST_F(UtilsTest, IsValidApkPath_SubdirEscapeSingleFail) { + const char *badasec6 = TEST_ASEC_DIR "com.example.asec/../pkg.apk"; + EXPECT_EQ(-1, validate_apk_path(badasec6)) + << badasec6 << " should be rejected as a invalid path"; +} + +TEST_F(UtilsTest, IsValidApkPath_TwoSubdirFail) { + const char *badasec7 = TEST_ASEC_DIR "com.example.asec/subdir1/pkg.apk"; + EXPECT_EQ(-1, validate_apk_path(badasec7)) + << badasec7 << " should be rejected as a invalid path"; +} + +TEST_F(UtilsTest, CheckSystemApp_Dir1) { + const char *sysapp1 = TEST_SYSTEM_DIR1 "Voice.apk"; + EXPECT_EQ(0, validate_system_app_path(sysapp1)) + << sysapp1 << " should be allowed as a system path"; +} + +TEST_F(UtilsTest, CheckSystemApp_Dir2) { + const char *sysapp2 = TEST_SYSTEM_DIR2 "com.example.myapp.apk"; + EXPECT_EQ(0, validate_system_app_path(sysapp2)) + << sysapp2 << " should be allowed as a system path"; +} + +TEST_F(UtilsTest, CheckSystemApp_EscapeFail) { + const char *badapp1 = TEST_SYSTEM_DIR1 "../com.example.apk"; + EXPECT_EQ(-1, validate_system_app_path(badapp1)) + << badapp1 << " should be rejected not a system path"; +} + +TEST_F(UtilsTest, CheckSystemApp_DoubleEscapeFail) { + const char *badapp2 = TEST_SYSTEM_DIR2 "/../../com.example.apk"; + EXPECT_EQ(-1, validate_system_app_path(badapp2)) + << badapp2 << " should be rejected not a system path"; +} + +TEST_F(UtilsTest, CheckSystemApp_BadPathEscapeFail) { + const char *badapp3 = TEST_APP_DIR "/../../com.example.apk"; + EXPECT_EQ(-1, validate_system_app_path(badapp3)) + << badapp3 << " should be rejected not a system path"; +} + +TEST_F(UtilsTest, GetPathFromString_NullPathFail) { + dir_rec_t test1; + EXPECT_EQ(-1, get_path_from_string(&test1, NULL)) + << "Should not allow NULL as a path."; +} + +TEST_F(UtilsTest, GetPathFromString_EmptyPathFail) { + dir_rec_t test1; + EXPECT_EQ(-1, get_path_from_string(&test1, "")) + << "Should not allow empty paths."; +} + +TEST_F(UtilsTest, GetPathFromString_RelativePathFail) { + dir_rec_t test1; + EXPECT_EQ(-1, get_path_from_string(&test1, "mnt/asec")) + << "Should not allow relative paths."; +} + +TEST_F(UtilsTest, GetPathFromString_NonCanonical) { + dir_rec_t test1; + + EXPECT_EQ(0, get_path_from_string(&test1, "/mnt/asec")) + << "Should be able to canonicalize directory /mnt/asec"; + EXPECT_STREQ("/mnt/asec/", test1.path) + << "/mnt/asec should be canonicalized to /mnt/asec/"; + EXPECT_EQ(10, (ssize_t) test1.len) + << "path len should be equal to the length of /mnt/asec/ (10)"; + free(test1.path); +} + +TEST_F(UtilsTest, GetPathFromString_CanonicalPath) { + dir_rec_t test3; + EXPECT_EQ(0, get_path_from_string(&test3, "/data/app/")) + << "Should be able to canonicalize directory /data/app/"; + EXPECT_STREQ("/data/app/", test3.path) + << "/data/app/ should be canonicalized to /data/app/"; + EXPECT_EQ(10, (ssize_t) test3.len) + << "path len should be equal to the length of /data/app/ (10)"; + free(test3.path); +} + +TEST_F(UtilsTest, CreatePkgPath_LongPkgNameSuccess) { + char path[PKG_PATH_MAX]; + + // Create long packagename of "aaaaa..." + size_t pkgnameSize = PKG_NAME_MAX; + char pkgname[pkgnameSize + 1]; + memset(pkgname, 'a', pkgnameSize); + pkgname[pkgnameSize] = '\0'; + + EXPECT_EQ(0, create_pkg_path(path, pkgname, "", 0)) + << "Should successfully be able to create package name."; + + const char *prefix = TEST_DATA_DIR PRIMARY_USER_PREFIX; + size_t offset = strlen(prefix); + EXPECT_STREQ(pkgname, path + offset) + << "Package path should be a really long string of a's"; +} + +TEST_F(UtilsTest, CreatePkgPath_LongPkgNameFail) { + char path[PKG_PATH_MAX]; + + // Create long packagename of "aaaaa..." + size_t pkgnameSize = PKG_NAME_MAX + 1; + char pkgname[pkgnameSize + 1]; + memset(pkgname, 'a', pkgnameSize); + pkgname[pkgnameSize] = '\0'; + + EXPECT_EQ(-1, create_pkg_path(path, pkgname, "", 0)) + << "Should return error because package name is too long."; +} + +TEST_F(UtilsTest, CreatePkgPath_LongPostfixFail) { + char path[PKG_PATH_MAX]; + + // Create long packagename of "aaaaa..." + size_t postfixSize = PKG_PATH_MAX; + char postfix[postfixSize + 1]; + memset(postfix, 'a', postfixSize); + postfix[postfixSize] = '\0'; + + EXPECT_EQ(-1, create_pkg_path(path, "com.example.package", postfix, 0)) + << "Should return error because postfix is too long."; +} + +TEST_F(UtilsTest, CreatePkgPath_PrimaryUser) { + char path[PKG_PATH_MAX]; + + EXPECT_EQ(0, create_pkg_path(path, "com.example.package", "", 0)) + << "Should return error because postfix is too long."; + + EXPECT_STREQ(TEST_DATA_DIR PRIMARY_USER_PREFIX "com.example.package", path) + << "Package path should be in /data/data/"; +} + +TEST_F(UtilsTest, CreatePkgPath_SecondaryUser) { + char path[PKG_PATH_MAX]; + + EXPECT_EQ(0, create_pkg_path(path, "com.example.package", "", 1)) + << "Should successfully create package path."; + + EXPECT_STREQ(TEST_DATA_DIR SECONDARY_USER_PREFIX "1/com.example.package", path) + << "Package path should be in /data/user/"; +} + +TEST_F(UtilsTest, CreatePkgPathInDir_ProtectedDir) { + char path[PKG_PATH_MAX]; + + dir_rec_t dir; + dir.path = "/data/app-private/"; + dir.len = strlen(dir.path); + + EXPECT_EQ(0, create_pkg_path_in_dir(path, &dir, "com.example.package", ".apk")) + << "Should successfully create package path."; + + EXPECT_STREQ("/data/app-private/com.example.package.apk", path) + << "Package path should be in /data/app-private/"; +} + +TEST_F(UtilsTest, CopyAndAppend_Normal) { + //int copy_and_append(dir_rec_t* dst, dir_rec_t* src, char* suffix) + dir_rec_t dst; + dir_rec_t src; + + src.path = "/data/"; + src.len = strlen(src.path); + + EXPECT_EQ(0, copy_and_append(&dst, &src, "app/")) + << "Should return error because postfix is too long."; + + EXPECT_STREQ("/data/app/", dst.path) + << "Appended path should be correct"; + + EXPECT_EQ(10, (ssize_t) dst.len) + << "Appended path should be length of '/data/app/' (10)"; +} + +TEST_F(UtilsTest, AppendAndIncrement_Normal) { + size_t dst_size = 10; + char dst[dst_size]; + char *dstp = dst; + const char* src = "FOO"; + + EXPECT_EQ(0, append_and_increment(&dstp, src, &dst_size)) + << "String should append successfully"; + + EXPECT_STREQ("FOO", dst) + << "String should append correctly"; + + EXPECT_EQ(0, append_and_increment(&dstp, src, &dst_size)) + << "String should append successfully again"; + + EXPECT_STREQ("FOOFOO", dst) + << "String should append correctly again"; +} + +TEST_F(UtilsTest, AppendAndIncrement_TooBig) { + size_t dst_size = 5; + char dst[dst_size]; + char *dstp = dst; + const char* src = "FOO"; + + EXPECT_EQ(0, append_and_increment(&dstp, src, &dst_size)) + << "String should append successfully"; + + EXPECT_STREQ("FOO", dst) + << "String should append correctly"; + + EXPECT_EQ(-1, append_and_increment(&dstp, src, &dst_size)) + << "String should fail because it's too large to fit"; +} + +} diff --git a/cmds/installd/utils.c b/cmds/installd/utils.c index a5e4b5a1557d..3099b8341f6e 100644 --- a/cmds/installd/utils.c +++ b/cmds/installd/utils.c @@ -16,24 +16,133 @@ #include "installd.h" +int create_pkg_path_in_dir(char path[PKG_PATH_MAX], + const dir_rec_t* dir, + const char* pkgname, + const char* postfix) +{ + const size_t postfix_len = strlen(postfix); + + const size_t pkgname_len = strlen(pkgname); + if (pkgname_len > PKG_NAME_MAX) { + return -1; + } + + if (is_valid_package_name(pkgname) < 0) { + return -1; + } + + if ((pkgname_len + dir->len + postfix_len) >= PKG_PATH_MAX) { + return -1; + } + + char *dst = path; + size_t dst_size = PKG_PATH_MAX; + + if (append_and_increment(&dst, dir->path, &dst_size) < 0 + || append_and_increment(&dst, pkgname, &dst_size) < 0 + || append_and_increment(&dst, postfix, &dst_size) < 0) { + LOGE("Error building APK path"); + return -1; + } + + return 0; +} + +/** + * Create the package path name for a given package name with a postfix for + * a certain persona. Returns 0 on success, and -1 on failure. + */ int create_pkg_path(char path[PKG_PATH_MAX], - const char *prefix, const char *pkgname, - const char *postfix) + const char *postfix, + uid_t persona) { - int len; - const char *x; + size_t uid_len; + char* persona_prefix; + if (persona == 0) { + persona_prefix = PRIMARY_USER_PREFIX; + uid_len = 0; + } else { + persona_prefix = SECONDARY_USER_PREFIX; + uid_len = snprintf(NULL, 0, "%d", persona); + } + + const size_t prefix_len = android_data_dir.len + strlen(persona_prefix) + uid_len + 1 /*slash*/; + char prefix[prefix_len + 1]; + + char *dst = prefix; + size_t dst_size = sizeof(prefix); - len = strlen(pkgname); - if (len > PKG_NAME_MAX) { + if (append_and_increment(&dst, android_data_dir.path, &dst_size) < 0 + || append_and_increment(&dst, persona_prefix, &dst_size) < 0) { + LOGE("Error building prefix for APK path"); return -1; } - if ((len + strlen(prefix) + strlen(postfix)) >= PKG_PATH_MAX) { + + if (persona != 0) { + int ret = snprintf(dst, dst_size, "%d/", persona); + if (ret < 0 || (size_t) ret != uid_len + 1) { + LOGW("Error appending UID to APK path"); + return -1; + } + } + + dir_rec_t dir; + dir.path = prefix; + dir.len = prefix_len; + + return create_pkg_path_in_dir(path, &dir, pkgname, postfix); +} + +/** + * Create the path name for user data for a certain persona. + * Returns 0 on success, and -1 on failure. + */ +int create_persona_path(char path[PKG_PATH_MAX], + uid_t persona) +{ + size_t uid_len; + char* persona_prefix; + if (persona == 0) { + persona_prefix = PRIMARY_USER_PREFIX; + uid_len = 0; + } else { + persona_prefix = SECONDARY_USER_PREFIX; + uid_len = snprintf(NULL, 0, "%d", persona); + } + + char *dst = path; + size_t dst_size = PKG_PATH_MAX; + + if (append_and_increment(&dst, android_data_dir.path, &dst_size) < 0 + || append_and_increment(&dst, persona_prefix, &dst_size) < 0) { + LOGE("Error building prefix for user path"); return -1; } - x = pkgname; + if (persona != 0) { + if (dst_size < uid_len + 1) { + LOGE("Error building user path"); + return -1; + } + int ret = snprintf(dst, dst_size, "%d", persona); + if (ret < 0 || (size_t) ret != uid_len) { + LOGE("Error appending persona id to path"); + return -1; + } + } + return 0; +} + +/** + * Checks whether the package name is valid. Returns -1 on error and + * 0 on success. + */ +int is_valid_package_name(const char* pkgname) { + const char *x = pkgname; int alpha = -1; + while (*x) { if (isalnum(*x) || (*x == '_')) { /* alphanumeric or underscore are fine */ @@ -47,13 +156,15 @@ int create_pkg_path(char path[PKG_PATH_MAX], /* Suffix -X is fine to let versioning of packages. But whatever follows should be alphanumeric.*/ alpha = 1; - }else { + } else { /* anything not A-Z, a-z, 0-9, _, or . is invalid */ LOGE("invalid package name '%s'\n", pkgname); return -1; } + x++; } + if (alpha == 1) { // Skip current character x++; @@ -66,7 +177,6 @@ int create_pkg_path(char path[PKG_PATH_MAX], } } - sprintf(path, "%s%s%s", prefix, pkgname, postfix); return 0; } @@ -171,3 +281,202 @@ int delete_dir_contents_fd(int dfd, const char *name) closedir(d); return res; } + +/** + * Checks whether a path points to a system app (.apk file). Returns 0 + * if it is a system app or -1 if it is not. + */ +int validate_system_app_path(const char* path) { + size_t i; + + for (i = 0; i < android_system_dirs.count; i++) { + const size_t dir_len = android_system_dirs.dirs[i].len; + if (!strncmp(path, android_system_dirs.dirs[i].path, dir_len)) { + if (path[dir_len] == '.' || strchr(path + dir_len, '/') != NULL) { + LOGE("invalid system apk path '%s' (trickery)\n", path); + return -1; + } + return 0; + } + } + + return -1; +} + +/** + * Get the contents of a environment variable that contains a path. Caller + * owns the string that is inserted into the directory record. Returns + * 0 on success and -1 on error. + */ +int get_path_from_env(dir_rec_t* rec, const char* var) { + const char* path = getenv(var); + int ret = get_path_from_string(rec, path); + if (ret < 0) { + LOGW("Problem finding value for environment variable %s\n", var); + } + return ret; +} + +/** + * Puts the string into the record as a directory. Appends '/' to the end + * of all paths. Caller owns the string that is inserted into the directory + * record. A null value will result in an error. + * + * Returns 0 on success and -1 on error. + */ +int get_path_from_string(dir_rec_t* rec, const char* path) { + if (path == NULL) { + return -1; + } else { + const size_t path_len = strlen(path); + if (path_len <= 0) { + return -1; + } + + // Make sure path is absolute. + if (path[0] != '/') { + return -1; + } + + if (path[path_len - 1] == '/') { + // Path ends with a forward slash. Make our own copy. + + rec->path = strdup(path); + if (rec->path == NULL) { + return -1; + } + + rec->len = path_len; + } else { + // Path does not end with a slash. Generate a new string. + char *dst; + + // Add space for slash and terminating null. + size_t dst_size = path_len + 2; + + rec->path = malloc(dst_size); + if (rec->path == NULL) { + return -1; + } + + dst = rec->path; + + if (append_and_increment(&dst, path, &dst_size) < 0 + || append_and_increment(&dst, "/", &dst_size)) { + LOGE("Error canonicalizing path"); + return -1; + } + + rec->len = dst - rec->path; + } + } + return 0; +} + +int copy_and_append(dir_rec_t* dst, const dir_rec_t* src, const char* suffix) { + dst->len = src->len + strlen(suffix); + const size_t dstSize = dst->len + 1; + dst->path = (char*) malloc(dstSize); + + if (dst->path == NULL + || snprintf(dst->path, dstSize, "%s%s", src->path, suffix) + != (ssize_t) dst->len) { + LOGE("Could not allocate memory to hold appended path; aborting\n"); + return -1; + } + + return 0; +} + +/** + * Check whether path points to a valid path for an APK file. An ASEC + * directory is allowed to have one level of subdirectory names. Returns -1 + * when an invalid path is encountered and 0 when a valid path is encountered. + */ +int validate_apk_path(const char *path) +{ + int allowsubdir = 0; + char *subdir = NULL; + size_t dir_len; + size_t path_len; + + if (!strncmp(path, android_app_dir.path, android_app_dir.len)) { + dir_len = android_app_dir.len; + } else if (!strncmp(path, android_app_private_dir.path, android_app_private_dir.len)) { + dir_len = android_app_private_dir.len; + } else if (!strncmp(path, android_asec_dir.path, android_asec_dir.len)) { + dir_len = android_asec_dir.len; + allowsubdir = 1; + } else { + LOGE("invalid apk path '%s' (bad prefix)\n", path); + return -1; + } + + path_len = strlen(path); + + /* + * Only allow the path to have a subdirectory if it's been marked as being allowed. + */ + if ((subdir = strchr(path + dir_len, '/')) != NULL) { + ++subdir; + if (!allowsubdir + || (path_len > (size_t) (subdir - path) && (strchr(subdir, '/') != NULL))) { + LOGE("invalid apk path '%s' (subdir?)\n", path); + return -1; + } + } + + /* + * Directories can't have a period directly after the directory markers + * to prevent ".." + */ + if (path[dir_len] == '.' + || (subdir != NULL && ((*subdir == '.') || (strchr(subdir, '/') != NULL)))) { + LOGE("invalid apk path '%s' (trickery)\n", path); + return -1; + } + + return 0; +} + +int append_and_increment(char** dst, const char* src, size_t* dst_size) { + ssize_t ret = strlcpy(*dst, src, *dst_size); + if (ret < 0 || (size_t) ret >= *dst_size) { + return -1; + } + *dst += ret; + *dst_size -= ret; + return 0; +} + +char *build_string2(char *s1, char *s2) { + if (s1 == NULL || s2 == NULL) return NULL; + + int len_s1 = strlen(s1); + int len_s2 = strlen(s2); + int len = len_s1 + len_s2 + 1; + char *result = malloc(len); + if (result == NULL) return NULL; + + strcpy(result, s1); + strcpy(result + len_s1, s2); + + return result; +} + +char *build_string3(char *s1, char *s2, char *s3) { + if (s1 == NULL || s2 == NULL || s3 == NULL) return NULL; + + int len_s1 = strlen(s1); + int len_s2 = strlen(s2); + int len_s3 = strlen(s3); + int len = len_s1 + len_s2 + len_s3 + 1; + char *result = malloc(len); + if (result == NULL) return NULL; + + strcpy(result, s1); + strcpy(result + len_s1, s2); + strcpy(result + len_s1 + len_s2, s3); + + return result; +} diff --git a/cmds/keystore/Android.mk b/cmds/keystore/Android.mk index 15a199f30c46..67dd9f8679ea 100644 --- a/cmds/keystore/Android.mk +++ b/cmds/keystore/Android.mk @@ -19,14 +19,14 @@ ifneq ($(TARGET_SIMULATOR),true) LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) -LOCAL_SRC_FILES := keystore.c +LOCAL_SRC_FILES := keystore.cpp LOCAL_C_INCLUDES := external/openssl/include LOCAL_SHARED_LIBRARIES := libcutils libcrypto LOCAL_MODULE:= keystore include $(BUILD_EXECUTABLE) include $(CLEAR_VARS) -LOCAL_SRC_FILES := keystore_cli.c +LOCAL_SRC_FILES := keystore_cli.cpp LOCAL_C_INCLUDES := external/openssl/include LOCAL_SHARED_LIBRARIES := libcutils libcrypto LOCAL_MODULE:= keystore_cli diff --git a/cmds/keystore/keystore.c b/cmds/keystore/keystore.c deleted file mode 100644 index afa64f8c9d12..000000000000 --- a/cmds/keystore/keystore.c +++ /dev/null @@ -1,589 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <stdio.h> -#include <stdint.h> -#include <string.h> -#include <unistd.h> -#include <signal.h> -#include <errno.h> -#include <dirent.h> -#include <fcntl.h> -#include <limits.h> -#include <sys/types.h> -#include <sys/socket.h> -#include <sys/stat.h> -#include <sys/time.h> -#include <arpa/inet.h> - -#include <openssl/aes.h> -#include <openssl/evp.h> -#include <openssl/md5.h> - -#define LOG_TAG "keystore" -#include <cutils/log.h> -#include <cutils/sockets.h> -#include <private/android_filesystem_config.h> - -#include "keystore.h" - -/* KeyStore is a secured storage for key-value pairs. In this implementation, - * each file stores one key-value pair. Keys are encoded in file names, and - * values are encrypted with checksums. The encryption key is protected by a - * user-defined password. To keep things simple, buffers are always larger than - * the maximum space we needed, so boundary checks on buffers are omitted. */ - -#define KEY_SIZE ((NAME_MAX - 15) / 2) -#define VALUE_SIZE 32768 -#define PASSWORD_SIZE VALUE_SIZE - -/* Here is the encoding of keys. This is necessary in order to allow arbitrary - * characters in keys. Characters in [0-~] are not encoded. Others are encoded - * into two bytes. The first byte is one of [+-.] which represents the first - * two bits of the character. The second byte encodes the rest of the bits into - * [0-o]. Therefore in the worst case the length of a key gets doubled. Note - * that Base64 cannot be used here due to the need of prefix match on keys. */ - -static int encode_key(char *out, uint8_t *in, int length) -{ - int i; - for (i = length; i > 0; --i, ++in, ++out) { - if (*in >= '0' && *in <= '~') { - *out = *in; - } else { - *out = '+' + (*in >> 6); - *++out = '0' + (*in & 0x3F); - ++length; - } - } - *out = 0; - return length; -} - -static int decode_key(uint8_t *out, char *in, int length) -{ - int i; - for (i = 0; i < length; ++i, ++in, ++out) { - if (*in >= '0' && *in <= '~') { - *out = *in; - } else { - *out = (*in - '+') << 6; - *out |= (*++in - '0') & 0x3F; - --length; - } - } - *out = 0; - return length; -} - -/* Here is the protocol used in both requests and responses: - * code [length_1 message_1 ... length_n message_n] end-of-file - * where code is one byte long and lengths are unsigned 16-bit integers in - * network order. Thus the maximum length of a message is 65535 bytes. */ - -static int the_socket = -1; - -static int recv_code(int8_t *code) -{ - return recv(the_socket, code, 1, 0) == 1; -} - -static int recv_message(uint8_t *message, int length) -{ - uint8_t bytes[2]; - if (recv(the_socket, &bytes[0], 1, 0) != 1 || - recv(the_socket, &bytes[1], 1, 0) != 1) { - return -1; - } else { - int offset = bytes[0] << 8 | bytes[1]; - if (length < offset) { - return -1; - } - length = offset; - offset = 0; - while (offset < length) { - int n = recv(the_socket, &message[offset], length - offset, 0); - if (n <= 0) { - return -1; - } - offset += n; - } - } - return length; -} - -static int recv_end_of_file() -{ - uint8_t byte; - return recv(the_socket, &byte, 1, 0) == 0; -} - -static void send_code(int8_t code) -{ - send(the_socket, &code, 1, 0); -} - -static void send_message(uint8_t *message, int length) -{ - uint16_t bytes = htons(length); - send(the_socket, &bytes, 2, 0); - send(the_socket, message, length, 0); -} - -/* Here is the file format. There are two parts in blob.value, the secret and - * the description. The secret is stored in ciphertext, and its original size - * can be found in blob.length. The description is stored after the secret in - * plaintext, and its size is specified in blob.info. The total size of the two - * parts must be no more than VALUE_SIZE bytes. The first three bytes of the - * file are reserved for future use and are always set to zero. Fields other - * than blob.info, blob.length, and blob.value are modified by encrypt_blob() - * and decrypt_blob(). Thus they should not be accessed from outside. */ - -static int the_entropy = -1; - -static struct __attribute__((packed)) { - uint8_t reserved[3]; - uint8_t info; - uint8_t vector[AES_BLOCK_SIZE]; - uint8_t encrypted[0]; - uint8_t digest[MD5_DIGEST_LENGTH]; - uint8_t digested[0]; - int32_t length; - uint8_t value[VALUE_SIZE + AES_BLOCK_SIZE]; -} blob; - -static int8_t encrypt_blob(char *name, AES_KEY *aes_key) -{ - uint8_t vector[AES_BLOCK_SIZE]; - int length; - int fd; - - if (read(the_entropy, blob.vector, AES_BLOCK_SIZE) != AES_BLOCK_SIZE) { - return SYSTEM_ERROR; - } - - length = blob.length + (blob.value - blob.encrypted); - length = (length + AES_BLOCK_SIZE - 1) / AES_BLOCK_SIZE * AES_BLOCK_SIZE; - - if (blob.info != 0) { - memmove(&blob.encrypted[length], &blob.value[blob.length], blob.info); - } - - blob.length = htonl(blob.length); - MD5(blob.digested, length - (blob.digested - blob.encrypted), blob.digest); - - memcpy(vector, blob.vector, AES_BLOCK_SIZE); - AES_cbc_encrypt(blob.encrypted, blob.encrypted, length, aes_key, vector, - AES_ENCRYPT); - - memset(blob.reserved, 0, sizeof(blob.reserved)); - length += (blob.encrypted - (uint8_t *)&blob) + blob.info; - - fd = open(".tmp", O_WRONLY | O_TRUNC | O_CREAT, S_IRUSR | S_IWUSR); - length -= write(fd, &blob, length); - close(fd); - return (length || rename(".tmp", name)) ? SYSTEM_ERROR : NO_ERROR; -} - -static int8_t decrypt_blob(char *name, AES_KEY *aes_key) -{ - int fd = open(name, O_RDONLY); - int length; - - if (fd == -1) { - return (errno == ENOENT) ? KEY_NOT_FOUND : SYSTEM_ERROR; - } - length = read(fd, &blob, sizeof(blob)); - close(fd); - - length -= (blob.encrypted - (uint8_t *)&blob) + blob.info; - if (length < blob.value - blob.encrypted || length % AES_BLOCK_SIZE != 0) { - return VALUE_CORRUPTED; - } - - AES_cbc_encrypt(blob.encrypted, blob.encrypted, length, aes_key, - blob.vector, AES_DECRYPT); - length -= blob.digested - blob.encrypted; - if (memcmp(blob.digest, MD5(blob.digested, length, NULL), - MD5_DIGEST_LENGTH)) { - return VALUE_CORRUPTED; - } - - length -= blob.value - blob.digested; - blob.length = ntohl(blob.length); - if (blob.length < 0 || blob.length > length) { - return VALUE_CORRUPTED; - } - if (blob.info != 0) { - memmove(&blob.value[blob.length], &blob.value[length], blob.info); - } - return NO_ERROR; -} - -/* Here are the actions. Each of them is a function without arguments. All - * information is defined in global variables, which are set properly before - * performing an action. The number of parameters required by each action is - * fixed and defined in a table. If the return value of an action is positive, - * it will be treated as a response code and transmitted to the client. Note - * that the lengths of parameters are checked when they are received, so - * boundary checks on parameters are omitted. */ - -#define MAX_PARAM 2 -#define MAX_RETRY 4 - -static uid_t uid = -1; -static int8_t state = UNINITIALIZED; -static int8_t retry = MAX_RETRY; - -static struct { - int length; - uint8_t value[VALUE_SIZE]; -} params[MAX_PARAM]; - -static AES_KEY encryption_key; -static AES_KEY decryption_key; - -static int8_t test() -{ - return state; -} - -static int8_t get() -{ - char name[NAME_MAX]; - int n = sprintf(name, "%u_", uid); - encode_key(&name[n], params[0].value, params[0].length); - n = decrypt_blob(name, &decryption_key); - if (n != NO_ERROR) { - return n; - } - send_code(NO_ERROR); - send_message(blob.value, blob.length); - return -NO_ERROR; -} - -static int8_t insert() -{ - char name[NAME_MAX]; - int n = sprintf(name, "%u_", uid); - encode_key(&name[n], params[0].value, params[0].length); - blob.info = 0; - blob.length = params[1].length; - memcpy(blob.value, params[1].value, params[1].length); - return encrypt_blob(name, &encryption_key); -} - -static int8_t delete() -{ - char name[NAME_MAX]; - int n = sprintf(name, "%u_", uid); - encode_key(&name[n], params[0].value, params[0].length); - return (unlink(name) && errno != ENOENT) ? SYSTEM_ERROR : NO_ERROR; -} - -static int8_t exist() -{ - char name[NAME_MAX]; - int n = sprintf(name, "%u_", uid); - encode_key(&name[n], params[0].value, params[0].length); - if (access(name, R_OK) == -1) { - return (errno != ENOENT) ? SYSTEM_ERROR : KEY_NOT_FOUND; - } - return NO_ERROR; -} - -static int8_t saw() -{ - DIR *dir = opendir("."); - struct dirent *file; - char name[NAME_MAX]; - int n; - - if (!dir) { - return SYSTEM_ERROR; - } - n = sprintf(name, "%u_", uid); - n += encode_key(&name[n], params[0].value, params[0].length); - send_code(NO_ERROR); - while ((file = readdir(dir)) != NULL) { - if (!strncmp(name, file->d_name, n)) { - char *p = &file->d_name[n]; - params[0].length = decode_key(params[0].value, p, strlen(p)); - send_message(params[0].value, params[0].length); - } - } - closedir(dir); - return -NO_ERROR; -} - -static int8_t reset() -{ - DIR *dir = opendir("."); - struct dirent *file; - - memset(&encryption_key, 0, sizeof(encryption_key)); - memset(&decryption_key, 0, sizeof(decryption_key)); - state = UNINITIALIZED; - retry = MAX_RETRY; - - if (!dir) { - return SYSTEM_ERROR; - } - while ((file = readdir(dir)) != NULL) { - unlink(file->d_name); - } - closedir(dir); - return NO_ERROR; -} - -#define MASTER_KEY_FILE ".masterkey" -#define MASTER_KEY_SIZE 16 -#define SALT_SIZE 16 - -static void set_key(uint8_t *key, uint8_t *password, int length, uint8_t *salt) -{ - if (salt) { - PKCS5_PBKDF2_HMAC_SHA1((char *)password, length, salt, SALT_SIZE, - 8192, MASTER_KEY_SIZE, key); - } else { - PKCS5_PBKDF2_HMAC_SHA1((char *)password, length, (uint8_t *)"keystore", - sizeof("keystore"), 1024, MASTER_KEY_SIZE, key); - } -} - -/* Here is the history. To improve the security, the parameters to generate the - * master key has been changed. To make a seamless transition, we update the - * file using the same password when the user unlock it for the first time. If - * any thing goes wrong during the transition, the new file will not overwrite - * the old one. This avoids permanent damages of the existing data. */ - -static int8_t password() -{ - uint8_t key[MASTER_KEY_SIZE]; - AES_KEY aes_key; - int8_t response = SYSTEM_ERROR; - - if (state == UNINITIALIZED) { - if (read(the_entropy, blob.value, MASTER_KEY_SIZE) != MASTER_KEY_SIZE) { - return SYSTEM_ERROR; - } - } else { - int fd = open(MASTER_KEY_FILE, O_RDONLY); - uint8_t *salt = NULL; - if (fd != -1) { - int length = read(fd, &blob, sizeof(blob)); - close(fd); - if (length > SALT_SIZE && blob.info == SALT_SIZE) { - salt = (uint8_t *)&blob + length - SALT_SIZE; - } - } - - set_key(key, params[0].value, params[0].length, salt); - AES_set_decrypt_key(key, MASTER_KEY_SIZE * 8, &aes_key); - response = decrypt_blob(MASTER_KEY_FILE, &aes_key); - if (response == SYSTEM_ERROR) { - return SYSTEM_ERROR; - } - if (response != NO_ERROR || blob.length != MASTER_KEY_SIZE) { - if (retry <= 0) { - reset(); - return UNINITIALIZED; - } - return WRONG_PASSWORD + --retry; - } - - if (!salt && params[1].length == -1) { - params[1] = params[0]; - } - } - - if (params[1].length == -1) { - memcpy(key, blob.value, MASTER_KEY_SIZE); - } else { - uint8_t *salt = &blob.value[MASTER_KEY_SIZE]; - if (read(the_entropy, salt, SALT_SIZE) != SALT_SIZE) { - return SYSTEM_ERROR; - } - - set_key(key, params[1].value, params[1].length, salt); - AES_set_encrypt_key(key, MASTER_KEY_SIZE * 8, &aes_key); - memcpy(key, blob.value, MASTER_KEY_SIZE); - blob.info = SALT_SIZE; - blob.length = MASTER_KEY_SIZE; - response = encrypt_blob(MASTER_KEY_FILE, &aes_key); - } - - if (response == NO_ERROR) { - AES_set_encrypt_key(key, MASTER_KEY_SIZE * 8, &encryption_key); - AES_set_decrypt_key(key, MASTER_KEY_SIZE * 8, &decryption_key); - state = NO_ERROR; - retry = MAX_RETRY; - } - return response; -} - -static int8_t lock() -{ - memset(&encryption_key, 0, sizeof(encryption_key)); - memset(&decryption_key, 0, sizeof(decryption_key)); - state = LOCKED; - return NO_ERROR; -} - -static int8_t unlock() -{ - params[1].length = -1; - return password(); -} - -/* Here are the permissions, actions, users, and the main function. */ - -enum perm { - TEST = 1, - GET = 2, - INSERT = 4, - DELETE = 8, - EXIST = 16, - SAW = 32, - RESET = 64, - PASSWORD = 128, - LOCK = 256, - UNLOCK = 512, -}; - -static struct action { - int8_t (*run)(); - int8_t code; - int8_t state; - uint32_t perm; - int lengths[MAX_PARAM]; -} actions[] = { - {test, 't', 0, TEST, {0}}, - {get, 'g', NO_ERROR, GET, {KEY_SIZE}}, - {insert, 'i', NO_ERROR, INSERT, {KEY_SIZE, VALUE_SIZE}}, - {delete, 'd', 0, DELETE, {KEY_SIZE}}, - {exist, 'e', 0, EXIST, {KEY_SIZE}}, - {saw, 's', 0, SAW, {KEY_SIZE}}, - {reset, 'r', 0, RESET, {0}}, - {password, 'p', 0, PASSWORD, {PASSWORD_SIZE, PASSWORD_SIZE}}, - {lock, 'l', NO_ERROR, LOCK, {0}}, - {unlock, 'u', LOCKED, UNLOCK, {PASSWORD_SIZE}}, - {NULL, 0 , 0, 0, {0}}, -}; - -static struct user { - uid_t uid; - uid_t euid; - uint32_t perms; -} users[] = { - {AID_SYSTEM, ~0, ~GET}, - {AID_VPN, AID_SYSTEM, GET}, - {AID_WIFI, AID_SYSTEM, GET}, - {AID_ROOT, AID_SYSTEM, GET}, - {~0, ~0, TEST | GET | INSERT | DELETE | EXIST | SAW}, -}; - -static int8_t process(int8_t code) { - struct user *user = users; - struct action *action = actions; - int i; - - while (~user->uid && user->uid != uid) { - ++user; - } - while (action->code && action->code != code) { - ++action; - } - if (!action->code) { - return UNDEFINED_ACTION; - } - if (!(action->perm & user->perms)) { - return PERMISSION_DENIED; - } - if (action->state && action->state != state) { - return state; - } - if (~user->euid) { - uid = user->euid; - } - for (i = 0; i < MAX_PARAM && action->lengths[i]; ++i) { - params[i].length = recv_message(params[i].value, action->lengths[i]); - if (params[i].length == -1) { - return PROTOCOL_ERROR; - } - } - if (!recv_end_of_file()) { - return PROTOCOL_ERROR; - } - return action->run(); -} - -#define RANDOM_DEVICE "/dev/urandom" - -int main(int argc, char **argv) -{ - int control_socket = android_get_control_socket("keystore"); - if (argc < 2) { - LOGE("A directory must be specified!"); - return 1; - } - if (chdir(argv[1]) == -1) { - LOGE("chdir: %s: %s", argv[1], strerror(errno)); - return 1; - } - if ((the_entropy = open(RANDOM_DEVICE, O_RDONLY)) == -1) { - LOGE("open: %s: %s", RANDOM_DEVICE, strerror(errno)); - return 1; - } - if (listen(control_socket, 3) == -1) { - LOGE("listen: %s", strerror(errno)); - return 1; - } - - signal(SIGPIPE, SIG_IGN); - if (access(MASTER_KEY_FILE, R_OK) == 0) { - state = LOCKED; - } - - while ((the_socket = accept(control_socket, NULL, 0)) != -1) { - struct timeval tv = {.tv_sec = 3}; - struct ucred cred; - socklen_t size = sizeof(cred); - int8_t request; - - setsockopt(the_socket, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); - setsockopt(the_socket, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); - - if (getsockopt(the_socket, SOL_SOCKET, SO_PEERCRED, &cred, &size)) { - LOGW("getsockopt: %s", strerror(errno)); - } else if (recv_code(&request)) { - int8_t old_state = state; - int8_t response; - uid = cred.uid; - - if ((response = process(request)) > 0) { - send_code(response); - response = -response; - } - - LOGI("uid: %d action: %c -> %d state: %d -> %d retry: %d", - cred.uid, request, -response, old_state, state, retry); - } - close(the_socket); - } - LOGE("accept: %s", strerror(errno)); - return 1; -} diff --git a/cmds/keystore/keystore.cpp b/cmds/keystore/keystore.cpp new file mode 100644 index 000000000000..b48be6ef3885 --- /dev/null +++ b/cmds/keystore/keystore.cpp @@ -0,0 +1,810 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdio.h> +#include <stdint.h> +#include <string.h> +#include <unistd.h> +#include <signal.h> +#include <errno.h> +#include <dirent.h> +#include <fcntl.h> +#include <limits.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <arpa/inet.h> + +#include <openssl/aes.h> +#include <openssl/evp.h> +#include <openssl/md5.h> + +#define LOG_TAG "keystore" +#include <cutils/log.h> +#include <cutils/sockets.h> +#include <private/android_filesystem_config.h> + +#include "keystore.h" + +/* KeyStore is a secured storage for key-value pairs. In this implementation, + * each file stores one key-value pair. Keys are encoded in file names, and + * values are encrypted with checksums. The encryption key is protected by a + * user-defined password. To keep things simple, buffers are always larger than + * the maximum space we needed, so boundary checks on buffers are omitted. */ + +#define KEY_SIZE ((NAME_MAX - 15) / 2) +#define VALUE_SIZE 32768 +#define PASSWORD_SIZE VALUE_SIZE + +struct Value { + int length; + uint8_t value[VALUE_SIZE]; +}; + +/* Here is the encoding of keys. This is necessary in order to allow arbitrary + * characters in keys. Characters in [0-~] are not encoded. Others are encoded + * into two bytes. The first byte is one of [+-.] which represents the first + * two bits of the character. The second byte encodes the rest of the bits into + * [0-o]. Therefore in the worst case the length of a key gets doubled. Note + * that Base64 cannot be used here due to the need of prefix match on keys. */ + +static int encode_key(char* out, uid_t uid, const Value* key) { + int n = snprintf(out, NAME_MAX, "%u_", uid); + out += n; + const uint8_t* in = key->value; + int length = key->length; + for (int i = length; i > 0; --i, ++in, ++out) { + if (*in >= '0' && *in <= '~') { + *out = *in; + } else { + *out = '+' + (*in >> 6); + *++out = '0' + (*in & 0x3F); + ++length; + } + } + *out = '\0'; + return n + length; +} + +static int decode_key(uint8_t* out, char* in, int length) { + for (int i = 0; i < length; ++i, ++in, ++out) { + if (*in >= '0' && *in <= '~') { + *out = *in; + } else { + *out = (*in - '+') << 6; + *out |= (*++in - '0') & 0x3F; + --length; + } + } + *out = '\0'; + return length; +} + +static size_t readFully(int fd, uint8_t* data, size_t size) { + size_t remaining = size; + while (remaining > 0) { + ssize_t n = TEMP_FAILURE_RETRY(read(fd, data, size)); + if (n == -1 || n == 0) { + return size-remaining; + } + data += n; + remaining -= n; + } + return size; +} + +static size_t writeFully(int fd, uint8_t* data, size_t size) { + size_t remaining = size; + while (remaining > 0) { + ssize_t n = TEMP_FAILURE_RETRY(write(fd, data, size)); + if (n == -1 || n == 0) { + return size-remaining; + } + data += n; + remaining -= n; + } + return size; +} + +class Entropy { +public: + Entropy() : mRandom(-1) {} + ~Entropy() { + if (mRandom != -1) { + close(mRandom); + } + } + + bool open() { + const char* randomDevice = "/dev/urandom"; + mRandom = ::open(randomDevice, O_RDONLY); + if (mRandom == -1) { + LOGE("open: %s: %s", randomDevice, strerror(errno)); + return false; + } + return true; + } + + bool generate_random_data(uint8_t* data, size_t size) { + return (readFully(mRandom, data, size) == size); + } + +private: + int mRandom; +}; + +/* Here is the file format. There are two parts in blob.value, the secret and + * the description. The secret is stored in ciphertext, and its original size + * can be found in blob.length. The description is stored after the secret in + * plaintext, and its size is specified in blob.info. The total size of the two + * parts must be no more than VALUE_SIZE bytes. The first three bytes of the + * file are reserved for future use and are always set to zero. Fields other + * than blob.info, blob.length, and blob.value are modified by encryptBlob() + * and decryptBlob(). Thus they should not be accessed from outside. */ + +struct __attribute__((packed)) blob { + uint8_t reserved[3]; + uint8_t info; + uint8_t vector[AES_BLOCK_SIZE]; + uint8_t encrypted[0]; + uint8_t digest[MD5_DIGEST_LENGTH]; + uint8_t digested[0]; + int32_t length; // in network byte order when encrypted + uint8_t value[VALUE_SIZE + AES_BLOCK_SIZE]; +}; + +class Blob { +public: + Blob(uint8_t* value, int32_t valueLength, uint8_t* info, uint8_t infoLength) { + mBlob.length = valueLength; + memcpy(mBlob.value, value, valueLength); + + mBlob.info = infoLength; + memcpy(mBlob.value + valueLength, info, infoLength); + } + + Blob(blob b) { + mBlob = b; + } + + Blob() {} + + uint8_t* getValue() { + return mBlob.value; + } + + int32_t getLength() { + return mBlob.length; + } + + uint8_t getInfo() { + return mBlob.info; + } + + ResponseCode encryptBlob(const char* filename, AES_KEY *aes_key, Entropy* entropy) { + if (!entropy->generate_random_data(mBlob.vector, AES_BLOCK_SIZE)) { + return SYSTEM_ERROR; + } + + // data includes the value and the value's length + size_t dataLength = mBlob.length + sizeof(mBlob.length); + // pad data to the AES_BLOCK_SIZE + size_t digestedLength = ((dataLength + AES_BLOCK_SIZE - 1) + / AES_BLOCK_SIZE * AES_BLOCK_SIZE); + // encrypted data includes the digest value + size_t encryptedLength = digestedLength + MD5_DIGEST_LENGTH; + // move info after space for padding + memmove(&mBlob.encrypted[encryptedLength], &mBlob.value[mBlob.length], mBlob.info); + // zero padding area + memset(mBlob.value + mBlob.length, 0, digestedLength - dataLength); + + mBlob.length = htonl(mBlob.length); + MD5(mBlob.digested, digestedLength, mBlob.digest); + + uint8_t vector[AES_BLOCK_SIZE]; + memcpy(vector, mBlob.vector, AES_BLOCK_SIZE); + AES_cbc_encrypt(mBlob.encrypted, mBlob.encrypted, encryptedLength, + aes_key, vector, AES_ENCRYPT); + + memset(mBlob.reserved, 0, sizeof(mBlob.reserved)); + size_t headerLength = (mBlob.encrypted - (uint8_t*) &mBlob); + size_t fileLength = encryptedLength + headerLength + mBlob.info; + + const char* tmpFileName = ".tmp"; + int out = open(tmpFileName, O_WRONLY | O_TRUNC | O_CREAT, S_IRUSR | S_IWUSR); + if (out == -1) { + return SYSTEM_ERROR; + } + size_t writtenBytes = writeFully(out, (uint8_t*) &mBlob, fileLength); + if (close(out) != 0) { + return SYSTEM_ERROR; + } + if (writtenBytes != fileLength) { + unlink(tmpFileName); + return SYSTEM_ERROR; + } + return (rename(tmpFileName, filename) == 0) ? NO_ERROR : SYSTEM_ERROR; + } + + ResponseCode decryptBlob(const char* filename, AES_KEY *aes_key) { + int in = open(filename, O_RDONLY); + if (in == -1) { + return (errno == ENOENT) ? KEY_NOT_FOUND : SYSTEM_ERROR; + } + // fileLength may be less than sizeof(mBlob) since the in + // memory version has extra padding to tolerate rounding up to + // the AES_BLOCK_SIZE + size_t fileLength = readFully(in, (uint8_t*) &mBlob, sizeof(mBlob)); + if (close(in) != 0) { + return SYSTEM_ERROR; + } + size_t headerLength = (mBlob.encrypted - (uint8_t*) &mBlob); + if (fileLength < headerLength) { + return VALUE_CORRUPTED; + } + + ssize_t encryptedLength = fileLength - (headerLength + mBlob.info); + if (encryptedLength < 0 || encryptedLength % AES_BLOCK_SIZE != 0) { + return VALUE_CORRUPTED; + } + AES_cbc_encrypt(mBlob.encrypted, mBlob.encrypted, encryptedLength, aes_key, + mBlob.vector, AES_DECRYPT); + size_t digestedLength = encryptedLength - MD5_DIGEST_LENGTH; + uint8_t computedDigest[MD5_DIGEST_LENGTH]; + MD5(mBlob.digested, digestedLength, computedDigest); + if (memcmp(mBlob.digest, computedDigest, MD5_DIGEST_LENGTH) != 0) { + return VALUE_CORRUPTED; + } + + ssize_t maxValueLength = digestedLength - sizeof(mBlob.length); + mBlob.length = ntohl(mBlob.length); + if (mBlob.length < 0 || mBlob.length > maxValueLength) { + return VALUE_CORRUPTED; + } + if (mBlob.info != 0) { + // move info from after padding to after data + memmove(&mBlob.value[mBlob.length], &mBlob.value[maxValueLength], mBlob.info); + } + return NO_ERROR; + } + +private: + struct blob mBlob; +}; + +class KeyStore { +public: + KeyStore(Entropy* entropy) : mEntropy(entropy), mRetry(MAX_RETRY) { + if (access(MASTER_KEY_FILE, R_OK) == 0) { + setState(STATE_LOCKED); + } else { + setState(STATE_UNINITIALIZED); + } + } + + State getState() { + return mState; + } + + int8_t getRetry() { + return mRetry; + } + + ResponseCode initialize(Value* pw) { + if (!generateMasterKey()) { + return SYSTEM_ERROR; + } + ResponseCode response = writeMasterKey(pw); + if (response != NO_ERROR) { + return response; + } + setupMasterKeys(); + return NO_ERROR; + } + + ResponseCode writeMasterKey(Value* pw) { + uint8_t passwordKey[MASTER_KEY_SIZE_BYTES]; + generateKeyFromPassword(passwordKey, MASTER_KEY_SIZE_BYTES, pw, mSalt); + AES_KEY passwordAesKey; + AES_set_encrypt_key(passwordKey, MASTER_KEY_SIZE_BITS, &passwordAesKey); + Blob masterKeyBlob(mMasterKey, sizeof(mMasterKey), mSalt, sizeof(mSalt)); + return masterKeyBlob.encryptBlob(MASTER_KEY_FILE, &passwordAesKey, mEntropy); + } + + ResponseCode readMasterKey(Value* pw) { + int in = open(MASTER_KEY_FILE, O_RDONLY); + if (in == -1) { + return SYSTEM_ERROR; + } + + // we read the raw blob to just to get the salt to generate + // the AES key, then we create the Blob to use with decryptBlob + blob rawBlob; + size_t length = readFully(in, (uint8_t*) &rawBlob, sizeof(rawBlob)); + if (close(in) != 0) { + return SYSTEM_ERROR; + } + // find salt at EOF if present, otherwise we have an old file + uint8_t* salt; + if (length > SALT_SIZE && rawBlob.info == SALT_SIZE) { + salt = (uint8_t*) &rawBlob + length - SALT_SIZE; + } else { + salt = NULL; + } + uint8_t passwordKey[MASTER_KEY_SIZE_BYTES]; + generateKeyFromPassword(passwordKey, MASTER_KEY_SIZE_BYTES, pw, salt); + AES_KEY passwordAesKey; + AES_set_decrypt_key(passwordKey, MASTER_KEY_SIZE_BITS, &passwordAesKey); + Blob masterKeyBlob(rawBlob); + ResponseCode response = masterKeyBlob.decryptBlob(MASTER_KEY_FILE, &passwordAesKey); + if (response == SYSTEM_ERROR) { + return SYSTEM_ERROR; + } + if (response == NO_ERROR && masterKeyBlob.getLength() == MASTER_KEY_SIZE_BYTES) { + // if salt was missing, generate one and write a new master key file with the salt. + if (salt == NULL) { + if (!generateSalt()) { + return SYSTEM_ERROR; + } + response = writeMasterKey(pw); + } + if (response == NO_ERROR) { + setupMasterKeys(); + } + return response; + } + if (mRetry <= 0) { + reset(); + return UNINITIALIZED; + } + --mRetry; + switch (mRetry) { + case 0: return WRONG_PASSWORD_0; + case 1: return WRONG_PASSWORD_1; + case 2: return WRONG_PASSWORD_2; + case 3: return WRONG_PASSWORD_3; + default: return WRONG_PASSWORD_3; + } + } + + bool reset() { + clearMasterKeys(); + setState(STATE_UNINITIALIZED); + + DIR* dir = opendir("."); + struct dirent* file; + + if (!dir) { + return false; + } + while ((file = readdir(dir)) != NULL) { + unlink(file->d_name); + } + closedir(dir); + return true; + } + + bool isEmpty() { + DIR* dir = opendir("."); + struct dirent* file; + if (!dir) { + return true; + } + bool result = true; + while ((file = readdir(dir)) != NULL) { + if (isKeyFile(file->d_name)) { + result = false; + break; + } + } + closedir(dir); + return result; + } + + void lock() { + clearMasterKeys(); + setState(STATE_LOCKED); + } + + ResponseCode get(const char* filename, Blob* keyBlob) { + return keyBlob->decryptBlob(filename, &mMasterKeyDecryption); + } + + ResponseCode put(const char* filename, Blob* keyBlob) { + return keyBlob->encryptBlob(filename, &mMasterKeyEncryption, mEntropy); + } + +private: + static const char* MASTER_KEY_FILE; + static const int MASTER_KEY_SIZE_BYTES = 16; + static const int MASTER_KEY_SIZE_BITS = MASTER_KEY_SIZE_BYTES * 8; + + static const int MAX_RETRY = 4; + static const size_t SALT_SIZE = 16; + + Entropy* mEntropy; + + State mState; + int8_t mRetry; + + uint8_t mMasterKey[MASTER_KEY_SIZE_BYTES]; + uint8_t mSalt[SALT_SIZE]; + + AES_KEY mMasterKeyEncryption; + AES_KEY mMasterKeyDecryption; + + void setState(State state) { + mState = state; + if (mState == STATE_NO_ERROR || mState == STATE_UNINITIALIZED) { + mRetry = MAX_RETRY; + } + } + + bool generateSalt() { + return mEntropy->generate_random_data(mSalt, sizeof(mSalt)); + } + + bool generateMasterKey() { + if (!mEntropy->generate_random_data(mMasterKey, sizeof(mMasterKey))) { + return false; + } + if (!generateSalt()) { + return false; + } + return true; + } + + void setupMasterKeys() { + AES_set_encrypt_key(mMasterKey, MASTER_KEY_SIZE_BITS, &mMasterKeyEncryption); + AES_set_decrypt_key(mMasterKey, MASTER_KEY_SIZE_BITS, &mMasterKeyDecryption); + setState(STATE_NO_ERROR); + } + + void clearMasterKeys() { + memset(mMasterKey, 0, sizeof(mMasterKey)); + memset(mSalt, 0, sizeof(mSalt)); + memset(&mMasterKeyEncryption, 0, sizeof(mMasterKeyEncryption)); + memset(&mMasterKeyDecryption, 0, sizeof(mMasterKeyDecryption)); + } + + static void generateKeyFromPassword(uint8_t* key, ssize_t keySize, Value* pw, uint8_t* salt) { + size_t saltSize; + if (salt != NULL) { + saltSize = SALT_SIZE; + } else { + // pre-gingerbread used this hardwired salt, readMasterKey will rewrite these when found + salt = (uint8_t*) "keystore"; + // sizeof = 9, not strlen = 8 + saltSize = sizeof("keystore"); + } + PKCS5_PBKDF2_HMAC_SHA1((char*) pw->value, pw->length, salt, saltSize, 8192, keySize, key); + } + + static bool isKeyFile(const char* filename) { + return ((strcmp(filename, MASTER_KEY_FILE) != 0) + && (strcmp(filename, ".") != 0) + && (strcmp(filename, "..") != 0)); + } +}; + +const char* KeyStore::MASTER_KEY_FILE = ".masterkey"; + +/* Here is the protocol used in both requests and responses: + * code [length_1 message_1 ... length_n message_n] end-of-file + * where code is one byte long and lengths are unsigned 16-bit integers in + * network order. Thus the maximum length of a message is 65535 bytes. */ + +static int recv_code(int sock, int8_t* code) { + return recv(sock, code, 1, 0) == 1; +} + +static int recv_message(int sock, uint8_t* message, int length) { + uint8_t bytes[2]; + if (recv(sock, &bytes[0], 1, 0) != 1 || + recv(sock, &bytes[1], 1, 0) != 1) { + return -1; + } else { + int offset = bytes[0] << 8 | bytes[1]; + if (length < offset) { + return -1; + } + length = offset; + offset = 0; + while (offset < length) { + int n = recv(sock, &message[offset], length - offset, 0); + if (n <= 0) { + return -1; + } + offset += n; + } + } + return length; +} + +static int recv_end_of_file(int sock) { + uint8_t byte; + return recv(sock, &byte, 1, 0) == 0; +} + +static void send_code(int sock, int8_t code) { + send(sock, &code, 1, 0); +} + +static void send_message(int sock, uint8_t* message, int length) { + uint16_t bytes = htons(length); + send(sock, &bytes, 2, 0); + send(sock, message, length, 0); +} + +/* Here are the actions. Each of them is a function without arguments. All + * information is defined in global variables, which are set properly before + * performing an action. The number of parameters required by each action is + * fixed and defined in a table. If the return value of an action is positive, + * it will be treated as a response code and transmitted to the client. Note + * that the lengths of parameters are checked when they are received, so + * boundary checks on parameters are omitted. */ + +static const ResponseCode NO_ERROR_RESPONSE_CODE_SENT = (ResponseCode) 0; + +static ResponseCode test(KeyStore* keyStore, int sock, uid_t uid, Value*, Value*) { + return (ResponseCode) keyStore->getState(); +} + +static ResponseCode get(KeyStore* keyStore, int sock, uid_t uid, Value* keyName, Value*) { + char filename[NAME_MAX]; + encode_key(filename, uid, keyName); + Blob keyBlob; + ResponseCode responseCode = keyStore->get(filename, &keyBlob); + if (responseCode != NO_ERROR) { + return responseCode; + } + send_code(sock, NO_ERROR); + send_message(sock, keyBlob.getValue(), keyBlob.getLength()); + return NO_ERROR_RESPONSE_CODE_SENT; +} + +static ResponseCode insert(KeyStore* keyStore, int sock, uid_t uid, Value* keyName, Value* val) { + char filename[NAME_MAX]; + encode_key(filename, uid, keyName); + Blob keyBlob(val->value, val->length, 0, NULL); + return keyStore->put(filename, &keyBlob); +} + +static ResponseCode del(KeyStore* keyStore, int sock, uid_t uid, Value* keyName, Value*) { + char filename[NAME_MAX]; + encode_key(filename, uid, keyName); + return (unlink(filename) && errno != ENOENT) ? SYSTEM_ERROR : NO_ERROR; +} + +static ResponseCode exist(KeyStore* keyStore, int sock, uid_t uid, Value* keyName, Value*) { + char filename[NAME_MAX]; + encode_key(filename, uid, keyName); + if (access(filename, R_OK) == -1) { + return (errno != ENOENT) ? SYSTEM_ERROR : KEY_NOT_FOUND; + } + return NO_ERROR; +} + +static ResponseCode saw(KeyStore* keyStore, int sock, uid_t uid, Value* keyPrefix, Value*) { + DIR* dir = opendir("."); + if (!dir) { + return SYSTEM_ERROR; + } + char filename[NAME_MAX]; + int n = encode_key(filename, uid, keyPrefix); + send_code(sock, NO_ERROR); + + struct dirent* file; + while ((file = readdir(dir)) != NULL) { + if (!strncmp(filename, file->d_name, n)) { + char* p = &file->d_name[n]; + keyPrefix->length = decode_key(keyPrefix->value, p, strlen(p)); + send_message(sock, keyPrefix->value, keyPrefix->length); + } + } + closedir(dir); + return NO_ERROR_RESPONSE_CODE_SENT; +} + +static ResponseCode reset(KeyStore* keyStore, int sock, uid_t uid, Value*, Value*) { + return keyStore->reset() ? NO_ERROR : SYSTEM_ERROR; +} + +/* Here is the history. To improve the security, the parameters to generate the + * master key has been changed. To make a seamless transition, we update the + * file using the same password when the user unlock it for the first time. If + * any thing goes wrong during the transition, the new file will not overwrite + * the old one. This avoids permanent damages of the existing data. */ + +static ResponseCode password(KeyStore* keyStore, int sock, uid_t uid, Value* pw, Value*) { + switch (keyStore->getState()) { + case STATE_UNINITIALIZED: { + // generate master key, encrypt with password, write to file, initialize mMasterKey*. + return keyStore->initialize(pw); + } + case STATE_NO_ERROR: { + // rewrite master key with new password. + return keyStore->writeMasterKey(pw); + } + case STATE_LOCKED: { + // read master key, decrypt with password, initialize mMasterKey*. + return keyStore->readMasterKey(pw); + } + } + return SYSTEM_ERROR; +} + +static ResponseCode lock(KeyStore* keyStore, int sock, uid_t uid, Value*, Value*) { + keyStore->lock(); + return NO_ERROR; +} + +static ResponseCode unlock(KeyStore* keyStore, int sock, uid_t uid, Value* pw, Value* unused) { + return password(keyStore, sock, uid, pw, unused); +} + +static ResponseCode zero(KeyStore* keyStore, int sock, uid_t uid, Value*, Value*) { + return keyStore->isEmpty() ? KEY_NOT_FOUND : NO_ERROR; +} + +/* Here are the permissions, actions, users, and the main function. */ + +enum perm { + TEST = 1, + GET = 2, + INSERT = 4, + DELETE = 8, + EXIST = 16, + SAW = 32, + RESET = 64, + PASSWORD = 128, + LOCK = 256, + UNLOCK = 512, + ZERO = 1024, +}; + +static const int MAX_PARAM = 2; + +static const State STATE_ANY = (State) 0; + +static struct action { + ResponseCode (*run)(KeyStore* keyStore, int sock, uid_t uid, Value* param1, Value* param2); + int8_t code; + State state; + uint32_t perm; + int lengths[MAX_PARAM]; +} actions[] = { + {test, 't', STATE_ANY, TEST, {0, 0}}, + {get, 'g', STATE_NO_ERROR, GET, {KEY_SIZE, 0}}, + {insert, 'i', STATE_NO_ERROR, INSERT, {KEY_SIZE, VALUE_SIZE}}, + {del, 'd', STATE_ANY, DELETE, {KEY_SIZE, 0}}, + {exist, 'e', STATE_ANY, EXIST, {KEY_SIZE, 0}}, + {saw, 's', STATE_ANY, SAW, {KEY_SIZE, 0}}, + {reset, 'r', STATE_ANY, RESET, {0, 0}}, + {password, 'p', STATE_ANY, PASSWORD, {PASSWORD_SIZE, 0}}, + {lock, 'l', STATE_NO_ERROR, LOCK, {0, 0}}, + {unlock, 'u', STATE_LOCKED, UNLOCK, {PASSWORD_SIZE, 0}}, + {zero, 'z', STATE_ANY, ZERO, {0, 0}}, + {NULL, 0 , STATE_ANY, 0, {0, 0}}, +}; + +static struct user { + uid_t uid; + uid_t euid; + uint32_t perms; +} users[] = { + {AID_SYSTEM, ~0, ~GET}, + {AID_VPN, AID_SYSTEM, GET}, + {AID_WIFI, AID_SYSTEM, GET}, + {AID_ROOT, AID_SYSTEM, GET}, + {AID_KEYCHAIN, AID_SYSTEM, TEST | GET | SAW}, + {~0, ~0, TEST | GET | INSERT | DELETE | EXIST | SAW}, +}; + +static ResponseCode process(KeyStore* keyStore, int sock, uid_t uid, int8_t code) { + struct user* user = users; + struct action* action = actions; + int i; + + while (~user->uid && user->uid != uid) { + ++user; + } + while (action->code && action->code != code) { + ++action; + } + if (!action->code) { + return UNDEFINED_ACTION; + } + if (!(action->perm & user->perms)) { + return PERMISSION_DENIED; + } + if (action->state != STATE_ANY && action->state != keyStore->getState()) { + return (ResponseCode) keyStore->getState(); + } + if (~user->euid) { + uid = user->euid; + } + Value params[MAX_PARAM]; + for (i = 0; i < MAX_PARAM && action->lengths[i] != 0; ++i) { + params[i].length = recv_message(sock, params[i].value, action->lengths[i]); + if (params[i].length < 0) { + return PROTOCOL_ERROR; + } + } + if (!recv_end_of_file(sock)) { + return PROTOCOL_ERROR; + } + return action->run(keyStore, sock, uid, ¶ms[0], ¶ms[1]); +} + +int main(int argc, char* argv[]) { + int controlSocket = android_get_control_socket("keystore"); + if (argc < 2) { + LOGE("A directory must be specified!"); + return 1; + } + if (chdir(argv[1]) == -1) { + LOGE("chdir: %s: %s", argv[1], strerror(errno)); + return 1; + } + + Entropy entropy; + if (!entropy.open()) { + return 1; + } + if (listen(controlSocket, 3) == -1) { + LOGE("listen: %s", strerror(errno)); + return 1; + } + + signal(SIGPIPE, SIG_IGN); + + KeyStore keyStore(&entropy); + int sock; + while ((sock = accept(controlSocket, NULL, 0)) != -1) { + struct timeval tv; + tv.tv_sec = 3; + setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); + setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); + + struct ucred cred; + socklen_t size = sizeof(cred); + int credResult = getsockopt(sock, SOL_SOCKET, SO_PEERCRED, &cred, &size); + if (credResult != 0) { + LOGW("getsockopt: %s", strerror(errno)); + } else { + int8_t request; + if (recv_code(sock, &request)) { + State old_state = keyStore.getState(); + ResponseCode response = process(&keyStore, sock, cred.uid, request); + if (response == NO_ERROR_RESPONSE_CODE_SENT) { + response = NO_ERROR; + } else { + send_code(sock, response); + } + LOGI("uid: %d action: %c -> %d state: %d -> %d retry: %d", + cred.uid, + request, response, + old_state, keyStore.getState(), + keyStore.getRetry()); + } + } + close(sock); + } + LOGE("accept: %s", strerror(errno)); + return 1; +} diff --git a/cmds/keystore/keystore.h b/cmds/keystore/keystore.h index 5ef51e9cd7d0..5ae3d24acee0 100644 --- a/cmds/keystore/keystore.h +++ b/cmds/keystore/keystore.h @@ -17,17 +17,27 @@ #ifndef __KEYSTORE_H__ #define __KEYSTORE_H__ -enum response_code { - NO_ERROR = 1, - LOCKED = 2, - UNINITIALIZED = 3, +// note state values overlap with ResponseCode for the purposes of the state() API +enum State { + STATE_NO_ERROR = 1, + STATE_LOCKED = 2, + STATE_UNINITIALIZED = 3, +}; + +enum ResponseCode { + NO_ERROR = STATE_NO_ERROR, // 1 + LOCKED = STATE_LOCKED, // 2 + UNINITIALIZED = STATE_UNINITIALIZED, // 3 SYSTEM_ERROR = 4, PROTOCOL_ERROR = 5, PERMISSION_DENIED = 6, KEY_NOT_FOUND = 7, VALUE_CORRUPTED = 8, UNDEFINED_ACTION = 9, - WRONG_PASSWORD = 10, + WRONG_PASSWORD_0 = 10, + WRONG_PASSWORD_1 = 11, + WRONG_PASSWORD_2 = 12, + WRONG_PASSWORD_3 = 13, // MAX_RETRY = 4 }; #endif diff --git a/cmds/keystore/keystore_cli.c b/cmds/keystore/keystore_cli.cpp index e8afb5a945b2..dcd3bcb8fc01 100644 --- a/cmds/keystore/keystore_cli.c +++ b/cmds/keystore/keystore_cli.cpp @@ -24,44 +24,40 @@ #include "keystore.h" -char *responses[256] = { - [NO_ERROR] = "No error", - [LOCKED] = "Locked", - [UNINITIALIZED] = "Uninitialized", - [SYSTEM_ERROR] = "System error", - [PROTOCOL_ERROR] = "Protocol error", - [PERMISSION_DENIED] = "Permission denied", - [KEY_NOT_FOUND] = "Key not found", - [VALUE_CORRUPTED] = "Value corrupted", - [UNDEFINED_ACTION] = "Undefined action", - [WRONG_PASSWORD] = "Wrong password (last chance)", - [WRONG_PASSWORD + 1] = "Wrong password (2 tries left)", - [WRONG_PASSWORD + 2] = "Wrong password (3 tries left)", - [WRONG_PASSWORD + 3] = "Wrong password (4 tries left)", +static const char* responses[] = { + NULL, + /* [NO_ERROR] = */ "No error", + /* [LOCKED] = */ "Locked", + /* [UNINITIALIZED] = */ "Uninitialized", + /* [SYSTEM_ERROR] = */ "System error", + /* [PROTOCOL_ERROR] = */ "Protocol error", + /* [PERMISSION_DENIED] = */ "Permission denied", + /* [KEY_NOT_FOUND] = */ "Key not found", + /* [VALUE_CORRUPTED] = */ "Value corrupted", + /* [UNDEFINED_ACTION] = */ "Undefined action", + /* [WRONG_PASSWORD] = */ "Wrong password (last chance)", + /* [WRONG_PASSWORD + 1] = */ "Wrong password (2 tries left)", + /* [WRONG_PASSWORD + 2] = */ "Wrong password (3 tries left)", + /* [WRONG_PASSWORD + 3] = */ "Wrong password (4 tries left)", }; -#define MAX_RESPONSE (WRONG_PASSWORD + 3) - -int main(int argc, char **argv) +int main(int argc, char* argv[]) { - uint8_t bytes[65536]; - uint8_t code; - int sock, i; - if (argc < 2) { printf("Usage: %s action [parameter ...]\n", argv[0]); return 0; } - sock = socket_local_client("keystore", ANDROID_SOCKET_NAMESPACE_RESERVED, - SOCK_STREAM); + int sock = socket_local_client("keystore", ANDROID_SOCKET_NAMESPACE_RESERVED, + SOCK_STREAM); if (sock == -1) { puts("Failed to connect"); return 1; } send(sock, argv[1], 1, 0); - for (i = 2; i < argc; ++i) { + uint8_t bytes[65536]; + for (int i = 2; i < argc; ++i) { uint16_t length = strlen(argv[i]); bytes[0] = length >> 8; bytes[1] = length; @@ -70,11 +66,13 @@ int main(int argc, char **argv) } shutdown(sock, SHUT_WR); + uint8_t code; if (recv(sock, &code, 1, 0) != 1) { puts("Failed to receive"); return 1; } printf("%d %s\n", code , responses[code] ? responses[code] : "Unknown"); + int i; while ((i = recv(sock, &bytes[0], 1, 0)) == 1) { int length; int offset; diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java index 1b2326a43562..b0e4a864b6b0 100644 --- a/cmds/pm/src/com/android/commands/pm/Pm.java +++ b/cmds/pm/src/com/android/commands/pm/Pm.java @@ -37,6 +37,7 @@ import android.content.res.AssetManager; import android.content.res.Resources; import android.net.Uri; import android.os.Parcel; +import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; @@ -61,6 +62,7 @@ public final class Pm { private static final String PM_NOT_RUNNING_ERR = "Error: Could not access the Package Manager. Is the system running?"; + private static final int ROOT_UID = 0; public static void main(String[] args) { new Pm().run(args); @@ -128,6 +130,16 @@ public final class Pm { return; } + if ("createUser".equals(op)) { + runCreateUser(); + return; + } + + if ("removeUser".equals(op)) { + runRemoveUser(); + return; + } + try { if (args.length == 1) { if (args[0].equalsIgnoreCase("-l")) { @@ -789,6 +801,63 @@ public final class Pm { } } + public void runCreateUser() { + // Need to be run as root + if (Process.myUid() != ROOT_UID) { + System.err.println("Error: createUser must be run as root"); + return; + } + String name; + String arg = nextArg(); + if (arg == null) { + System.err.println("Error: no user name specified."); + showUsage(); + return; + } + name = arg; + try { + if (mPm.createUser(name, 0) == null) { + System.err.println("Error: couldn't create user."); + showUsage(); + } + } catch (RemoteException e) { + System.err.println(e.toString()); + System.err.println(PM_NOT_RUNNING_ERR); + } + + } + + public void runRemoveUser() { + // Need to be run as root + if (Process.myUid() != ROOT_UID) { + System.err.println("Error: removeUser must be run as root"); + return; + } + int userId; + String arg = nextArg(); + if (arg == null) { + System.err.println("Error: no user id specified."); + showUsage(); + return; + } + try { + userId = Integer.parseInt(arg); + } catch (NumberFormatException e) { + System.err.println("Error: user id has to be a number."); + showUsage(); + return; + } + try { + if (!mPm.removeUser(userId)) { + System.err.println("Error: couldn't remove user."); + showUsage(); + } + } catch (RemoteException e) { + System.err.println(e.toString()); + System.err.println(PM_NOT_RUNNING_ERR); + } + } + class PackageDeleteObserver extends IPackageDeleteObserver.Stub { boolean finished; boolean result; @@ -1032,6 +1101,8 @@ public final class Pm { System.err.println(" pm enable PACKAGE_OR_COMPONENT"); System.err.println(" pm disable PACKAGE_OR_COMPONENT"); System.err.println(" pm setInstallLocation [0/auto] [1/internal] [2/external]"); + System.err.println(" pm createUser USER_NAME"); + System.err.println(" pm removeUser USER_ID"); System.err.println(""); System.err.println("The list packages command prints all packages, optionally only"); System.err.println("those whose package name contains the text in FILTER. Options:"); diff --git a/cmds/runtime/main_runtime.cpp b/cmds/runtime/main_runtime.cpp index 83cb53317028..dbff095f6501 100644 --- a/cmds/runtime/main_runtime.cpp +++ b/cmds/runtime/main_runtime.cpp @@ -12,7 +12,7 @@ #include <binder/IPCThreadState.h> #include <binder/ProcessState.h> -#include <utils/Log.h> +#include <utils/Log.h> #include <cutils/zygote.h> #include <cutils/properties.h> @@ -41,7 +41,7 @@ #undef LOG_TAG #define LOG_TAG "runtime" -static const char* ZYGOTE_ARGV[] = { +static const char* ZYGOTE_ARGV[] = { "--setuid=1000", "--setgid=1000", "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,3001,3002,3003", @@ -68,7 +68,6 @@ extern Condition gEventQCondition; namespace android { -extern status_t app_init(const char* className); extern void set_finish_init_func(void (*func)()); @@ -76,7 +75,7 @@ extern void set_finish_init_func(void (*func)()); * This class is used to kill this process (runtime) when the system_server dies. */ class GrimReaper : public IBinder::DeathRecipient { -public: +public: GrimReaper() { } virtual void binderDied(const wp<IBinder>& who) @@ -170,7 +169,7 @@ LOGI("run() sending FIRST_CALL_TRANSACTION to activity manager"); /* * Post-system-process initialization. - * + * * This function continues initialization after the system process * has been initialized. It needs to be separate because the system * initialization needs to care of starting the Android runtime if it is not @@ -210,17 +209,17 @@ static bool contextChecker( static void boot_init() { LOGI("Entered boot_init()!\n"); - + sp<ProcessState> proc(ProcessState::self()); LOGD("ProcessState: %p\n", proc.get()); proc->becomeContextManager(contextChecker, NULL); - + if (proc->supportsProcesses()) { LOGI("Binder driver opened. Multiprocess enabled.\n"); } else { LOGI("Binder driver not found. Processes not supported.\n"); } - + sp<BServiceManager> sm = new BServiceManager; proc->setContextObject(sm); } @@ -258,7 +257,7 @@ static void validateTime() int res; time_t min_time = 1167652800; // jan 1 2007, type 'date -ud "1/1 12:00" +%s' to get value for current year struct timespec ts; - + fd = open("/dev/alarm", O_RDWR); if(fd < 0) { LOGW("Unable to open alarm driver: %s\n", strerror(errno)); @@ -346,14 +345,14 @@ int main(int argc, char* const argv[]) int ic; int result = 1; pid_t systemPid; - + sp<ProcessState> proc; #ifndef HAVE_ANDROID_OS /* Set stdout/stderr to unbuffered for MinGW/MSYS. */ //setvbuf(stdout, NULL, _IONBF, 0); //setvbuf(stderr, NULL, _IONBF, 0); - + LOGI("commandline args:\n"); for (int i = 0; i < argc; i++) LOGI(" %2d: '%s'\n", i, argv[i]); @@ -455,7 +454,7 @@ int main(int argc, char* const argv[]) #if 0 // Hack to keep libc from beating the filesystem to death. It's - // hitting /etc/localtime frequently, + // hitting /etc/localtime frequently, // // This statement locks us into Pacific time. We could do better, // but there's not much point until we're sure that the library @@ -467,15 +466,15 @@ int main(int argc, char* const argv[]) /* track our progress through the boot sequence */ const int LOG_BOOT_PROGRESS_START = 3000; - LOG_EVENT_LONG(LOG_BOOT_PROGRESS_START, + LOG_EVENT_LONG(LOG_BOOT_PROGRESS_START, ns2ms(systemTime(SYSTEM_TIME_MONOTONIC))); validateTime(); proc = ProcessState::self(); - + boot_init(); - + /* If we are in multiprocess mode, have zygote spawn the system * server process and call system_init(). If we are running in * single process mode just call system_init() directly. @@ -488,8 +487,8 @@ int main(int argc, char* const argv[]) property_get("log.redirect-stdio", propBuf, ""); logStdio = (strcmp(propBuf, "true") == 0); - zygote_run_oneshot((int)(!logStdio), - sizeof(ZYGOTE_ARGV) / sizeof(ZYGOTE_ARGV[0]), + zygote_run_oneshot((int)(!logStdio), + sizeof(ZYGOTE_ARGV) / sizeof(ZYGOTE_ARGV[0]), ZYGOTE_ARGV); //start_process("/system/bin/mediaserver"); @@ -497,8 +496,8 @@ int main(int argc, char* const argv[]) } else { #ifndef HAVE_ANDROID_OS QuickRuntime* runt = new QuickRuntime(); - runt->start("com/android/server/SystemServer", - false /* spontaneously fork system server from zygote */); + runt->start("com/android/server/SystemServer", + "" /* spontaneously fork system server from zygote */); #endif } @@ -506,11 +505,11 @@ int main(int argc, char* const argv[]) finish_system_init(proc); run(proc); - + bail: if (proc != NULL) { proc->setContextObject(NULL); } - + return 0; } diff --git a/cmds/screencap/Android.mk b/cmds/screencap/Android.mk index 400a36b76719..ca8008bdba31 100644 --- a/cmds/screencap/Android.mk +++ b/cmds/screencap/Android.mk @@ -10,7 +10,7 @@ LOCAL_SHARED_LIBRARIES := \ libbinder \ libskia \ libui \ - libsurfaceflinger_client + libgui LOCAL_MODULE:= screencap diff --git a/cmds/sensorservice/Android.mk b/cmds/sensorservice/Android.mk new file mode 100644 index 000000000000..0811be57f61d --- /dev/null +++ b/cmds/sensorservice/Android.mk @@ -0,0 +1,19 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + main_sensorservice.cpp + +LOCAL_SHARED_LIBRARIES := \ + libsensorservice \ + libbinder \ + libutils + +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/../../services/sensorservice + +LOCAL_MODULE_TAGS := optional + +LOCAL_MODULE:= sensorservice + +include $(BUILD_EXECUTABLE) diff --git a/cmds/sensorservice/main_sensorservice.cpp b/cmds/sensorservice/main_sensorservice.cpp new file mode 100644 index 000000000000..8610627b80c3 --- /dev/null +++ b/cmds/sensorservice/main_sensorservice.cpp @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <binder/BinderService.h> +#include <SensorService.h> + +using namespace android; + +int main(int argc, char** argv) { + SensorService::publishAndJoinThreadPool(); + return 0; +} diff --git a/cmds/stagefright/Android.mk b/cmds/stagefright/Android.mk index 1b13dd954535..e9642f7cc8c5 100644 --- a/cmds/stagefright/Android.mk +++ b/cmds/stagefright/Android.mk @@ -8,7 +8,7 @@ LOCAL_SRC_FILES:= \ LOCAL_SHARED_LIBRARIES := \ libstagefright libmedia libutils libbinder libstagefright_foundation \ - libskia libsurfaceflinger_client libgui + libskia libgui LOCAL_C_INCLUDES:= \ $(JNI_H_INCLUDE) \ @@ -107,7 +107,7 @@ LOCAL_SRC_FILES:= \ stream.cpp \ LOCAL_SHARED_LIBRARIES := \ - libstagefright liblog libutils libbinder libsurfaceflinger_client \ + libstagefright liblog libutils libbinder libgui \ libstagefright_foundation libmedia LOCAL_C_INCLUDES:= \ @@ -132,7 +132,7 @@ LOCAL_SRC_FILES:= \ LOCAL_SHARED_LIBRARIES := \ libstagefright liblog libutils libbinder libstagefright_foundation \ - libmedia libsurfaceflinger_client libcutils libui + libmedia libgui libcutils libui LOCAL_C_INCLUDES:= \ $(JNI_H_INCLUDE) \ diff --git a/cmds/stagefright/audioloop.cpp b/cmds/stagefright/audioloop.cpp index 87336629f2b2..858681f6ce45 100644 --- a/cmds/stagefright/audioloop.cpp +++ b/cmds/stagefright/audioloop.cpp @@ -11,6 +11,8 @@ #include <media/stagefright/OMXClient.h> #include <media/stagefright/OMXCodec.h> +#include <system/audio.h> + using namespace android; int main() { @@ -31,8 +33,8 @@ int main() { AUDIO_SOURCE_DEFAULT, kSampleRate, kNumChannels == 1 - ? AudioSystem::CHANNEL_IN_MONO - : AudioSystem::CHANNEL_IN_STEREO); + ? AUDIO_CHANNEL_IN_MONO + : AUDIO_CHANNEL_IN_STEREO); #endif sp<MetaData> meta = new MetaData; diff --git a/cmds/stagefright/sf2.cpp b/cmds/stagefright/sf2.cpp index c1d08036d6bd..289665f51b5d 100644 --- a/cmds/stagefright/sf2.cpp +++ b/cmds/stagefright/sf2.cpp @@ -543,7 +543,6 @@ int main(int argc, char **argv) { CHECK_EQ(composerClient->initCheck(), (status_t)OK); control = composerClient->createSurface( - getpid(), String8("A Surface"), 0, 1280, diff --git a/cmds/stagefright/stagefright.cpp b/cmds/stagefright/stagefright.cpp index a875c3afeeb7..01262fa7a136 100644 --- a/cmds/stagefright/stagefright.cpp +++ b/cmds/stagefright/stagefright.cpp @@ -695,12 +695,14 @@ int main(int argc, char **argv) { for (int k = 0; k < argc; ++k) { const char *filename = argv[k]; + bool failed = true; CHECK_EQ(retriever->setDataSource(filename), (status_t)OK); sp<IMemory> mem = retriever->getFrameAtTime(-1, MediaSource::ReadOptions::SEEK_PREVIOUS_SYNC); if (mem != NULL) { + failed = false; printf("getFrameAtTime(%s) => OK\n", filename); VideoFrame *frame = (VideoFrame *)mem->pointer(); @@ -715,16 +717,21 @@ int main(int argc, char **argv) { "/sdcard/out.jpg", bitmap, SkImageEncoder::kJPEG_Type, SkImageEncoder::kDefaultQuality)); - } else { + } + + { mem = retriever->extractAlbumArt(); if (mem != NULL) { + failed = false; printf("extractAlbumArt(%s) => OK\n", filename); - } else { - printf("both getFrameAtTime and extractAlbumArt " - "failed on file '%s'.\n", filename); } } + + if (failed) { + printf("both getFrameAtTime and extractAlbumArt " + "failed on file '%s'.\n", filename); + } } return 0; diff --git a/cmds/stagefright/stream.cpp b/cmds/stagefright/stream.cpp index bb84bd1ab995..f780afb80a0a 100644 --- a/cmds/stagefright/stream.cpp +++ b/cmds/stagefright/stream.cpp @@ -107,7 +107,7 @@ struct MyClient : public BnMediaPlayerClient { : mEOS(false) { } - virtual void notify(int msg, int ext1, int ext2) { + virtual void notify(int msg, int ext1, int ext2, const Parcel *obj) { Mutex::Autolock autoLock(mLock); if (msg == MEDIA_ERROR || msg == MEDIA_PLAYBACK_COMPLETE) { @@ -149,7 +149,6 @@ int main(int argc, char **argv) { sp<SurfaceControl> control = composerClient->createSurface( - getpid(), String8("A Surface"), 0, 1280, diff --git a/cmds/system_server/library/system_init.cpp b/cmds/system_server/library/system_init.cpp index a29ba733a0f0..a19711e4794b 100644 --- a/cmds/system_server/library/system_init.cpp +++ b/cmds/system_server/library/system_init.cpp @@ -37,7 +37,7 @@ namespace android { * This class is used to kill this process when the runtime dies. */ class GrimReaper : public IBinder::DeathRecipient { -public: +public: GrimReaper() { } virtual void binderDied(const wp<IBinder>& who) @@ -54,15 +54,15 @@ public: extern "C" status_t system_init() { LOGI("Entered system_init()"); - + sp<ProcessState> proc(ProcessState::self()); - + sp<IServiceManager> sm = defaultServiceManager(); LOGI("ServiceManager: %p\n", sm.get()); - + sp<GrimReaper> grim = new GrimReaper(); sm->asBinder()->linkToDeath(grim, grim.get(), 0); - + char propBuf[PROPERTY_VALUE_MAX]; property_get("system_init.startsurfaceflinger", propBuf, "1"); if (strcmp(propBuf, "1") == 0) { @@ -70,8 +70,11 @@ extern "C" status_t system_init() SurfaceFlinger::instantiate(); } - // Start the sensor service - SensorService::instantiate(); + property_get("system_init.startsensorservice", propBuf, "1"); + if (strcmp(propBuf, "1") == 0) { + // Start the sensor service + SensorService::instantiate(); + } // On the simulator, audioflinger et al don't get started the // same way as on the device, and we need to start them here @@ -97,12 +100,23 @@ extern "C" status_t system_init() // the beginning of their processes's main(), before calling // the init function. LOGI("System server: starting Android runtime.\n"); - AndroidRuntime* runtime = AndroidRuntime::getRuntime(); LOGI("System server: starting Android services.\n"); - runtime->callStatic("com/android/server/SystemServer", "init2"); - + JNIEnv* env = runtime->getJNIEnv(); + if (env == NULL) { + return UNKNOWN_ERROR; + } + jclass clazz = env->FindClass("com/android/server/SystemServer"); + if (clazz == NULL) { + return UNKNOWN_ERROR; + } + jmethodID methodId = env->GetStaticMethodID(clazz, "init2", "()V"); + if (methodId == NULL) { + return UNKNOWN_ERROR; + } + env->CallStaticVoidMethod(clazz, methodId); + // If running in our own process, just go into the thread // pool. Otherwise, call the initialization finished // func to let this process continue its initilization. @@ -114,4 +128,3 @@ extern "C" status_t system_init() } return NO_ERROR; } - |