From 7fb7dc471584920f74d6a3dc3c71c33001c95db6 Mon Sep 17 00:00:00 2001 From: David Srbecky Date: Thu, 25 Jun 2026 14:24:41 +0100 Subject: [PATCH 1/8] Use JCommander 1.85 Test: m android-smali Test: ./gradlew assemble Change-Id: I74961a1c88d0de8055ecf5ca7b35557286044369 --- build.gradle | 28 +------------------ .../smali/util/jcommander/HelpFormatter.java | 6 ++-- 2 files changed, 4 insertions(+), 30 deletions(-) diff --git a/build.gradle b/build.gradle index c58ae6fcc..2f80e0794 100644 --- a/build.gradle +++ b/build.gradle @@ -31,32 +31,6 @@ apply plugin: 'idea' version = '3.0.9' -def jcommanderVersion = '' - -if (!('release' in gradle.startParameter.taskNames)) { - // we compile against 1.48 normally, to match what's in AOSP, but switch to a newer version - // for release, because it has some fixes required when running on Android - jcommanderVersion = 'com.beust:jcommander:1.48' - - def versionSuffix - try { - def git = org.eclipse.jgit.api.Git.open(file('.')) - def head = git.getRepository().getRef('HEAD') - versionSuffix = head.getObjectId().abbreviate(8).name() - - if (!git.status().call().clean) { - versionSuffix += '-dirty' - } - } catch (Exception) { - // In case we can't get the commit for some reason, - // just use -dev - versionSuffix = 'dev' - } - - version += "-${versionSuffix}" -} else { - jcommanderVersion = 'com.beust:jcommander:1.64' -} // Note: please don't use this. This is strictly for the official releases // that are posted on, e.g. the bitbucket download page. @@ -108,7 +82,7 @@ subprojects { proguard_gradle: 'com.guardsquare:proguard-gradle:7.1.0', dx: 'com.google.android.tools:dx:1.7', gson: 'com.google.code.gson:gson:2.3.1', - jcommander: jcommanderVersion + jcommander: 'org.jcommander:jcommander:1.85' ] } diff --git a/util/src/main/java/com/android/tools/smali/util/jcommander/HelpFormatter.java b/util/src/main/java/com/android/tools/smali/util/jcommander/HelpFormatter.java index 5b580dcfc..2d050f5c4 100644 --- a/util/src/main/java/com/android/tools/smali/util/jcommander/HelpFormatter.java +++ b/util/src/main/java/com/android/tools/smali/util/jcommander/HelpFormatter.java @@ -162,7 +162,7 @@ public String format(@Nonnull List commandHierarchy) { } if (leafJc.getMainParameter() != null) { - String[] argumentNames = ExtendedCommands.parameterArgumentNames(leafJc.getMainParameter()); + String[] argumentNames = ExtendedCommands.parameterArgumentNames(leafJc.getMainParameterValue()); if (argumentNames.length == 0) { writer.write(" "); } else { @@ -239,7 +239,7 @@ public String format(@Nonnull List commandHierarchy) { } if (leafJc.getMainParameter() != null) { - String[] argumentNames = ExtendedCommands.parameterArgumentNames(leafJc.getMainParameter()); + String[] argumentNames = ExtendedCommands.parameterArgumentNames(leafJc.getMainParameterValue()); writer.write("\n"); writer.indent(4); if (argumentNames.length > 0) { @@ -288,7 +288,7 @@ public String format(@Nonnull List commandHierarchy) { writer.write(")"); } - String commandDesc = leafJc.getCommandDescription(commandName); + String commandDesc = leafJc.getUsageFormatter().getCommandDescription(commandName); if (commandDesc != null) { writer.write(" - "); writer.write(commandDesc); From 947737fe22529e900fd7adf89fa6aa6172669aaa Mon Sep 17 00:00:00 2001 From: David Srbecky Date: Thu, 25 Jun 2026 15:24:43 +0100 Subject: [PATCH 2/8] Fix typo in StringUtils.repeat Correct the count validation logic in StringUtils.repeat. Previously, a count of 0 or 1 was incorrectly treated as invalid, and a negative count did not throw an IllegalArgumentException. Test: ./gradlew :dexlib2:test Flag: EXEMPT bugfix TAG=agy CONV=8b50a6b3-6ed8-4255-859d-a4739fc9b48a Change-Id: I7d0118a22fb468d6e49620d82d1674f6da989eb0 --- .../android/tools/smali/util/StringUtils.java | 2 +- .../tools/smali/util/StringUtilsTest.java | 53 +++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 dexlib2/src/test/java/com/android/tools/smali/util/StringUtilsTest.java diff --git a/dexlib2/src/main/java/com/android/tools/smali/util/StringUtils.java b/dexlib2/src/main/java/com/android/tools/smali/util/StringUtils.java index 6aa63a4b7..8e7d60085 100644 --- a/dexlib2/src/main/java/com/android/tools/smali/util/StringUtils.java +++ b/dexlib2/src/main/java/com/android/tools/smali/util/StringUtils.java @@ -166,7 +166,7 @@ public static String repeat(String string, int count) { } if (count <= 1) { - if (count >= 0) { + if (count < 0) { throw new IllegalArgumentException("invalid count: " + count); } return (count == 0) ? "" : string; diff --git a/dexlib2/src/test/java/com/android/tools/smali/util/StringUtilsTest.java b/dexlib2/src/test/java/com/android/tools/smali/util/StringUtilsTest.java new file mode 100644 index 000000000..8303b6662 --- /dev/null +++ b/dexlib2/src/test/java/com/android/tools/smali/util/StringUtilsTest.java @@ -0,0 +1,53 @@ +/* + * Copyright 2026, Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.android.tools.smali.util; + +import org.junit.Assert; +import org.junit.Test; + +public class StringUtilsTest { + @Test + public void testRepeat() { + Assert.assertEquals("", StringUtils.repeat("a", 0)); + Assert.assertEquals("a", StringUtils.repeat("a", 1)); + Assert.assertEquals("aa", StringUtils.repeat("a", 2)); + Assert.assertEquals("aaaaa", StringUtils.repeat("a", 5)); + + Assert.assertEquals("", StringUtils.repeat("", 0)); + Assert.assertEquals("", StringUtils.repeat("", 1)); + Assert.assertEquals("", StringUtils.repeat("", 5)); + } + + @Test(expected = IllegalArgumentException.class) + public void testRepeatNegative() { + StringUtils.repeat("a", -1); + } +} From 6d6da5e63e4e360bba9785003f85962b999e858b Mon Sep 17 00:00:00 2001 From: David Srbecky Date: Mon, 29 Jun 2026 10:26:22 +0100 Subject: [PATCH 3/8] Regenerate smali parser and lexer with relative paths Regenerated the smali parser, lexer, and tree walker using updated scripts that use relative paths. This avoids embedding absolute paths of the build environment in the generated Java files. Updated the helper scripts to cd to the project directory before running the generators: - smali/run_antlr.sh - smali/run_jflex.sh Bug: None Test: ./gradlew clean test -x :smali:generateGrammarSource -x :smali:jflex Flag: EXEMPT refactoring TAG=agy CONV=7fa249f9-41b5-437f-baeb-29303ea91886 Change-Id: I69c20fe194b4bd068b72fd808309baa38bbbc65d --- smali/run_antlr.sh | 11 +++++++---- smali/run_jflex.sh | 9 ++++++--- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/smali/run_antlr.sh b/smali/run_antlr.sh index 1b5f000cc..e7127fc74 100755 --- a/smali/run_antlr.sh +++ b/smali/run_antlr.sh @@ -46,6 +46,9 @@ if [[ -z "$ANTLR_JARS" ]]; then exit 1 fi +# cd to the script directory so we can use relative paths +cd "$SCRIPT_DIR" + function run_antlr() { CLASSPATH="$ANTLR_JARS" java 2>&1 "$ANTLR_MAIN" "$@" } @@ -100,12 +103,12 @@ function cleanup_tokens { [[ -f "$TOKEN_FILE" ]] && rm "$TOKEN_FILE" } -generate_file "$SCRIPT_DIR/src/main/java/com/android/tools/smali/smali/smaliParser.java" "$SCRIPT_DIR/src/main/antlr/smaliParser.g" -generate_file "$SCRIPT_DIR/src/main/java/com/android/tools/smali/smali/smaliTreeWalker.java" "$SCRIPT_DIR/src/main/antlr/smaliTreeWalker.g" +generate_file "src/main/java/com/android/tools/smali/smali/smaliParser.java" "src/main/antlr/smaliParser.g" +generate_file "src/main/java/com/android/tools/smali/smali/smaliTreeWalker.java" "src/main/antlr/smaliTreeWalker.g" # Clean up the tokens, no longer necessary once the tree walker is generated -cleanup_tokens "$SCRIPT_DIR/src/main/java/com/android/tools/smali/smali/smaliParser.java" -cleanup_tokens "$SCRIPT_DIR/src/main/java/com/android/tools/smali/smali/smaliTreeWalker.java" +cleanup_tokens "src/main/java/com/android/tools/smali/smali/smaliParser.java" +cleanup_tokens "src/main/java/com/android/tools/smali/smali/smaliTreeWalker.java" # Uncomment to run interactively #run_antlr "$@" \ No newline at end of file diff --git a/smali/run_jflex.sh b/smali/run_jflex.sh index 79f1acaf1..65c9cb8b5 100755 --- a/smali/run_jflex.sh +++ b/smali/run_jflex.sh @@ -18,7 +18,10 @@ EXPECTED_JFLEX_VERSION_STR="This is JFlex $EXPECTED_JFLEX_VERSION" SCRIPT_DIR=`dirname $0` echo $SCRIPT_DIR -TOP_PATH="$SCRIPT_DIR/../../.." +# cd to the script directory so we can use relative paths +cd "$SCRIPT_DIR" + +TOP_PATH="../../.." # Run the java jar when 'JFLEX' is not in the environment function exec_jar_jflex { @@ -52,10 +55,10 @@ then exit 1 fi -JAVA_FILE=$SCRIPT_DIR/src/main/java/com/android/tools/smali/smali/smaliFlexLexer.java +JAVA_FILE=src/main/java/com/android/tools/smali/smali/smaliFlexLexer.java rm -f "$JAVA_FILE" -"$JFLEX" --nobak -d "$SCRIPT_DIR/src/main/java/com/android/tools/smali/smali" "$SCRIPT_DIR/src/main/jflex/smaliLexer.jflex" +"$JFLEX" --nobak -d "src/main/java/com/android/tools/smali/smali" "src/main/jflex/smaliLexer.jflex" # delete trailing space from end of each line to make gerrit happy sed 's/[ ]*$//' "$JAVA_FILE" > "$JAVA_FILE.tmp" From 5cde6a277976c3848ab31ad5dfccdedeac6c2f9e Mon Sep 17 00:00:00 2001 From: David Srbecky Date: Mon, 29 Jun 2026 12:33:48 +0100 Subject: [PATCH 4/8] Hardening DexBackedDexFile map parsing. Eagerly read and validate mapSize to ensure it does not extend beyond the container bounds. Also validate mapOffset. This prevents ArrayIndexOutOfBoundsException when parsing malformed DEX files with corrupted map size/offset. Test: test on corrupt apk Change-Id: I3e805ebe8a1deb5e24713e7c2846daccb5221587 --- .../smali/dexlib2/dexbacked/DexBackedDexFile.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/dexlib2/src/main/java/com/android/tools/smali/dexlib2/dexbacked/DexBackedDexFile.java b/dexlib2/src/main/java/com/android/tools/smali/dexlib2/dexbacked/DexBackedDexFile.java index b3d24bc9e..61c480e70 100644 --- a/dexlib2/src/main/java/com/android/tools/smali/dexlib2/dexbacked/DexBackedDexFile.java +++ b/dexlib2/src/main/java/com/android/tools/smali/dexlib2/dexbacked/DexBackedDexFile.java @@ -179,6 +179,16 @@ protected DexBackedDexFile(@Nullable Opcodes opcodes, if (container_off != header_offset) { throw new DexUtil.InvalidFile(String.format("Unexpected container offset in header")); } + + if (mapOffset < 0 || mapOffset > containerSize - 4) { + throw new DexUtil.InvalidFile(String.format("Invalid mapOffset %d", mapOffset)); + } + + // Eagerly read mapSize to validate map bounds + int mapSize = dexBuffer.readSmallUint(mapOffset); + if (mapOffset + 4 + mapSize * MapItem.ITEM_SIZE > containerSize) { + throw new DexUtil.InvalidFile("Map extends beyond container bounds"); + } } /** From a1fb7ffbf0cb195c0ee4dfde0124c1ef4aab8e90 Mon Sep 17 00:00:00 2001 From: David Srbecky Date: Mon, 29 Jun 2026 12:37:32 +0100 Subject: [PATCH 5/8] Expose getDebugOffset() in DexBackedMethodImplementation Making getDebugOffset() public allows external tools to access the debug offset and calculate method/class sizes while properly handling debug info duplication. Change-Id: I56321c16d0ee6769c9fbd7041d826ef7dcb6b150 --- .../smali/dexlib2/dexbacked/CDexBackedMethodImplementation.java | 2 +- .../smali/dexlib2/dexbacked/DexBackedMethodImplementation.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dexlib2/src/main/java/com/android/tools/smali/dexlib2/dexbacked/CDexBackedMethodImplementation.java b/dexlib2/src/main/java/com/android/tools/smali/dexlib2/dexbacked/CDexBackedMethodImplementation.java index 99df58e1e..5167caee1 100644 --- a/dexlib2/src/main/java/com/android/tools/smali/dexlib2/dexbacked/CDexBackedMethodImplementation.java +++ b/dexlib2/src/main/java/com/android/tools/smali/dexlib2/dexbacked/CDexBackedMethodImplementation.java @@ -110,7 +110,7 @@ protected int getTriesSize() { } @Override - protected int getDebugOffset() { + public int getDebugOffset() { CDexBackedDexFile cdexFile = ((CDexBackedDexFile) dexFile); int debugTableItemOffset = (method.methodIndex / 16) * 4; diff --git a/dexlib2/src/main/java/com/android/tools/smali/dexlib2/dexbacked/DexBackedMethodImplementation.java b/dexlib2/src/main/java/com/android/tools/smali/dexlib2/dexbacked/DexBackedMethodImplementation.java index a231cfb2b..497511063 100644 --- a/dexlib2/src/main/java/com/android/tools/smali/dexlib2/dexbacked/DexBackedMethodImplementation.java +++ b/dexlib2/src/main/java/com/android/tools/smali/dexlib2/dexbacked/DexBackedMethodImplementation.java @@ -137,7 +137,7 @@ public int size() { return Collections.emptyList(); } - protected int getDebugOffset() { + public int getDebugOffset() { return dexFile.getDataBuffer().readInt(codeOffset + CodeItem.DEBUG_INFO_OFFSET); } From f719fae503b1fed65c2e77f35f81fc9cd55070ed Mon Sep 17 00:00:00 2001 From: David Srbecky Date: Fri, 3 Jul 2026 15:44:37 +0100 Subject: [PATCH 6/8] API 23 compatibility fixes for emptySortedSet Replacing Collections.emptySortedSet() (which requires API 26) with Collections.unmodifiableSortedSet(new TreeSet()) to support API 23. Bug: 285129432 Test: ./gradlew :dexlib2:test Flag: NONE tool compatibility fix TAG=agy CONV=1ea891e6-8562-4a15-8bba-16a2d9b01b34 Change-Id: I6880b22ff0b455128e9bd1572ac2a0e177b6767e --- .../tools/smali/dexlib2/writer/builder/BuilderClassDef.java | 4 ++-- .../java/com/android/tools/smali/util/ImmutableConverter.java | 2 +- .../java/com/android/tools/smali/util/ImmutableUtils.java | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dexlib2/src/main/java/com/android/tools/smali/dexlib2/writer/builder/BuilderClassDef.java b/dexlib2/src/main/java/com/android/tools/smali/dexlib2/writer/builder/BuilderClassDef.java index b6d0c29af..ff4b6f5d3 100644 --- a/dexlib2/src/main/java/com/android/tools/smali/dexlib2/writer/builder/BuilderClassDef.java +++ b/dexlib2/src/main/java/com/android/tools/smali/dexlib2/writer/builder/BuilderClassDef.java @@ -80,10 +80,10 @@ public class BuilderClassDef extends BaseTypeReference implements ClassDef { methods = Collections.emptyList(); } if (staticFields == null) { - staticFields = Collections.emptySortedSet(); + staticFields = Collections.unmodifiableSortedSet(new java.util.TreeSet<>()); } if (instanceFields == null) { - instanceFields = Collections.emptySortedSet(); + instanceFields = Collections.unmodifiableSortedSet(new java.util.TreeSet<>()); } this.type = type; diff --git a/dexlib2/src/main/java/com/android/tools/smali/util/ImmutableConverter.java b/dexlib2/src/main/java/com/android/tools/smali/util/ImmutableConverter.java index 5d48d705a..1ff9be12d 100644 --- a/dexlib2/src/main/java/com/android/tools/smali/util/ImmutableConverter.java +++ b/dexlib2/src/main/java/com/android/tools/smali/util/ImmutableConverter.java @@ -117,7 +117,7 @@ public Set toSet(@Nullable final Iterable iterabl public SortedSet toSortedSet(@Nonnull Comparator comparator, @Nullable final Iterable iterable) { if (iterable == null) { - return Collections.emptySortedSet(); + return unmodifiableSortedSet(new java.util.TreeSet(comparator)); } boolean needsCopy = false; diff --git a/dexlib2/src/main/java/com/android/tools/smali/util/ImmutableUtils.java b/dexlib2/src/main/java/com/android/tools/smali/util/ImmutableUtils.java index 0107f856a..233ced416 100644 --- a/dexlib2/src/main/java/com/android/tools/smali/util/ImmutableUtils.java +++ b/dexlib2/src/main/java/com/android/tools/smali/util/ImmutableUtils.java @@ -58,7 +58,7 @@ public class ImmutableUtils { @Nonnull public static SortedSet nullToEmptySortedSet(@Nullable SortedSet set) { if (set == null) { - return Collections.emptySortedSet(); + return unmodifiableSortedSet(new java.util.TreeSet()); } return unmodifiableSortedSet(set); } From 2b7a562196830d90dcd093b18ff4076cb352cf08 Mon Sep 17 00:00:00 2001 From: David Srbecky Date: Fri, 3 Jul 2026 07:52:48 -0700 Subject: [PATCH 7/8] Hardening ZipDexContainer and DexBackedDexFile Improve ZipDexContainer to handle invalid dex entries. It now allows registering invalid entries and throws when they are accessed, or ignores them if configured to do so. Also update DexBackedDexFile to throw InvalidFile instead of IndexOutOfBoundsException for invalid offsets. Bug: 285129432 Test: ./gradlew :dexlib2:test TAG=agy CONV=1ea891e6-8562-4a15-8bba-16a2d9b01b34 Change-Id: I7cba6f96809be00aca247dcc7c22b5ac1f988b3f --- .../dexlib2/dexbacked/DexBackedDexFile.java | 9 +- .../dexlib2/dexbacked/ZipDexContainer.java | 97 ++++++++++++------- 2 files changed, 67 insertions(+), 39 deletions(-) diff --git a/dexlib2/src/main/java/com/android/tools/smali/dexlib2/dexbacked/DexBackedDexFile.java b/dexlib2/src/main/java/com/android/tools/smali/dexlib2/dexbacked/DexBackedDexFile.java index 61c480e70..4772f3063 100644 --- a/dexlib2/src/main/java/com/android/tools/smali/dexlib2/dexbacked/DexBackedDexFile.java +++ b/dexlib2/src/main/java/com/android/tools/smali/dexlib2/dexbacked/DexBackedDexFile.java @@ -104,13 +104,14 @@ protected DexBackedDexFile(@Nullable Opcodes opcodes, boolean verifyMagic, int header_offset) { if (offset < 0 || offset > buf.length) { - throw new IndexOutOfBoundsException( + throw new DexUtil.InvalidFile( String.format("Invalid offset %d for buffer of length %d", offset, buf.length)); } if (header_offset < 0 || buf.length - offset < header_offset) { - throw new IndexOutOfBoundsException(String.format( - "Invalid header_offset %d for buffer of length %d at offset %d", - header_offset, buf.length, offset)); + throw new DexUtil.InvalidFile( + String.format( + "Invalid header_offset %d for buffer of length %d at offset %d", + header_offset, buf.length, offset)); } dexBuffer = new DexBuffer(buf, offset); diff --git a/dexlib2/src/main/java/com/android/tools/smali/dexlib2/dexbacked/ZipDexContainer.java b/dexlib2/src/main/java/com/android/tools/smali/dexlib2/dexbacked/ZipDexContainer.java index 4d2454863..99e492b8c 100644 --- a/dexlib2/src/main/java/com/android/tools/smali/dexlib2/dexbacked/ZipDexContainer.java +++ b/dexlib2/src/main/java/com/android/tools/smali/dexlib2/dexbacked/ZipDexContainer.java @@ -31,7 +31,7 @@ package com.android.tools.smali.dexlib2.dexbacked; import com.android.tools.smali.dexlib2.Opcodes; -import com.android.tools.smali.dexlib2.iface.DexFile; +import com.android.tools.smali.dexlib2.dexbacked.DexBackedDexFile.NotADexFile; import com.android.tools.smali.dexlib2.iface.MultiDexContainer; import com.android.tools.smali.dexlib2.util.DexUtil; import com.android.tools.smali.dexlib2.util.DexUtil.InvalidFile; @@ -76,6 +76,7 @@ public interface ZipDexEntry extends DexEntry { private final File zipFilePath; @Nullable private final Opcodes opcodes; + private final boolean ignoreInvalid; private Map entries; /** @@ -84,8 +85,20 @@ public interface ZipDexEntry extends DexEntry { * @param zipFilePath The path to the zip file */ public ZipDexContainer(@Nonnull File zipFilePath, @Nullable Opcodes opcodes) { + this(zipFilePath, opcodes, true); + } + + /** + * Constructs a new ZipDexContainer for the given zip file + * + * @param zipFilePath The path to the zip file + * @param ignoreInvalid If true, invalid dex entries will be silently ignored + */ + public ZipDexContainer( + @Nonnull File zipFilePath, @Nullable Opcodes opcodes, boolean ignoreInvalid) { this.zipFilePath = zipFilePath; this.opcodes = opcodes; + this.ignoreInvalid = ignoreInvalid; } /** @@ -144,40 +157,54 @@ private void loadDexFilesFromZipEntry(@Nonnull ZipFile zipFile, @Nonnull ZipEntr try (InputStream inputStream = zipFile.getInputStream(entry)) { byte[] buf = InputStreamUtil.toByteArray(inputStream); for (int offset = 0, i = 0; offset < buf.length; i++) { - DexBackedDexFile dex = new DexBackedDexFile(opcodes, buf, 0, true, offset); - boolean hasSingleEntry = dex.isDexContainerFirstEntry() && - dex.isDexContainerLastEntry(); - String entryName = entry.getName() + (hasSingleEntry ? "" : ("/" + i)); - ZipDexEntry dexEntry = new ZipDexEntry() { - @Nonnull - @Override - public String getEntryName() { - return entryName; - } - - @Nonnull - @Override - public DexBackedDexFile getDexFile() { - return dex; - } - - @Nonnull - @Override - public MultiDexContainer getContainer() { - return ZipDexContainer.this; - } - - @Override - public long getCrc() { - return entryCrc; - } - }; - entries.put(entryName, dexEntry); - offset += dex.getFileSize(); - if (dex.isDexContainerLastEntry()) { - break; - } - }; + final int headerOffset = offset; + boolean isLast; + int fileSize; + try { + DexBackedDexFile dex = new DexBackedDexFile(opcodes, buf, 0, true, headerOffset); + isLast = dex.isDexContainerLastEntry(); + fileSize = dex.getFileSize(); + } catch (NotADexFile | InvalidFile | UnsupportedFile ex) { + if (ignoreInvalid) { + return; + } + isLast = true; + fileSize = buf.length - offset; + } + final String entryName = entry.getName() + (i == 0 && isLast ? "" : ("/" + i)); + ZipDexEntry dexEntry = + new ZipDexEntry() { + @Nonnull + @Override + public String getEntryName() { + return entryName; + } + + // throws InvalidFile if the dex file has an invalid header. + // throws UnsupportedFile if the dex file version is not supported. + @Nonnull + @Override + public DexBackedDexFile getDexFile() { + return new DexBackedDexFile(opcodes, buf, 0, true, headerOffset); + } + + @Nonnull + @Override + public MultiDexContainer getContainer() { + return ZipDexContainer.this; + } + + @Override + public long getCrc() { + return entryCrc; + } + }; + entries.put(entryName, dexEntry); + offset += fileSize; + if (isLast) { + return; + } + } } } From 91918144d99d4b5b600492927f28d9c047dfac28 Mon Sep 17 00:00:00 2001 From: Vali Dobrota Date: Tue, 14 Jul 2026 04:12:38 -0700 Subject: [PATCH 8/8] Fix improper Value Skipping in OatHeader Key-Value Store Parsing In OatHeader.getKeyValue(String key), when iterating through key-value store, if the current key k didn't match the requested key, offset was incremented by only 1 byte (moving it to the value), causing the loop to treat the value as a key in the next iteration. The format of the OAT key-value store is key1\0value1\0key2\0value2\0.... When a key does not match, the parser must skip BOTH the key and its corresponding value. The code only incremented offset++ once, which just skips the null terminator of the key, starting the next key search inside the value string. Change-Id: Ib8be875e9bdfe50cb88412c9399f46cc6b11f748 --- .../smali/dexlib2/dexbacked/OatFile.java | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/dexlib2/src/main/java/com/android/tools/smali/dexlib2/dexbacked/OatFile.java b/dexlib2/src/main/java/com/android/tools/smali/dexlib2/dexbacked/OatFile.java index c16990eaa..3e0573210 100644 --- a/dexlib2/src/main/java/com/android/tools/smali/dexlib2/dexbacked/OatFile.java +++ b/dexlib2/src/main/java/com/android/tools/smali/dexlib2/dexbacked/OatFile.java @@ -330,18 +330,19 @@ public String getKeyValue(@Nonnull String key) { int keyEndOffset = offset; String k = new String(buf, keyStartOffset, keyEndOffset - keyStartOffset); + int valueStartOffset = ++offset; + while (offset < endOffset && buf[offset] != '\0') { + offset++; + } + if (offset >= endOffset) { + throw new InvalidOatFileException("Oat file contains truncated key value store"); + } + if (k.equals(key)) { - int valueStartOffset = ++offset; - while (offset < endOffset && buf[offset] != '\0') { - offset++; - } - if (offset >= endOffset) { - throw new InvalidOatFileException("Oat file contains truncated key value store"); - } - int valueEndOffset = offset; - return new String(buf, valueStartOffset, valueEndOffset - valueStartOffset); + return new String(buf, valueStartOffset, offset - valueStartOffset); } - offset++; + + offset++; // Move past the null terminator of the value } return null; }