diff options
author | Justin Klaassen <justinklaassen@google.com> | 2017-11-17 16:38:15 -0500 |
---|---|---|
committer | Justin Klaassen <justinklaassen@google.com> | 2017-11-17 16:38:15 -0500 |
commit | 6a65f2da209bff03cb0eb6da309710ac6ee5026d (patch) | |
tree | 48e2090e716d4178378cb0599fc5d9cffbcf3f63 /android/util | |
parent | 46c77c203439b3b37c99d09e326df4b1fe08c10b (diff) | |
download | android-28-6a65f2da209bff03cb0eb6da309710ac6ee5026d.tar.gz |
Import Android SDK Platform P [4456821]
/google/data/ro/projects/android/fetch_artifact \
--bid 4456821 \
--target sdk_phone_armv7-win_sdk \
sdk-repo-linux-sources-4456821.zip
AndroidVersion.ApiLevel has been modified to appear as 28
Change-Id: I2d206b200d7952f899a5d1647ab532638cc8dd43
Diffstat (limited to 'android/util')
-rw-r--r-- | android/util/FeatureFlagUtils.java | 3 | ||||
-rw-r--r-- | android/util/KeyValueListParser.java | 14 | ||||
-rw-r--r-- | android/util/Log.java | 295 | ||||
-rw-r--r-- | android/util/StatsManager.java | 134 | ||||
-rw-r--r-- | android/util/TimeUtils.java | 8 | ||||
-rw-r--r-- | android/util/apk/ApkSignatureSchemeV2Verifier.java | 192 | ||||
-rw-r--r-- | android/util/apk/ApkVerityBuilder.java | 351 | ||||
-rw-r--r-- | android/util/apk/ByteBufferDataSource.java | 66 | ||||
-rw-r--r-- | android/util/apk/ByteBufferFactory.java | 28 | ||||
-rw-r--r-- | android/util/apk/DataDigester.java | 28 | ||||
-rw-r--r-- | android/util/apk/DataSource.java | 38 | ||||
-rw-r--r-- | android/util/apk/MemoryMappedFileDataSource.java | 105 | ||||
-rw-r--r-- | android/util/apk/SignatureInfo.java | 49 | ||||
-rw-r--r-- | android/util/proto/ProtoOutputStream.java | 82 |
14 files changed, 898 insertions, 495 deletions
diff --git a/android/util/FeatureFlagUtils.java b/android/util/FeatureFlagUtils.java index fc1d4873..2a272208 100644 --- a/android/util/FeatureFlagUtils.java +++ b/android/util/FeatureFlagUtils.java @@ -16,6 +16,7 @@ package android.util; +import android.content.Context; import android.os.SystemProperties; import android.text.TextUtils; @@ -37,7 +38,7 @@ public class FeatureFlagUtils { * @param feature the flag name * @return true if the flag is enabled (either by default in system, or override by user) */ - public static boolean isEnabled(String feature) { + public static boolean isEnabled(Context context, String feature) { // Tries to get feature flag from system property. // Step 1: check if feature flag has any override. Flag name: sys.fflag.override.<feature> String value = SystemProperties.get(FFLAG_OVERRIDE_PREFIX + feature); diff --git a/android/util/KeyValueListParser.java b/android/util/KeyValueListParser.java index be531ff3..d50395e2 100644 --- a/android/util/KeyValueListParser.java +++ b/android/util/KeyValueListParser.java @@ -147,4 +147,18 @@ public class KeyValueListParser { } return def; } + + /** + * @return the number of keys. + */ + public int size() { + return mValues.size(); + } + + /** + * @return the key at {@code index}. Use with {@link #size()} to enumerate all key-value pairs. + */ + public String keyAt(int index) { + return mValues.keyAt(index); + } } diff --git a/android/util/Log.java b/android/util/Log.java index 02998653..b94e48b3 100644 --- a/android/util/Log.java +++ b/android/util/Log.java @@ -16,45 +16,12 @@ package android.util; -import android.os.DeadSystemException; - -import com.android.internal.os.RuntimeInit; -import com.android.internal.util.FastPrintWriter; -import com.android.internal.util.LineBreakBufferedWriter; - import java.io.PrintWriter; import java.io.StringWriter; -import java.io.Writer; import java.net.UnknownHostException; /** - * API for sending log output. - * - * <p>Generally, you should use the {@link #v Log.v()}, {@link #d Log.d()}, - * {@link #i Log.i()}, {@link #w Log.w()}, and {@link #e Log.e()} methods to write logs. - * You can then <a href="{@docRoot}studio/debug/am-logcat.html">view the logs in logcat</a>. - * - * <p>The order in terms of verbosity, from least to most is - * ERROR, WARN, INFO, DEBUG, VERBOSE. Verbose should never be compiled - * into an application except during development. Debug logs are compiled - * in but stripped at runtime. Error, warning and info logs are always kept. - * - * <p><b>Tip:</b> A good convention is to declare a <code>TAG</code> constant - * in your class: - * - * <pre>private static final String TAG = "MyActivity";</pre> - * - * and use that in subsequent calls to the log methods. - * </p> - * - * <p><b>Tip:</b> Don't forget that when you make a call like - * <pre>Log.v(TAG, "index=" + i);</pre> - * that when you're building the string to pass into Log.d, the compiler uses a - * StringBuilder and at least three allocations occur: the StringBuilder - * itself, the buffer, and the String object. Realistically, there is also - * another buffer allocation and copy, and even more pressure on the gc. - * That means that if your log message is filtered out, you might be doing - * significant work and incurring significant overhead. + * Mock Log implementation for testing on non android host. */ public final class Log { @@ -88,29 +55,6 @@ public final class Log { */ public static final int ASSERT = 7; - /** - * Exception class used to capture a stack trace in {@link #wtf}. - * @hide - */ - public static class TerribleFailure extends Exception { - TerribleFailure(String msg, Throwable cause) { super(msg, cause); } - } - - /** - * Interface to handle terrible failures from {@link #wtf}. - * - * @hide - */ - public interface TerribleFailureHandler { - void onTerribleFailure(String tag, TerribleFailure what, boolean system); - } - - private static TerribleFailureHandler sWtfHandler = new TerribleFailureHandler() { - public void onTerribleFailure(String tag, TerribleFailure what, boolean system) { - RuntimeInit.wtf(tag, what, system); - } - }; - private Log() { } @@ -121,7 +65,7 @@ public final class Log { * @param msg The message you would like logged. */ public static int v(String tag, String msg) { - return println_native(LOG_ID_MAIN, VERBOSE, tag, msg); + return println(LOG_ID_MAIN, VERBOSE, tag, msg); } /** @@ -132,7 +76,7 @@ public final class Log { * @param tr An exception to log */ public static int v(String tag, String msg, Throwable tr) { - return printlns(LOG_ID_MAIN, VERBOSE, tag, msg, tr); + return println(LOG_ID_MAIN, VERBOSE, tag, msg + '\n' + getStackTraceString(tr)); } /** @@ -142,7 +86,7 @@ public final class Log { * @param msg The message you would like logged. */ public static int d(String tag, String msg) { - return println_native(LOG_ID_MAIN, DEBUG, tag, msg); + return println(LOG_ID_MAIN, DEBUG, tag, msg); } /** @@ -153,7 +97,7 @@ public final class Log { * @param tr An exception to log */ public static int d(String tag, String msg, Throwable tr) { - return printlns(LOG_ID_MAIN, DEBUG, tag, msg, tr); + return println(LOG_ID_MAIN, DEBUG, tag, msg + '\n' + getStackTraceString(tr)); } /** @@ -163,7 +107,7 @@ public final class Log { * @param msg The message you would like logged. */ public static int i(String tag, String msg) { - return println_native(LOG_ID_MAIN, INFO, tag, msg); + return println(LOG_ID_MAIN, INFO, tag, msg); } /** @@ -174,7 +118,7 @@ public final class Log { * @param tr An exception to log */ public static int i(String tag, String msg, Throwable tr) { - return printlns(LOG_ID_MAIN, INFO, tag, msg, tr); + return println(LOG_ID_MAIN, INFO, tag, msg + '\n' + getStackTraceString(tr)); } /** @@ -184,7 +128,7 @@ public final class Log { * @param msg The message you would like logged. */ public static int w(String tag, String msg) { - return println_native(LOG_ID_MAIN, WARN, tag, msg); + return println(LOG_ID_MAIN, WARN, tag, msg); } /** @@ -195,31 +139,9 @@ public final class Log { * @param tr An exception to log */ public static int w(String tag, String msg, Throwable tr) { - return printlns(LOG_ID_MAIN, WARN, tag, msg, tr); + return println(LOG_ID_MAIN, WARN, tag, msg + '\n' + getStackTraceString(tr)); } - /** - * Checks to see whether or not a log for the specified tag is loggable at the specified level. - * - * The default level of any tag is set to INFO. This means that any level above and including - * INFO will be logged. Before you make any calls to a logging method you should check to see - * if your tag should be logged. You can change the default level by setting a system property: - * 'setprop log.tag.<YOUR_LOG_TAG> <LEVEL>' - * Where level is either VERBOSE, DEBUG, INFO, WARN, ERROR, ASSERT, or SUPPRESS. SUPPRESS will - * turn off all logging for your tag. You can also create a local.prop file that with the - * following in it: - * 'log.tag.<YOUR_LOG_TAG>=<LEVEL>' - * and place that in /data/local.prop. - * - * @param tag The tag to check. - * @param level The level to check. - * @return Whether or not that this is allowed to be logged. - * @throws IllegalArgumentException is thrown if the tag.length() > 23 - * for Nougat (7.0) releases (API <= 23) and prior, there is no - * tag limit of concern after this API level. - */ - public static native boolean isLoggable(String tag, int level); - /* * Send a {@link #WARN} log message and log the exception. * @param tag Used to identify the source of a log message. It usually identifies @@ -227,7 +149,7 @@ public final class Log { * @param tr An exception to log */ public static int w(String tag, Throwable tr) { - return printlns(LOG_ID_MAIN, WARN, tag, "", tr); + return println(LOG_ID_MAIN, WARN, tag, getStackTraceString(tr)); } /** @@ -237,7 +159,7 @@ public final class Log { * @param msg The message you would like logged. */ public static int e(String tag, String msg) { - return println_native(LOG_ID_MAIN, ERROR, tag, msg); + return println(LOG_ID_MAIN, ERROR, tag, msg); } /** @@ -248,82 +170,7 @@ public final class Log { * @param tr An exception to log */ public static int e(String tag, String msg, Throwable tr) { - return printlns(LOG_ID_MAIN, ERROR, tag, msg, tr); - } - - /** - * What a Terrible Failure: Report a condition that should never happen. - * The error will always be logged at level ASSERT with the call stack. - * Depending on system configuration, a report may be added to the - * {@link android.os.DropBoxManager} and/or the process may be terminated - * immediately with an error dialog. - * @param tag Used to identify the source of a log message. - * @param msg The message you would like logged. - */ - public static int wtf(String tag, String msg) { - return wtf(LOG_ID_MAIN, tag, msg, null, false, false); - } - - /** - * Like {@link #wtf(String, String)}, but also writes to the log the full - * call stack. - * @hide - */ - public static int wtfStack(String tag, String msg) { - return wtf(LOG_ID_MAIN, tag, msg, null, true, false); - } - - /** - * What a Terrible Failure: Report an exception that should never happen. - * Similar to {@link #wtf(String, String)}, with an exception to log. - * @param tag Used to identify the source of a log message. - * @param tr An exception to log. - */ - public static int wtf(String tag, Throwable tr) { - return wtf(LOG_ID_MAIN, tag, tr.getMessage(), tr, false, false); - } - - /** - * What a Terrible Failure: Report an exception that should never happen. - * Similar to {@link #wtf(String, Throwable)}, with a message as well. - * @param tag Used to identify the source of a log message. - * @param msg The message you would like logged. - * @param tr An exception to log. May be null. - */ - public static int wtf(String tag, String msg, Throwable tr) { - return wtf(LOG_ID_MAIN, tag, msg, tr, false, false); - } - - static int wtf(int logId, String tag, String msg, Throwable tr, boolean localStack, - boolean system) { - TerribleFailure what = new TerribleFailure(msg, tr); - // Only mark this as ERROR, do not use ASSERT since that should be - // reserved for cases where the system is guaranteed to abort. - // The onTerribleFailure call does not always cause a crash. - int bytes = printlns(logId, ERROR, tag, msg, localStack ? what : tr); - sWtfHandler.onTerribleFailure(tag, what, system); - return bytes; - } - - static void wtfQuiet(int logId, String tag, String msg, boolean system) { - TerribleFailure what = new TerribleFailure(msg, null); - sWtfHandler.onTerribleFailure(tag, what, system); - } - - /** - * Sets the terrible failure handler, for testing. - * - * @return the old handler - * - * @hide - */ - public static TerribleFailureHandler setWtfHandler(TerribleFailureHandler handler) { - if (handler == null) { - throw new NullPointerException("handler == null"); - } - TerribleFailureHandler oldHandler = sWtfHandler; - sWtfHandler = handler; - return oldHandler; + return println(LOG_ID_MAIN, ERROR, tag, msg + '\n' + getStackTraceString(tr)); } /** @@ -346,7 +193,7 @@ public final class Log { } StringWriter sw = new StringWriter(); - PrintWriter pw = new FastPrintWriter(sw, false, 256); + PrintWriter pw = new PrintWriter(sw); tr.printStackTrace(pw); pw.flush(); return sw.toString(); @@ -361,7 +208,7 @@ public final class Log { * @return The number of bytes written. */ public static int println(int priority, String tag, String msg) { - return println_native(LOG_ID_MAIN, priority, tag, msg); + return println(LOG_ID_MAIN, priority, tag, msg); } /** @hide */ public static final int LOG_ID_MAIN = 0; @@ -370,115 +217,9 @@ public final class Log { /** @hide */ public static final int LOG_ID_SYSTEM = 3; /** @hide */ public static final int LOG_ID_CRASH = 4; - /** @hide */ public static native int println_native(int bufID, - int priority, String tag, String msg); - - /** - * Return the maximum payload the log daemon accepts without truncation. - * @return LOGGER_ENTRY_MAX_PAYLOAD. - */ - private static native int logger_entry_max_payload_native(); - - /** - * Helper function for long messages. Uses the LineBreakBufferedWriter to break - * up long messages and stacktraces along newlines, but tries to write in large - * chunks. This is to avoid truncation. - * @hide - */ - public static int printlns(int bufID, int priority, String tag, String msg, - Throwable tr) { - ImmediateLogWriter logWriter = new ImmediateLogWriter(bufID, priority, tag); - // Acceptable buffer size. Get the native buffer size, subtract two zero terminators, - // and the length of the tag. - // Note: we implicitly accept possible truncation for Modified-UTF8 differences. It - // is too expensive to compute that ahead of time. - int bufferSize = PreloadHolder.LOGGER_ENTRY_MAX_PAYLOAD // Base. - - 2 // Two terminators. - - (tag != null ? tag.length() : 0) // Tag length. - - 32; // Some slack. - // At least assume you can print *some* characters (tag is not too large). - bufferSize = Math.max(bufferSize, 100); - - LineBreakBufferedWriter lbbw = new LineBreakBufferedWriter(logWriter, bufferSize); - - lbbw.println(msg); - - if (tr != null) { - // This is to reduce the amount of log spew that apps do in the non-error - // condition of the network being unavailable. - Throwable t = tr; - while (t != null) { - if (t instanceof UnknownHostException) { - break; - } - if (t instanceof DeadSystemException) { - lbbw.println("DeadSystemException: The system died; " - + "earlier logs will point to the root cause"); - break; - } - t = t.getCause(); - } - if (t == null) { - tr.printStackTrace(lbbw); - } - } - - lbbw.flush(); - - return logWriter.getWritten(); - } - - /** - * PreloadHelper class. Caches the LOGGER_ENTRY_MAX_PAYLOAD value to avoid - * a JNI call during logging. - */ - static class PreloadHolder { - public final static int LOGGER_ENTRY_MAX_PAYLOAD = - logger_entry_max_payload_native(); - } - - /** - * Helper class to write to the logcat. Different from LogWriter, this writes - * the whole given buffer and does not break along newlines. - */ - private static class ImmediateLogWriter extends Writer { - - private int bufID; - private int priority; - private String tag; - - private int written = 0; - - /** - * Create a writer that immediately writes to the log, using the given - * parameters. - */ - public ImmediateLogWriter(int bufID, int priority, String tag) { - this.bufID = bufID; - this.priority = priority; - this.tag = tag; - } - - public int getWritten() { - return written; - } - - @Override - public void write(char[] cbuf, int off, int len) { - // Note: using String here has a bit of overhead as a Java object is created, - // but using the char[] directly is not easier, as it needs to be translated - // to a C char[] for logging. - written += println_native(bufID, priority, tag, new String(cbuf, off, len)); - } - - @Override - public void flush() { - // Ignored. - } - - @Override - public void close() { - // Ignored. - } + /** @hide */ @SuppressWarnings("unused") + public static int println(int bufID, + int priority, String tag, String msg) { + return 0; } } diff --git a/android/util/StatsManager.java b/android/util/StatsManager.java new file mode 100644 index 00000000..55b33a61 --- /dev/null +++ b/android/util/StatsManager.java @@ -0,0 +1,134 @@ +/* + * Copyright 2017 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 android.util; + +import android.Manifest; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.os.IBinder; +import android.os.IStatsManager; +import android.os.RemoteException; +import android.os.ServiceManager; + +/** + * API for StatsD clients to send configurations and retrieve data. + * + * @hide + */ +@SystemApi +public final class StatsManager { + IStatsManager mService; + private static final String TAG = "StatsManager"; + + /** + * Constructor for StatsManagerClient. + * + * @hide + */ + public StatsManager() { + } + + /** + * Clients can send a configuration and simultaneously registers the name of a broadcast + * receiver that listens for when it should request data. + * + * @param configKey An arbitrary string that allows clients to track the configuration. + * @param config Wire-encoded StatsDConfig proto that specifies metrics (and all + * dependencies eg, conditions and matchers). + * @param pkg The package name to receive the broadcast. + * @param cls The name of the class that receives the broadcast. + * @return true if successful + */ + @RequiresPermission(Manifest.permission.DUMP) + public boolean addConfiguration(String configKey, byte[] config, String pkg, String cls) { + synchronized (this) { + try { + IStatsManager service = getIStatsManagerLocked(); + if (service == null) { + throw new RuntimeException("StatsD service connection lost"); + } + return service.addConfiguration(configKey, config, pkg, cls); + } catch (RemoteException e) { + Slog.d(TAG, "Failed to connect to statsd when getting data"); + return false; + } + } + } + + /** + * Remove a configuration from logging. + * + * @param configKey Configuration key to remove. + * @return true if successful + */ + @RequiresPermission(Manifest.permission.DUMP) + public boolean removeConfiguration(String configKey) { + synchronized (this) { + try { + IStatsManager service = getIStatsManagerLocked(); + if (service == null) { + throw new RuntimeException("StatsD service connection lost"); + } + return service.removeConfiguration(configKey); + } catch (RemoteException e) { + Slog.d(TAG, "Failed to connect to statsd when getting data"); + return false; + } + } + } + + /** + * Clients can request data with a binder call. + * + * @param configKey Configuration key to retrieve data from. + * @return Serialized ConfigMetricsReport proto. Returns null on failure. + */ + @RequiresPermission(Manifest.permission.DUMP) + public byte[] getData(String configKey) { + synchronized (this) { + try { + IStatsManager service = getIStatsManagerLocked(); + if (service == null) { + throw new RuntimeException("StatsD service connection lost"); + } + return service.getData(configKey); + } catch (RemoteException e) { + Slog.d(TAG, "Failed to connecto statsd when getting data"); + return null; + } + } + } + + private class StatsdDeathRecipient implements IBinder.DeathRecipient { + @Override + public void binderDied() { + synchronized (this) { + mService = null; + } + } + } + + private IStatsManager getIStatsManagerLocked() throws RemoteException { + if (mService != null) { + return mService; + } + mService = IStatsManager.Stub.asInterface(ServiceManager.getService("stats")); + if (mService != null) { + mService.asBinder().linkToDeath(new StatsdDeathRecipient(), 0); + } + return mService; + } +} diff --git a/android/util/TimeUtils.java b/android/util/TimeUtils.java index 2b03ed6c..cc4a0b60 100644 --- a/android/util/TimeUtils.java +++ b/android/util/TimeUtils.java @@ -340,6 +340,14 @@ public class TimeUtils { } /** @hide Just for debugging; not internationalized. */ + public static String formatDuration(long duration) { + synchronized (sFormatSync) { + int len = formatDurationLocked(duration, 0); + return new String(sFormatStr, 0, len); + } + } + + /** @hide Just for debugging; not internationalized. */ public static void formatDuration(long duration, PrintWriter pw) { formatDuration(duration, pw, 0); } diff --git a/android/util/apk/ApkSignatureSchemeV2Verifier.java b/android/util/apk/ApkSignatureSchemeV2Verifier.java index a9ccae11..18081234 100644 --- a/android/util/apk/ApkSignatureSchemeV2Verifier.java +++ b/android/util/apk/ApkSignatureSchemeV2Verifier.java @@ -16,9 +16,6 @@ package android.util.apk; -import android.system.ErrnoException; -import android.system.Os; -import android.system.OsConstants; import android.util.ArrayMap; import android.util.Pair; @@ -30,7 +27,6 @@ import java.math.BigInteger; import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; import java.nio.ByteOrder; -import java.nio.DirectByteBuffer; import java.security.DigestException; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; @@ -122,40 +118,6 @@ public class ApkSignatureSchemeV2Verifier { } /** - * APK Signature Scheme v2 block and additional information relevant to verifying the signatures - * contained in the block against the file. - */ - private static class SignatureInfo { - /** Contents of APK Signature Scheme v2 block. */ - private final ByteBuffer signatureBlock; - - /** Position of the APK Signing Block in the file. */ - private final long apkSigningBlockOffset; - - /** Position of the ZIP Central Directory in the file. */ - private final long centralDirOffset; - - /** Position of the ZIP End of Central Directory (EoCD) in the file. */ - private final long eocdOffset; - - /** Contents of ZIP End of Central Directory (EoCD) of the file. */ - private final ByteBuffer eocd; - - private SignatureInfo( - ByteBuffer signatureBlock, - long apkSigningBlockOffset, - long centralDirOffset, - long eocdOffset, - ByteBuffer eocd) { - this.signatureBlock = signatureBlock; - this.apkSigningBlockOffset = apkSigningBlockOffset; - this.centralDirOffset = centralDirOffset; - this.eocdOffset = eocdOffset; - this.eocd = eocd; - } - } - - /** * Returns the APK Signature Scheme v2 block contained in the provided APK file and the * additional information relevant for verifying the block against the file. * @@ -497,6 +459,7 @@ public class ApkSignatureSchemeV2Verifier { // TODO: Compute digests of chunks in parallel when beneficial. This requires some research // into how to parallelize (if at all) based on the capabilities of the hardware on which // this code is running and based on the size of input. + DataDigester digester = new MultipleDigestDataDigester(mds); int dataSourceIndex = 0; for (DataSource input : contents) { long inputOffset = 0; @@ -508,7 +471,7 @@ public class ApkSignatureSchemeV2Verifier { mds[i].update(chunkContentPrefix); } try { - input.feedIntoMessageDigests(mds, inputOffset, chunkSize); + input.feedIntoDataDigester(digester, inputOffset, chunkSize); } catch (IOException e) { throw new DigestException( "Failed to digest chunk #" + chunkIndex + " of section #" @@ -967,155 +930,26 @@ public class ApkSignatureSchemeV2Verifier { } /** - * Source of data to be digested. + * {@link DataDigester} that updates multiple {@link MessageDigest}s whenever data is feeded. */ - private static interface DataSource { - - /** - * Returns the size (in bytes) of the data offered by this source. - */ - long size(); - - /** - * Feeds the specified region of this source's data into the provided digests. Each digest - * instance gets the same data. - * - * @param offset offset of the region inside this data source. - * @param size size (in bytes) of the region. - */ - void feedIntoMessageDigests(MessageDigest[] mds, long offset, int size) throws IOException; - } + private static class MultipleDigestDataDigester implements DataDigester { + private final MessageDigest[] mMds; - /** - * {@link DataSource} which provides data from a file descriptor by memory-mapping the sections - * of the file requested by - * {@link DataSource#feedIntoMessageDigests(MessageDigest[], long, int) feedIntoMessageDigests}. - */ - private static final class MemoryMappedFileDataSource implements DataSource { - private static final long MEMORY_PAGE_SIZE_BYTES = Os.sysconf(OsConstants._SC_PAGESIZE); - - private final FileDescriptor mFd; - private final long mFilePosition; - private final long mSize; - - /** - * Constructs a new {@code MemoryMappedFileDataSource} for the specified region of the file. - * - * @param position start position of the region in the file. - * @param size size (in bytes) of the region. - */ - public MemoryMappedFileDataSource(FileDescriptor fd, long position, long size) { - mFd = fd; - mFilePosition = position; - mSize = size; + MultipleDigestDataDigester(MessageDigest[] mds) { + mMds = mds; } @Override - public long size() { - return mSize; - } - - @Override - public void feedIntoMessageDigests( - MessageDigest[] mds, long offset, int size) throws IOException { - // IMPLEMENTATION NOTE: After a lot of experimentation, the implementation of this - // method was settled on a straightforward mmap with prefaulting. - // - // This method is not using FileChannel.map API because that API does not offset a way - // to "prefault" the resulting memory pages. Without prefaulting, performance is about - // 10% slower on small to medium APKs, but is significantly worse for APKs in 500+ MB - // range. FileChannel.load (which currently uses madvise) doesn't help. Finally, - // invoking madvise (MADV_SEQUENTIAL) after mmap with prefaulting wastes quite a bit of - // time, which is not compensated for by faster reads. - - // We mmap the smallest region of the file containing the requested data. mmap requires - // that the start offset in the file must be a multiple of memory page size. We thus may - // need to mmap from an offset less than the requested offset. - long filePosition = mFilePosition + offset; - long mmapFilePosition = - (filePosition / MEMORY_PAGE_SIZE_BYTES) * MEMORY_PAGE_SIZE_BYTES; - int dataStartOffsetInMmapRegion = (int) (filePosition - mmapFilePosition); - long mmapRegionSize = size + dataStartOffsetInMmapRegion; - long mmapPtr = 0; - try { - mmapPtr = Os.mmap( - 0, // let the OS choose the start address of the region in memory - mmapRegionSize, - OsConstants.PROT_READ, - OsConstants.MAP_SHARED | OsConstants.MAP_POPULATE, // "prefault" all pages - mFd, - mmapFilePosition); - // Feeding a memory region into MessageDigest requires the region to be represented - // as a direct ByteBuffer. - ByteBuffer buf = new DirectByteBuffer( - size, - mmapPtr + dataStartOffsetInMmapRegion, - mFd, // not really needed, but just in case - null, // no need to clean up -- it's taken care of by the finally block - true // read only buffer - ); - for (MessageDigest md : mds) { - buf.position(0); - md.update(buf); - } - } catch (ErrnoException e) { - throw new IOException("Failed to mmap " + mmapRegionSize + " bytes", e); - } finally { - if (mmapPtr != 0) { - try { - Os.munmap(mmapPtr, mmapRegionSize); - } catch (ErrnoException ignored) {} - } + public void consume(ByteBuffer buffer) { + buffer = buffer.slice(); + for (MessageDigest md : mMds) { + buffer.position(0); + md.update(buffer); } } - } - - /** - * {@link DataSource} which provides data from a {@link ByteBuffer}. - */ - private static final class ByteBufferDataSource implements DataSource { - /** - * Underlying buffer. The data is stored between position 0 and the buffer's capacity. - * The buffer's position is 0 and limit is equal to capacity. - */ - private final ByteBuffer mBuf; - - public ByteBufferDataSource(ByteBuffer buf) { - // Defensive copy, to avoid changes to mBuf being visible in buf. - mBuf = buf.slice(); - } @Override - public long size() { - return mBuf.capacity(); - } - - @Override - public void feedIntoMessageDigests( - MessageDigest[] mds, long offset, int size) throws IOException { - // There's no way to tell MessageDigest to read data from ByteBuffer from a position - // other than the buffer's current position. We thus need to change the buffer's - // position to match the requested offset. - // - // In the future, it may be necessary to compute digests of multiple regions in - // parallel. Given that digest computation is a slow operation, we enable multiple - // such requests to be fulfilled by this instance. This is achieved by serially - // creating a new ByteBuffer corresponding to the requested data range and then, - // potentially concurrently, feeding these buffers into MessageDigest instances. - ByteBuffer region; - synchronized (mBuf) { - mBuf.position((int) offset); - mBuf.limit((int) offset + size); - region = mBuf.slice(); - } - - for (MessageDigest md : mds) { - // Need to reset position to 0 at the start of each iteration because - // MessageDigest.update below sets it to the buffer's limit. - region.position(0); - md.update(region); - } - } + public void finish() {} } /** diff --git a/android/util/apk/ApkVerityBuilder.java b/android/util/apk/ApkVerityBuilder.java new file mode 100644 index 00000000..7412ef41 --- /dev/null +++ b/android/util/apk/ApkVerityBuilder.java @@ -0,0 +1,351 @@ +/* + * Copyright (C) 2017 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 android.util.apk; + +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.security.DigestException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; + +/** + * ApkVerityBuilder builds the APK verity tree and the verity header, which will be used by the + * kernel to verity the APK content on access. + * + * <p>Unlike a regular Merkle tree, APK verity tree does not cover the content fully. Due to + * the existing APK format, it has to skip APK Signing Block and also has some special treatment for + * the "Central Directory offset" field of ZIP End of Central Directory. + * + * @hide + */ +abstract class ApkVerityBuilder { + private ApkVerityBuilder() {} + + private static final int CHUNK_SIZE_BYTES = 4096; // Typical Linux block size + private static final int DIGEST_SIZE_BYTES = 32; // SHA-256 size + private static final int FSVERITY_HEADER_SIZE_BYTES = 64; + private static final int ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_SIZE = 4; + private static final int ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET = 16; + private static final String JCA_DIGEST_ALGORITHM = "SHA-256"; + private static final byte[] DEFAULT_SALT = new byte[8]; + + static class ApkVerityResult { + public final ByteBuffer fsverityData; + public final byte[] rootHash; + + ApkVerityResult(ByteBuffer fsverityData, byte[] rootHash) { + this.fsverityData = fsverityData; + this.rootHash = rootHash; + } + } + + /** + * Generates fsverity metadata and the Merkle tree into the {@link ByteBuffer} created by the + * {@link ByteBufferFactory}. The bytes layout in the buffer will be used by the kernel and is + * ready to be appended to the target file to set up fsverity. For fsverity to work, this data + * must be placed at the next page boundary, and the caller must add additional padding in that + * case. + * + * @return ApkVerityResult containing the fsverity data and the root hash of the Merkle tree. + */ + static ApkVerityResult generateApkVerity(RandomAccessFile apk, + SignatureInfo signatureInfo, ByteBufferFactory bufferFactory) + throws IOException, SecurityException, NoSuchAlgorithmException, DigestException { + assertSigningBlockAlignedAndHasFullPages(signatureInfo); + + long signingBlockSize = + signatureInfo.centralDirOffset - signatureInfo.apkSigningBlockOffset; + long dataSize = apk.length() - signingBlockSize - ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_SIZE; + int[] levelOffset = calculateVerityLevelOffset(dataSize); + ByteBuffer output = bufferFactory.create( + CHUNK_SIZE_BYTES + // fsverity header + extensions + padding + levelOffset[levelOffset.length - 1] + // Merkle tree size + FSVERITY_HEADER_SIZE_BYTES); // second fsverity header (verbatim copy) + + // Start generating the tree from the block boundary as the kernel will expect. + ByteBuffer treeOutput = slice(output, CHUNK_SIZE_BYTES, + output.limit() - FSVERITY_HEADER_SIZE_BYTES); + byte[] rootHash = generateApkVerityTree(apk, signatureInfo, DEFAULT_SALT, levelOffset, + treeOutput); + + ByteBuffer integrityHeader = generateFsverityHeader(apk.length(), DEFAULT_SALT); + output.put(integrityHeader); + output.put(generateFsverityExtensions()); + + integrityHeader.rewind(); + output.put(integrityHeader); + output.rewind(); + return new ApkVerityResult(output, rootHash); + } + + /** + * A helper class to consume and digest data by block continuously, and write into a buffer. + */ + private static class BufferedDigester implements DataDigester { + /** Amount of the data to digest in each cycle before writting out the digest. */ + private static final int BUFFER_SIZE = CHUNK_SIZE_BYTES; + + /** + * Amount of data the {@link MessageDigest} has consumed since the last reset. This must be + * always less than BUFFER_SIZE since {@link MessageDigest} is reset whenever it has + * consumed BUFFER_SIZE of data. + */ + private int mBytesDigestedSinceReset; + + /** The final output {@link ByteBuffer} to write the digest to sequentially. */ + private final ByteBuffer mOutput; + + private final MessageDigest mMd; + private final byte[] mDigestBuffer = new byte[DIGEST_SIZE_BYTES]; + private final byte[] mSalt; + + private BufferedDigester(byte[] salt, ByteBuffer output) throws NoSuchAlgorithmException { + mSalt = salt; + mOutput = output.slice(); + mMd = MessageDigest.getInstance(JCA_DIGEST_ALGORITHM); + mMd.update(mSalt); + mBytesDigestedSinceReset = 0; + } + + /** + * Consumes and digests data up to BUFFER_SIZE (may continue from the previous remaining), + * then writes the final digest to the output buffer. Repeat until all data are consumed. + * If the last consumption is not enough for BUFFER_SIZE, the state will stay and future + * consumption will continuous from there. + */ + @Override + public void consume(ByteBuffer buffer) throws DigestException { + int offset = buffer.position(); + int remaining = buffer.remaining(); + while (remaining > 0) { + int allowance = (int) Math.min(remaining, BUFFER_SIZE - mBytesDigestedSinceReset); + // Optimization: set the buffer limit to avoid allocating a new ByteBuffer object. + buffer.limit(buffer.position() + allowance); + mMd.update(buffer); + offset += allowance; + remaining -= allowance; + mBytesDigestedSinceReset += allowance; + + if (mBytesDigestedSinceReset == BUFFER_SIZE) { + mMd.digest(mDigestBuffer, 0, mDigestBuffer.length); + mOutput.put(mDigestBuffer); + // After digest, MessageDigest resets automatically, so no need to reset again. + mMd.update(mSalt); + mBytesDigestedSinceReset = 0; + } + } + } + + /** Finish the current digestion if any. */ + @Override + public void finish() throws DigestException { + if (mBytesDigestedSinceReset == 0) { + return; + } + mMd.digest(mDigestBuffer, 0, mDigestBuffer.length); + mOutput.put(mDigestBuffer); + } + + private void fillUpLastOutputChunk() { + int extra = (int) (BUFFER_SIZE - mOutput.position() % BUFFER_SIZE); + if (extra == 0) { + return; + } + mOutput.put(ByteBuffer.allocate(extra)); + } + } + + /** + * Digest the source by chunk in the given range. If the last chunk is not a full chunk, + * digest the remaining. + */ + private static void consumeByChunk(DataDigester digester, DataSource source, int chunkSize) + throws IOException, DigestException { + long inputRemaining = source.size(); + long inputOffset = 0; + while (inputRemaining > 0) { + int size = (int) Math.min(inputRemaining, chunkSize); + source.feedIntoDataDigester(digester, inputOffset, size); + inputOffset += size; + inputRemaining -= size; + } + } + + // Rationale: 1) 1 MB should fit in memory space on all devices. 2) It is not too granular + // thus the syscall overhead is not too big. + private static final int MMAP_REGION_SIZE_BYTES = 1024 * 1024; + + private static void generateApkVerityDigestAtLeafLevel(RandomAccessFile apk, + SignatureInfo signatureInfo, byte[] salt, ByteBuffer output) + throws IOException, NoSuchAlgorithmException, DigestException { + BufferedDigester digester = new BufferedDigester(salt, output); + + // 1. Digest from the beginning of the file, until APK Signing Block is reached. + consumeByChunk(digester, + new MemoryMappedFileDataSource(apk.getFD(), 0, signatureInfo.apkSigningBlockOffset), + MMAP_REGION_SIZE_BYTES); + + // 2. Skip APK Signing Block and continue digesting, until the Central Directory offset + // field in EoCD is reached. + long eocdCdOffsetFieldPosition = + signatureInfo.eocdOffset + ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET; + consumeByChunk(digester, + new MemoryMappedFileDataSource(apk.getFD(), signatureInfo.centralDirOffset, + eocdCdOffsetFieldPosition - signatureInfo.centralDirOffset), + MMAP_REGION_SIZE_BYTES); + + // 3. Fill up the rest of buffer with 0s. + ByteBuffer alternativeCentralDirOffset = ByteBuffer.allocate( + ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_SIZE).order(ByteOrder.LITTLE_ENDIAN); + alternativeCentralDirOffset.putInt(Math.toIntExact(signatureInfo.apkSigningBlockOffset)); + alternativeCentralDirOffset.flip(); + digester.consume(alternativeCentralDirOffset); + + // 4. Read from end of the Central Directory offset field in EoCD to the end of the file. + long offsetAfterEocdCdOffsetField = + eocdCdOffsetFieldPosition + ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_SIZE; + consumeByChunk(digester, + new MemoryMappedFileDataSource(apk.getFD(), offsetAfterEocdCdOffsetField, + apk.length() - offsetAfterEocdCdOffsetField), + MMAP_REGION_SIZE_BYTES); + digester.finish(); + + // 5. Fill up the rest of buffer with 0s. + digester.fillUpLastOutputChunk(); + } + + private static byte[] generateApkVerityTree(RandomAccessFile apk, SignatureInfo signatureInfo, + byte[] salt, int[] levelOffset, ByteBuffer output) + throws IOException, NoSuchAlgorithmException, DigestException { + // 1. Digest the apk to generate the leaf level hashes. + generateApkVerityDigestAtLeafLevel(apk, signatureInfo, salt, slice(output, + levelOffset[levelOffset.length - 2], levelOffset[levelOffset.length - 1])); + + // 2. Digest the lower level hashes bottom up. + for (int level = levelOffset.length - 3; level >= 0; level--) { + ByteBuffer inputBuffer = slice(output, levelOffset[level + 1], levelOffset[level + 2]); + ByteBuffer outputBuffer = slice(output, levelOffset[level], levelOffset[level + 1]); + + DataSource source = new ByteBufferDataSource(inputBuffer); + BufferedDigester digester = new BufferedDigester(salt, outputBuffer); + consumeByChunk(digester, source, CHUNK_SIZE_BYTES); + digester.finish(); + + digester.fillUpLastOutputChunk(); + } + + // 3. Digest the first block (i.e. first level) to generate the root hash. + byte[] rootHash = new byte[DIGEST_SIZE_BYTES]; + BufferedDigester digester = new BufferedDigester(salt, ByteBuffer.wrap(rootHash)); + digester.consume(slice(output, 0, CHUNK_SIZE_BYTES)); + digester.finish(); + return rootHash; + } + + private static ByteBuffer generateFsverityHeader(long fileSize, byte[] salt) { + if (salt.length != 8) { + throw new IllegalArgumentException("salt is not 8 bytes long"); + } + + ByteBuffer buffer = ByteBuffer.allocate(FSVERITY_HEADER_SIZE_BYTES); + buffer.order(ByteOrder.LITTLE_ENDIAN); + + // TODO(b/30972906): insert a reference when there is a public one. + buffer.put("TrueBrew".getBytes()); // magic + buffer.put((byte) 1); // major version + buffer.put((byte) 0); // minor version + buffer.put((byte) 12); // log2(block-size) == log2(4096) + buffer.put((byte) 7); // log2(leaves-per-node) == log2(block-size / digest-size) + // == log2(4096 / 32) + buffer.putShort((short) 1); // meta algorithm, 1: SHA-256 FIXME finalize constant + buffer.putShort((short) 1); // data algorithm, 1: SHA-256 FIXME finalize constant + buffer.putInt(0x1); // flags, 0x1: has extension, FIXME also hide it + buffer.putInt(0); // reserved + buffer.putLong(fileSize); // original i_size + buffer.put(salt); // salt (8 bytes) + + // TODO(b/30972906): Add extension. + + buffer.rewind(); + return buffer; + } + + private static ByteBuffer generateFsverityExtensions() { + return ByteBuffer.allocate(64); // TODO(b/30972906): implement this. + } + + /** + * Returns an array of summed area table of level size in the verity tree. In other words, the + * returned array is offset of each level in the verity tree file format, plus an additional + * offset of the next non-existing level (i.e. end of the last level + 1). Thus the array size + * is level + 1. Thus, the returned array is guarantee to have at least 2 elements. + */ + private static int[] calculateVerityLevelOffset(long fileSize) { + ArrayList<Long> levelSize = new ArrayList<>(); + while (true) { + long levelDigestSize = divideRoundup(fileSize, CHUNK_SIZE_BYTES) * DIGEST_SIZE_BYTES; + long chunksSize = CHUNK_SIZE_BYTES * divideRoundup(levelDigestSize, CHUNK_SIZE_BYTES); + levelSize.add(chunksSize); + if (levelDigestSize <= CHUNK_SIZE_BYTES) { + break; + } + fileSize = levelDigestSize; + } + + // Reverse and convert to summed area table. + int[] levelOffset = new int[levelSize.size() + 1]; + levelOffset[0] = 0; + for (int i = 0; i < levelSize.size(); i++) { + // We don't support verity tree if it is larger then Integer.MAX_VALUE. + levelOffset[i + 1] = levelOffset[i] + + Math.toIntExact(levelSize.get(levelSize.size() - i - 1)); + } + return levelOffset; + } + + private static void assertSigningBlockAlignedAndHasFullPages(SignatureInfo signatureInfo) { + if (signatureInfo.apkSigningBlockOffset % CHUNK_SIZE_BYTES != 0) { + throw new IllegalArgumentException( + "APK Signing Block does not start at the page boundary: " + + signatureInfo.apkSigningBlockOffset); + } + + if ((signatureInfo.centralDirOffset - signatureInfo.apkSigningBlockOffset) + % CHUNK_SIZE_BYTES != 0) { + throw new IllegalArgumentException( + "Size of APK Signing Block is not a multiple of 4096: " + + (signatureInfo.centralDirOffset - signatureInfo.apkSigningBlockOffset)); + } + } + + /** Returns a slice of the buffer which shares content with the provided buffer. */ + private static ByteBuffer slice(ByteBuffer buffer, int begin, int end) { + ByteBuffer b = buffer.duplicate(); + b.position(0); // to ensure position <= limit invariant. + b.limit(end); + b.position(begin); + return b.slice(); + } + + /** Divides a number and round up to the closest integer. */ + private static long divideRoundup(long dividend, long divisor) { + return (dividend + divisor - 1) / divisor; + } +} diff --git a/android/util/apk/ByteBufferDataSource.java b/android/util/apk/ByteBufferDataSource.java new file mode 100644 index 00000000..3976568a --- /dev/null +++ b/android/util/apk/ByteBufferDataSource.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2017 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 android.util.apk; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.security.DigestException; + +/** + * {@link DataSource} which provides data from a {@link ByteBuffer}. + */ +class ByteBufferDataSource implements DataSource { + /** + * Underlying buffer. The data is stored between position 0 and the buffer's capacity. + * The buffer's position is 0 and limit is equal to capacity. + */ + private final ByteBuffer mBuf; + + ByteBufferDataSource(ByteBuffer buf) { + // Defensive copy, to avoid changes to mBuf being visible in buf, and to ensure position is + // 0 and limit == capacity. + mBuf = buf.slice(); + } + + @Override + public long size() { + return mBuf.capacity(); + } + + @Override + public void feedIntoDataDigester(DataDigester md, long offset, int size) + throws IOException, DigestException { + // There's no way to tell MessageDigest to read data from ByteBuffer from a position + // other than the buffer's current position. We thus need to change the buffer's + // position to match the requested offset. + // + // In the future, it may be necessary to compute digests of multiple regions in + // parallel. Given that digest computation is a slow operation, we enable multiple + // such requests to be fulfilled by this instance. This is achieved by serially + // creating a new ByteBuffer corresponding to the requested data range and then, + // potentially concurrently, feeding these buffers into MessageDigest instances. + ByteBuffer region; + synchronized (mBuf) { + mBuf.position(0); + mBuf.limit((int) offset + size); + mBuf.position((int) offset); + region = mBuf.slice(); + } + + md.consume(region); + } +} diff --git a/android/util/apk/ByteBufferFactory.java b/android/util/apk/ByteBufferFactory.java new file mode 100644 index 00000000..7a998822 --- /dev/null +++ b/android/util/apk/ByteBufferFactory.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2017 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 android.util.apk; + +import java.nio.ByteBuffer; + +/** + * Provider of {@link ByteBuffer} instances. + * @hide + */ +public interface ByteBufferFactory { + /** Initiates a {@link ByteBuffer} with the given size. */ + ByteBuffer create(int capacity); +} diff --git a/android/util/apk/DataDigester.java b/android/util/apk/DataDigester.java new file mode 100644 index 00000000..278be803 --- /dev/null +++ b/android/util/apk/DataDigester.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2017 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 android.util.apk; + +import java.nio.ByteBuffer; +import java.security.DigestException; + +interface DataDigester { + /** Consumes the {@link ByteBuffer}. */ + void consume(ByteBuffer buffer) throws DigestException; + + /** Finishes the digestion. Must be called after the last {@link #consume(ByteBuffer)}. */ + void finish() throws DigestException; +} diff --git a/android/util/apk/DataSource.java b/android/util/apk/DataSource.java new file mode 100644 index 00000000..82f3800a --- /dev/null +++ b/android/util/apk/DataSource.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2017 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 android.util.apk; + +import java.io.IOException; +import java.security.DigestException; + +/** Source of data to be digested. */ +interface DataSource { + + /** + * Returns the size (in bytes) of the data offered by this source. + */ + long size(); + + /** + * Feeds the specified region of this source's data into the provided digester. + * + * @param offset offset of the region inside this data source. + * @param size size (in bytes) of the region. + */ + void feedIntoDataDigester(DataDigester md, long offset, int size) + throws IOException, DigestException; +} diff --git a/android/util/apk/MemoryMappedFileDataSource.java b/android/util/apk/MemoryMappedFileDataSource.java new file mode 100644 index 00000000..8d2b1e32 --- /dev/null +++ b/android/util/apk/MemoryMappedFileDataSource.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2017 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 android.util.apk; + +import android.system.ErrnoException; +import android.system.Os; +import android.system.OsConstants; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.DirectByteBuffer; +import java.security.DigestException; + +/** + * {@link DataSource} which provides data from a file descriptor by memory-mapping the sections + * of the file. + */ +class MemoryMappedFileDataSource implements DataSource { + private static final long MEMORY_PAGE_SIZE_BYTES = Os.sysconf(OsConstants._SC_PAGESIZE); + + private final FileDescriptor mFd; + private final long mFilePosition; + private final long mSize; + + /** + * Constructs a new {@code MemoryMappedFileDataSource} for the specified region of the file. + * + * @param position start position of the region in the file. + * @param size size (in bytes) of the region. + */ + MemoryMappedFileDataSource(FileDescriptor fd, long position, long size) { + mFd = fd; + mFilePosition = position; + mSize = size; + } + + @Override + public long size() { + return mSize; + } + + @Override + public void feedIntoDataDigester(DataDigester md, long offset, int size) + throws IOException, DigestException { + // IMPLEMENTATION NOTE: After a lot of experimentation, the implementation of this + // method was settled on a straightforward mmap with prefaulting. + // + // This method is not using FileChannel.map API because that API does not offset a way + // to "prefault" the resulting memory pages. Without prefaulting, performance is about + // 10% slower on small to medium APKs, but is significantly worse for APKs in 500+ MB + // range. FileChannel.load (which currently uses madvise) doesn't help. Finally, + // invoking madvise (MADV_SEQUENTIAL) after mmap with prefaulting wastes quite a bit of + // time, which is not compensated for by faster reads. + + // We mmap the smallest region of the file containing the requested data. mmap requires + // that the start offset in the file must be a multiple of memory page size. We thus may + // need to mmap from an offset less than the requested offset. + long filePosition = mFilePosition + offset; + long mmapFilePosition = + (filePosition / MEMORY_PAGE_SIZE_BYTES) * MEMORY_PAGE_SIZE_BYTES; + int dataStartOffsetInMmapRegion = (int) (filePosition - mmapFilePosition); + long mmapRegionSize = size + dataStartOffsetInMmapRegion; + long mmapPtr = 0; + try { + mmapPtr = Os.mmap( + 0, // let the OS choose the start address of the region in memory + mmapRegionSize, + OsConstants.PROT_READ, + OsConstants.MAP_SHARED | OsConstants.MAP_POPULATE, // "prefault" all pages + mFd, + mmapFilePosition); + ByteBuffer buf = new DirectByteBuffer( + size, + mmapPtr + dataStartOffsetInMmapRegion, + mFd, // not really needed, but just in case + null, // no need to clean up -- it's taken care of by the finally block + true // read only buffer + ); + md.consume(buf); + } catch (ErrnoException e) { + throw new IOException("Failed to mmap " + mmapRegionSize + " bytes", e); + } finally { + if (mmapPtr != 0) { + try { + Os.munmap(mmapPtr, mmapRegionSize); + } catch (ErrnoException ignored) { } + } + } + } +} diff --git a/android/util/apk/SignatureInfo.java b/android/util/apk/SignatureInfo.java new file mode 100644 index 00000000..8e1233af --- /dev/null +++ b/android/util/apk/SignatureInfo.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2017 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 android.util.apk; + +import java.nio.ByteBuffer; + +/** + * APK Signature Scheme v2 block and additional information relevant to verifying the signatures + * contained in the block against the file. + */ +class SignatureInfo { + /** Contents of APK Signature Scheme v2 block. */ + public final ByteBuffer signatureBlock; + + /** Position of the APK Signing Block in the file. */ + public final long apkSigningBlockOffset; + + /** Position of the ZIP Central Directory in the file. */ + public final long centralDirOffset; + + /** Position of the ZIP End of Central Directory (EoCD) in the file. */ + public final long eocdOffset; + + /** Contents of ZIP End of Central Directory (EoCD) of the file. */ + public final ByteBuffer eocd; + + SignatureInfo(ByteBuffer signatureBlock, long apkSigningBlockOffset, long centralDirOffset, + long eocdOffset, ByteBuffer eocd) { + this.signatureBlock = signatureBlock; + this.apkSigningBlockOffset = apkSigningBlockOffset; + this.centralDirOffset = centralDirOffset; + this.eocdOffset = eocdOffset; + this.eocd = eocd; + } +} diff --git a/android/util/proto/ProtoOutputStream.java b/android/util/proto/ProtoOutputStream.java index 43a97897..a94806a0 100644 --- a/android/util/proto/ProtoOutputStream.java +++ b/android/util/proto/ProtoOutputStream.java @@ -127,42 +127,48 @@ public final class ProtoOutputStream { public static final long FIELD_TYPE_UNKNOWN = 0; + /** + * The types are copied from external/protobuf/src/google/protobuf/descriptor.h directly, + * so no extra mapping needs to be maintained in this case. + */ public static final long FIELD_TYPE_DOUBLE = 1L << FIELD_TYPE_SHIFT; public static final long FIELD_TYPE_FLOAT = 2L << FIELD_TYPE_SHIFT; - public static final long FIELD_TYPE_INT32 = 3L << FIELD_TYPE_SHIFT; - public static final long FIELD_TYPE_INT64 = 4L << FIELD_TYPE_SHIFT; - public static final long FIELD_TYPE_UINT32 = 5L << FIELD_TYPE_SHIFT; - public static final long FIELD_TYPE_UINT64 = 6L << FIELD_TYPE_SHIFT; - public static final long FIELD_TYPE_SINT32 = 7L << FIELD_TYPE_SHIFT; - public static final long FIELD_TYPE_SINT64 = 8L << FIELD_TYPE_SHIFT; - public static final long FIELD_TYPE_FIXED32 = 9L << FIELD_TYPE_SHIFT; - public static final long FIELD_TYPE_FIXED64 = 10L << FIELD_TYPE_SHIFT; - public static final long FIELD_TYPE_SFIXED32 = 11L << FIELD_TYPE_SHIFT; - public static final long FIELD_TYPE_SFIXED64 = 12L << FIELD_TYPE_SHIFT; - public static final long FIELD_TYPE_BOOL = 13L << FIELD_TYPE_SHIFT; - public static final long FIELD_TYPE_STRING = 14L << FIELD_TYPE_SHIFT; - public static final long FIELD_TYPE_BYTES = 15L << FIELD_TYPE_SHIFT; - public static final long FIELD_TYPE_ENUM = 16L << FIELD_TYPE_SHIFT; - public static final long FIELD_TYPE_OBJECT = 17L << FIELD_TYPE_SHIFT; + public static final long FIELD_TYPE_INT64 = 3L << FIELD_TYPE_SHIFT; + public static final long FIELD_TYPE_UINT64 = 4L << FIELD_TYPE_SHIFT; + public static final long FIELD_TYPE_INT32 = 5L << FIELD_TYPE_SHIFT; + public static final long FIELD_TYPE_FIXED64 = 6L << FIELD_TYPE_SHIFT; + public static final long FIELD_TYPE_FIXED32 = 7L << FIELD_TYPE_SHIFT; + public static final long FIELD_TYPE_BOOL = 8L << FIELD_TYPE_SHIFT; + public static final long FIELD_TYPE_STRING = 9L << FIELD_TYPE_SHIFT; +// public static final long FIELD_TYPE_GROUP = 10L << FIELD_TYPE_SHIFT; // Deprecated. + public static final long FIELD_TYPE_MESSAGE = 11L << FIELD_TYPE_SHIFT; + public static final long FIELD_TYPE_BYTES = 12L << FIELD_TYPE_SHIFT; + public static final long FIELD_TYPE_UINT32 = 13L << FIELD_TYPE_SHIFT; + public static final long FIELD_TYPE_ENUM = 14L << FIELD_TYPE_SHIFT; + public static final long FIELD_TYPE_SFIXED32 = 15L << FIELD_TYPE_SHIFT; + public static final long FIELD_TYPE_SFIXED64 = 16L << FIELD_TYPE_SHIFT; + public static final long FIELD_TYPE_SINT32 = 17L << FIELD_TYPE_SHIFT; + public static final long FIELD_TYPE_SINT64 = 18L << FIELD_TYPE_SHIFT; private static final String[] FIELD_TYPE_NAMES = new String[] { "Double", "Float", - "Int32", "Int64", - "UInt32", "UInt64", - "SInt32", - "SInt64", - "Fixed32", + "Int32", "Fixed64", - "SFixed32", - "SFixed64", + "Fixed32", "Bool", "String", + "Group", // This field is deprecated but reserved here for indexing. + "Message", "Bytes", + "UInt32", "Enum", - "Object", + "SFixed32", + "SFixed64", + "SInt32", + "SInt64", }; // @@ -867,21 +873,21 @@ public final class ProtoOutputStream { assertNotCompacted(); final int id = (int)fieldId; - switch ((int)((fieldId & (FIELD_TYPE_MASK | FIELD_COUNT_MASK)) >> FIELD_TYPE_SHIFT)) { + switch ((int) ((fieldId & (FIELD_TYPE_MASK | FIELD_COUNT_MASK)) >> FIELD_TYPE_SHIFT)) { // bytes - case (int)((FIELD_TYPE_BYTES | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT): + case (int) ((FIELD_TYPE_BYTES | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT): writeBytesImpl(id, val); break; - case (int)((FIELD_TYPE_BYTES | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT): - case (int)((FIELD_TYPE_BYTES | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT): + case (int) ((FIELD_TYPE_BYTES | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT): + case (int) ((FIELD_TYPE_BYTES | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT): writeRepeatedBytesImpl(id, val); break; // Object - case (int)((FIELD_TYPE_OBJECT | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT): + case (int) ((FIELD_TYPE_MESSAGE | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT): writeObjectImpl(id, val); break; - case (int)((FIELD_TYPE_OBJECT | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT): - case (int)((FIELD_TYPE_OBJECT | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT): + case (int) ((FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT): + case (int) ((FIELD_TYPE_MESSAGE | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT): writeRepeatedObjectImpl(id, val); break; // nothing else allowed @@ -899,7 +905,7 @@ public final class ProtoOutputStream { assertNotCompacted(); final int id = (int)fieldId; - if ((fieldId & FIELD_TYPE_MASK) == FIELD_TYPE_OBJECT) { + if ((fieldId & FIELD_TYPE_MASK) == FIELD_TYPE_MESSAGE) { final long count = fieldId & FIELD_COUNT_MASK; if (count == FIELD_COUNT_SINGLE) { return startObjectImpl(id, false); @@ -2091,7 +2097,7 @@ public final class ProtoOutputStream { @Deprecated public long startObject(long fieldId) { assertNotCompacted(); - final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_OBJECT); + final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_MESSAGE); return startObjectImpl(id, false); } @@ -2119,7 +2125,7 @@ public final class ProtoOutputStream { @Deprecated public long startRepeatedObject(long fieldId) { assertNotCompacted(); - final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_OBJECT); + final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_MESSAGE); return startObjectImpl(id, true); } @@ -2217,7 +2223,7 @@ public final class ProtoOutputStream { @Deprecated public void writeObject(long fieldId, byte[] value) { assertNotCompacted(); - final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_OBJECT); + final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_MESSAGE); writeObjectImpl(id, value); } @@ -2237,7 +2243,7 @@ public final class ProtoOutputStream { @Deprecated public void writeRepeatedObject(long fieldId, byte[] value) { assertNotCompacted(); - final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_OBJECT); + final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_MESSAGE); writeRepeatedObjectImpl(id, value); } @@ -2296,7 +2302,7 @@ public final class ProtoOutputStream { final String typeString = getFieldTypeString(fieldType); if (typeString != null && countString != null) { final StringBuilder sb = new StringBuilder(); - if (expectedType == FIELD_TYPE_OBJECT) { + if (expectedType == FIELD_TYPE_MESSAGE) { sb.append("start"); } else { sb.append("write"); @@ -2306,7 +2312,7 @@ public final class ProtoOutputStream { sb.append(" called for field "); sb.append((int)fieldId); sb.append(" which should be used with "); - if (fieldType == FIELD_TYPE_OBJECT) { + if (fieldType == FIELD_TYPE_MESSAGE) { sb.append("start"); } else { sb.append("write"); @@ -2321,7 +2327,7 @@ public final class ProtoOutputStream { throw new IllegalArgumentException(sb.toString()); } else { final StringBuilder sb = new StringBuilder(); - if (expectedType == FIELD_TYPE_OBJECT) { + if (expectedType == FIELD_TYPE_MESSAGE) { sb.append("start"); } else { sb.append("write"); |