Implement range-based pin list
This extends the original pin list generation to support specifying a
range within a file. If any part of a file is pinned, its local file
header in the APK file is pinned as well.
Test: unzip -q -c signed.apk pinlist.meta | od --endian=big -w8 -t d4 -
Bug: 136040313
Bug: 135953430
Change-Id: I2f6383a47effee4095055ad1f12e5cc701c19f42
diff --git a/src/main/java/com/android/apksig/ApkSigner.java b/src/main/java/com/android/apksig/ApkSigner.java
index 88f2617..e3870e7 100644
--- a/src/main/java/com/android/apksig/ApkSigner.java
+++ b/src/main/java/com/android/apksig/ApkSigner.java
@@ -240,7 +240,8 @@
List<CentralDirectoryRecord> inputCdRecords =
parseZipCentralDirectory(inputCd, inputZipSections);
- List<Pattern> pinPatterns = extractPinPatterns(inputCdRecords, inputApkLfhSection);
+ List<Hints.PatternWithRange> pinPatterns = extractPinPatterns(
+ inputCdRecords, inputApkLfhSection);
List<Hints.ByteRange> pinByteRanges = pinPatterns == null ? null : new ArrayList<>();
// Step 3. Obtain a signer engine instance
@@ -371,28 +372,33 @@
// Output entry's Local File Header + data
long outputLocalFileHeaderOffset = outputOffset;
- long outputLocalFileRecordSize =
+ OutputSizeAndDataOffset outputLfrResult =
outputInputJarEntryLfhRecordPreservingDataAlignment(
inputApkLfhSection,
inputLocalFileRecord,
outputApkOut,
outputLocalFileHeaderOffset);
- outputOffset += outputLocalFileRecordSize;
+ outputOffset += outputLfrResult.outputBytes;
+ long outputDataOffset =
+ outputLocalFileHeaderOffset + outputLfrResult.dataOffsetBytes;
if (pinPatterns != null) {
- boolean pinThisFile = false;
- for (Pattern pinPattern : pinPatterns) {
+ boolean pinFileHeader = false;
+ for (Hints.PatternWithRange pinPattern : pinPatterns) {
if (pinPattern.matcher(inputCdRecord.getName()).matches()) {
- pinThisFile = true;
- break;
+ Hints.ByteRange dataRange =
+ new Hints.ByteRange(outputDataOffset, outputOffset);
+ Hints.ByteRange pinRange =
+ pinPattern.ClampToAbsoluteByteRange(dataRange);
+ if (pinRange != null) {
+ pinFileHeader = true;
+ pinByteRanges.add(pinRange);
+ }
}
}
-
- if (pinThisFile) {
- pinByteRanges.add(
- new Hints.ByteRange(
- outputLocalFileHeaderOffset,
- outputOffset));
+ if (pinFileHeader) {
+ pinByteRanges.add(new Hints.ByteRange(outputLocalFileHeaderOffset,
+ outputDataOffset));
}
}
@@ -574,7 +580,17 @@
inspectEntryRequest.done();
}
- private static long outputInputJarEntryLfhRecordPreservingDataAlignment(
+ private static class OutputSizeAndDataOffset {
+ public long outputBytes;
+ public long dataOffsetBytes;
+
+ public OutputSizeAndDataOffset(long outputBytes, long dataOffsetBytes) {
+ this.outputBytes = outputBytes;
+ this.dataOffsetBytes = dataOffsetBytes;
+ }
+ }
+
+ private static OutputSizeAndDataOffset outputInputJarEntryLfhRecordPreservingDataAlignment(
DataSource inputLfhSection,
LocalFileRecord inputRecord,
DataSink outputLfhSection,
@@ -582,21 +598,27 @@
long inputOffset = inputRecord.getStartOffsetInArchive();
if (inputOffset == outputOffset) {
// This record's data will be aligned same as in the input APK.
- return inputRecord.outputRecord(inputLfhSection, outputLfhSection);
+ return new OutputSizeAndDataOffset(
+ inputRecord.outputRecord(inputLfhSection, outputLfhSection),
+ inputRecord.getDataStartOffsetInRecord());
}
int dataAlignmentMultiple = getInputJarEntryDataAlignmentMultiple(inputRecord);
if ((dataAlignmentMultiple <= 1)
|| ((inputOffset % dataAlignmentMultiple)
== (outputOffset % dataAlignmentMultiple))) {
// This record's data will be aligned same as in the input APK.
- return inputRecord.outputRecord(inputLfhSection, outputLfhSection);
+ return new OutputSizeAndDataOffset(
+ inputRecord.outputRecord(inputLfhSection, outputLfhSection),
+ inputRecord.getDataStartOffsetInRecord());
}
long inputDataStartOffset = inputOffset + inputRecord.getDataStartOffsetInRecord();
if ((inputDataStartOffset % dataAlignmentMultiple) != 0) {
// This record's data is not aligned in the input APK. No need to align it in the
// output.
- return inputRecord.outputRecord(inputLfhSection, outputLfhSection);
+ return new OutputSizeAndDataOffset(
+ inputRecord.outputRecord(inputLfhSection, outputLfhSection),
+ inputRecord.getDataStartOffsetInRecord());
}
// This record's data needs to be re-aligned in the output. This is achieved using the
@@ -606,8 +628,11 @@
inputRecord.getExtra(),
outputOffset + inputRecord.getExtraFieldStartOffsetInsideRecord(),
dataAlignmentMultiple);
- return inputRecord.outputRecordWithModifiedExtra(
- inputLfhSection, aligningExtra, outputLfhSection);
+ long dataOffset = inputRecord.getDataStartOffsetInRecord() +
+ aligningExtra.remaining() -
+ inputRecord.getExtra().remaining();
+ return new OutputSizeAndDataOffset(inputRecord.outputRecordWithModifiedExtra(
+ inputLfhSection, aligningExtra, outputLfhSection), dataOffset);
}
private static int getInputJarEntryDataAlignmentMultiple(LocalFileRecord entry) {
@@ -794,12 +819,12 @@
* Return list of pin patterns embedded in the pin pattern asset
* file. If no such file, return {@code null}.
*/
- private static List<Pattern> extractPinPatterns(
+ private static List<Hints.PatternWithRange> extractPinPatterns(
List<CentralDirectoryRecord> cdRecords, DataSource lhfSection)
throws IOException, ApkFormatException {
CentralDirectoryRecord pinListCdRecord =
findCdRecord(cdRecords, Hints.PIN_HINT_ASSET_ZIP_ENTRY_NAME);
- List<Pattern> pinPatterns = null;
+ List<Hints.PatternWithRange> pinPatterns = null;
if (pinListCdRecord != null) {
pinPatterns = new ArrayList<>();
byte[] patternBlob;
diff --git a/src/main/java/com/android/apksig/Hints.java b/src/main/java/com/android/apksig/Hints.java
index 49ef2b0..4070fa2 100644
--- a/src/main/java/com/android/apksig/Hints.java
+++ b/src/main/java/com/android/apksig/Hints.java
@@ -20,6 +20,7 @@
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
+import java.util.regex.Matcher;
import java.util.regex.Pattern;
public final class Hints {
@@ -47,6 +48,39 @@
}
}
+ public static final class PatternWithRange {
+ final Pattern pattern;
+ final long offset;
+ final long size;
+
+ public PatternWithRange(String pattern) {
+ this.pattern = Pattern.compile(pattern);
+ this.offset= 0;
+ this.size = Long.MAX_VALUE;
+ }
+
+ public PatternWithRange(String pattern, long offset, long size) {
+ this.pattern = Pattern.compile(pattern);
+ this.offset = offset;
+ this.size = size;
+ }
+
+ public Matcher matcher(CharSequence input) {
+ return this.pattern.matcher(input);
+ }
+
+ public ByteRange ClampToAbsoluteByteRange(ByteRange rangeIn) {
+ if (rangeIn.end - rangeIn.start < this.offset) {
+ return null;
+ }
+ long rangeOutStart = rangeIn.start + this.offset;
+ long rangeOutSize = Math.min(rangeIn.end - rangeOutStart,
+ this.size);
+ return new ByteRange(rangeOutStart,
+ rangeOutStart + rangeOutSize);
+ }
+ }
+
/**
* Create a blob of bytes that PinnerService understands as a
* sequence of byte ranges to pin.
@@ -65,13 +99,20 @@
return bos.toByteArray();
}
- public static ArrayList<Pattern> parsePinPatterns(byte[] patternBlob) {
- ArrayList<Pattern> pinPatterns = new ArrayList<>();
+ public static ArrayList<PatternWithRange> parsePinPatterns(byte[] patternBlob) {
+ ArrayList<PatternWithRange> pinPatterns = new ArrayList<>();
try {
for (String rawLine : new String(patternBlob, "UTF-8").split("\n")) {
String line = rawLine.replaceFirst("#.*", ""); // # starts a comment
- if (!("".equals(line))) {
- pinPatterns.add(Pattern.compile(line));
+ String[] fields = line.split(" ");
+ if (fields.length == 1) {
+ pinPatterns.add(new PatternWithRange(fields[0]));
+ } else if (fields.length == 3) {
+ long start = Long.parseLong(fields[1]);
+ long end = Long.parseLong(fields[2]);
+ pinPatterns.add(new PatternWithRange(fields[0], start, end - start));
+ } else {
+ throw new AssertionError("bad pin pattern line " + line);
}
}
} catch (UnsupportedEncodingException ex) {