mirror of
https://github.com/morluto/rea.git
synced 2026-11-14 18:52:48 +00:00
fix(android): bound split facts and keep saved JNI reports readable
Charge the manifest split facts against the native inventory's retained JSON allowance and refuse more than 64 split types or 256-character names as an unknown, so a crafted manifest cannot overflow the response. Reports saved before split facts existed still parse: the report schema treats absent facts as optional while the live bridge contract requires them. The JADX regression now decodes a generated binary manifest through nativeDeclarations, with named and resource-ID-only attributes. Refs #9756 Co-Authored-By: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
This commit is contained in:
1 parent
18b72ca461
commit
9943c1c8bc
5 files changed
+123
-17
No files matched your search
@@ -43,7 +43,7 @@ public class ReaJadxMetadataRegression {
|
||||
return ((TextContent) response.getContent().get(0)).getText();
|
||||
}
|
||||
|
||||
private static void verifyManifestSummary() {
|
||||
private static void verifyManifestSummary() throws Exception {
|
||||
for (String prefix : List.of("android", "a")) {
|
||||
for (String encoded : List.of("one & beta", "one &amp; beta")) {
|
||||
String xml = "<manifest xmlns:" + prefix + "=\"http://schemas.android.com/apk/res/android\""
|
||||
@@ -81,7 +81,7 @@ public class ReaJadxMetadataRegression {
|
||||
verifyManifestSplitFacts();
|
||||
}
|
||||
|
||||
private static void verifyManifestSplitFacts() {
|
||||
private static void verifyManifestSplitFacts() throws Exception {
|
||||
String android = "xmlns:a=\"http://schemas.android.com/apk/res/android\"";
|
||||
Map<String, Object> split = ReaJadxBridge.manifestSplitFacts("<manifest " + android
|
||||
+ " a:requiredSplitTypes=\"base__abi, base__density\"><application a:isSplitRequired=\"true\"/>"
|
||||
@@ -92,9 +92,83 @@ public class ReaJadxMetadataRegression {
|
||||
+ " requiredSplitTypes=\"unnamespaced\"><application a:isSplitRequired=\"false\"/></manifest>");
|
||||
require(plain.get("required_split_types").equals(List.of())
|
||||
&& plain.get("split_required").equals(false),
|
||||
"Split facts came from an unnamespaced or false declaration: " + plain);
|
||||
"Split facts came from an unnamespaced or false declaration: " + plain); boolean refused = false;
|
||||
try {
|
||||
ReaJadxBridge.manifestSplitFacts("<manifest " + android + " a:requiredSplitTypes=\""
|
||||
+ String.join(",", Collections.nCopies(65, "s")) + "\"/>");
|
||||
} catch (IllegalArgumentException expected) { refused = true; }
|
||||
require(refused, "An unbounded requiredSplitTypes list was admitted");
|
||||
// The bridge decodes the binary manifest itself, without a session; framework
|
||||
// attribute names must resolve through their resource IDs when names are stripped.
|
||||
for (boolean named : List.of(true, false)) {
|
||||
Path apk = Files.createTempFile("rea-split-manifest-", ".apk");
|
||||
try {
|
||||
try (ZipOutputStream zip = new ZipOutputStream(Files.newOutputStream(apk))) {
|
||||
zip.putNextEntry(new ZipEntry("AndroidManifest.xml"));
|
||||
zip.write(splitManifest(named));
|
||||
zip.closeEntry();
|
||||
}
|
||||
Object facts = ReaJadxBridge.nativeDeclarations(apk).get("manifest_splits");
|
||||
require(facts.equals(Map.of("status", "observed",
|
||||
"required_split_types", List.of("base__abi", "base__density"), "split_required", true)),
|
||||
"Binary manifest split facts were lost (named=" + named + "): " + facts);
|
||||
} finally {
|
||||
Files.deleteIfExists(apk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** A binary manifest: requiredSplitTypes on <manifest>, isSplitRequired on <application>. */
|
||||
private static byte[] splitManifest(boolean namedAttributes) {
|
||||
List<String> strings = List.of("http://schemas.android.com/apk/res/android", "manifest",
|
||||
namedAttributes ? "requiredSplitTypes" : "", "base__abi,base__density", "application",
|
||||
namedAttributes ? "isSplitRequired" : "x");
|
||||
java.io.ByteArrayOutputStream data = new java.io.ByteArrayOutputStream();
|
||||
java.nio.ByteBuffer offsets = littleEndian(4 * strings.size());
|
||||
for (String value : strings) {
|
||||
offsets.putInt(data.size());
|
||||
byte[] utf16 = value.getBytes(StandardCharsets.UTF_16LE);
|
||||
data.writeBytes(littleEndian(2).putShort((short) value.length()).array());
|
||||
data.writeBytes(utf16);
|
||||
data.writeBytes(new byte[2]);
|
||||
}
|
||||
byte[] pool = chunk(0x0001, 28, littleEndian(20 + offsets.capacity() + data.size())
|
||||
.putInt(strings.size()).putInt(0).putInt(0).putInt(28 + offsets.capacity()).putInt(0)
|
||||
.put(offsets.array()).put(data.toByteArray()).array());
|
||||
java.nio.ByteBuffer ids = littleEndian(4 * 6);
|
||||
for (int id : new int[] {0, 0, 0x0101064e, 0, 0, 0x01010591}) ids.putInt(id);
|
||||
byte[] resources = chunk(0x0180, 8, ids.array());
|
||||
byte[] manifest = startElement(1, 2, 3, 0x03, 3);
|
||||
byte[] application = startElement(4, 5, -1, 0x12, -1);
|
||||
java.io.ByteArrayOutputStream body = new java.io.ByteArrayOutputStream();
|
||||
for (byte[] part : List.of(pool, resources, manifest, application, endElement(4), endElement(1)))
|
||||
body.writeBytes(part);
|
||||
return chunk(0x0003, 8, body.toByteArray());
|
||||
}
|
||||
|
||||
private static java.nio.ByteBuffer littleEndian(int size) {
|
||||
return java.nio.ByteBuffer.allocate(size).order(java.nio.ByteOrder.LITTLE_ENDIAN);
|
||||
}
|
||||
|
||||
private static byte[] chunk(int type, int headerSize, byte[] body) {
|
||||
return littleEndian(8 + body.length).putShort((short) type).putShort((short) headerSize)
|
||||
.putInt(8 + body.length).put(body).array();
|
||||
}
|
||||
|
||||
/** One element with one android-namespaced attribute row. */
|
||||
private static byte[] startElement(int name, int attribute, int raw, int type, int value) {
|
||||
return chunk(0x0102, 16, littleEndian(48).putInt(-1).putInt(-1).putInt(-1).putInt(name)
|
||||
.putShort((short) 20).putShort((short) 20).putShort((short) 1).putShort((short) 0)
|
||||
.putShort((short) 0).putShort((short) 0)
|
||||
.putInt(0).putInt(attribute).putInt(raw).putShort((short) 8).put((byte) 0)
|
||||
.put((byte) type).putInt(value).array());
|
||||
}
|
||||
|
||||
private static byte[] endElement(int name) {
|
||||
return chunk(0x0103, 16, littleEndian(16).putInt(-1).putInt(-1).putInt(-1).putInt(name).array());
|
||||
}
|
||||
|
||||
|
||||
private static void verifyRootManifests(SessionHolder holder, SessionConfig config,
|
||||
Constructor<?> constructor, Method handle, Path output) throws Exception {
|
||||
verifyManifestSummary();
|
||||
|
||||
Reference in new issue
Block a user