Files
fastfetch/src/common/android/dex.c
T
2026-10-10 23:55:38 +08:00

1310 lines
61 KiB
C

#include "common/android/dex.h"
#include "common/debug.h"
#include "common/io.h"
#include "common/mallocHelper.h"
#include <errno.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#ifdef FF_HAVE_ZLIB
#include "common/zlib.h"
#endif
// Offsets into the dex header, from https://source.android.com/docs/core/runtime/dex-format.
#define FF_DEX_ENDIAN_TAG 0x12345678u
#define FF_DEX_HEADER_SIZE 0x70
#define FF_DEX_OFF_FILE_SIZE 0x20
#define FF_DEX_OFF_ENDIAN_TAG 0x28
#define FF_DEX_OFF_STRING_IDS 0x3C
#define FF_DEX_OFF_TYPE_IDS_SIZE 0x40
#define FF_DEX_OFF_TYPE_IDS 0x44
#define FF_DEX_OFF_FIELD_IDS_SIZE 0x50
#define FF_DEX_OFF_FIELD_IDS 0x54
#define FF_DEX_OFF_METHOD_IDS_SIZE 0x58
#define FF_DEX_OFF_METHOD_IDS 0x5C
#define FF_DEX_OFF_CLASS_DEFS_SIZE 0x60
#define FF_DEX_OFF_CLASS_DEFS 0x64
// class_def_item: [u32 class_idx][u32 access_flags][u32 superclass_idx][u32 interfaces_off]
// [u32 source_file_idx][u32 annotations_off][u32 class_data_off][u32 static_values_off]
#define FF_DEX_CLASS_DEF_SIZE 32
#define FF_DEX_OFF_CLASS_DEF_DATA 24
#define FF_DEX_OFF_CLASS_DEF_STATIC_VALUES 28
// field_id_item: [u16 class_idx][u16 type_idx][u32 name_idx]
#define FF_DEX_FIELD_ID_SIZE 8
#define FF_DEX_OFF_FIELD_ID_TYPE 2
#define FF_DEX_OFF_FIELD_ID_NAME 4
// method_id_item: [u16 class_idx][u16 proto_idx][u32 name_idx]
#define FF_DEX_METHOD_ID_SIZE 8
#define FF_DEX_OFF_METHOD_ID_NAME 4
// code_item: [u16 registers_size][u16 ins_size][u16 outs_size][u16 tries_size][u32 debug_info_off]
// [u32 insns_size][u16 insns...]. Everything before the instruction stream is skipped by reading
// `insns_size` and starting `insns` where it ends.
#define FF_DEX_OFF_CODE_INSNS_SIZE 12
#define FF_DEX_OFF_CODE_INSNS 16
// The seven `iget*` opcodes, which are how a method reads an instance field. `iput*` shares the same
// format and the same operand, and a `writeToParcel` does not store to a field, so one range covers
// both and the walk does not have to tell them apart.
#define FF_DEX_OP_FIELD_FIRST 0x52
#define FF_DEX_OP_FIELD_LAST 0x5F
// The zip local file header, from APPNOTE.TXT 4.3.7. Only the two length fields are read from it:
// the payload starts right behind them, and the local extra field is allowed to differ from the
// central one, so the offset cannot be computed from the directory alone.
#define FF_ZIP_LOCAL_HEADER_SIZE 30
#define FF_ZIP_OFF_NAME_LENGTH 26
#define FF_ZIP_OFF_EXTRA_LENGTH 28
// The central directory. This is what makes an entry reachable without walking everything in front
// of it, and that matters here: framework.jar is 52 MB and its classes2.dex starts 9.6 MB in, so
// looking for a local header by scanning the file costs a pass over everything before it -- once per
// lookup. Measured on the test device, where the sound module resolves seven codes, that was the
// difference between 30 ms and 2 seconds.
#define FF_ZIP_CENTRAL_MAGIC "PK\x01\x02"
#define FF_ZIP_CENTRAL_SIZE 46
#define FF_ZIP_OFF_CENTRAL_METHOD 10
#define FF_ZIP_OFF_CENTRAL_COMPRESSED_SIZE 20
#define FF_ZIP_OFF_CENTRAL_UNCOMPRESSED_SIZE 24
#define FF_ZIP_OFF_CENTRAL_NAME_LENGTH 28
#define FF_ZIP_OFF_CENTRAL_EXTRA_LENGTH 30
#define FF_ZIP_OFF_CENTRAL_COMMENT_LENGTH 32
#define FF_ZIP_OFF_CENTRAL_LOCAL_OFFSET 42
// The end of central directory record, which points at the directory. It is the last thing in the
// file, behind a comment of at most 64 KiB, so it is searched for backwards over that much.
#define FF_ZIP_EOCD_MAGIC "PK\x05\x06"
#define FF_ZIP_EOCD_SIZE 22
#define FF_ZIP_EOCD_MAX_COMMENT 0xffffu
#define FF_ZIP_OFF_EOCD_DIRECTORY_SIZE 12
#define FF_ZIP_OFF_EOCD_DIRECTORY 16
// A size or offset of 0xFFFFFFFF means the real one is in a zip64 extra field. AOSP's framework jars
// are orders of magnitude below that, and carrying the extra field reader for them is not worth it.
#define FF_ZIP_ZIP64_SENTINEL 0xffffffffu
#define FF_ZIP_METHOD_STORED 0
#define FF_ZIP_METHOD_DEFLATED 8
#define FF_DEX_ENTRY "classes.dex"
// How many `classesN.dex` entries a jar is walked for. A jar that spreads its classes over more
// than this still resolves everything in the entries that were walked; only a class that lives in
// one of the ones past the bound is reported as missing.
#define FF_DEX_MAX_ENTRIES 10
#define FF_DEX_MAGIC "dex\n"
static uint16_t dexU16(const uint8_t* p) {
return (uint16_t) ((uint16_t) p[0] | (uint16_t) ((uint16_t) p[1] << 8));
}
static uint32_t dexU32(const uint8_t* p) {
return (uint32_t) p[0] | ((uint32_t) p[1] << 8) | ((uint32_t) p[2] << 16) | ((uint32_t) p[3] << 24);
}
// The dex header points at its tables with offsets that a corrupt or hostile file is free to set
// anywhere, including past the end of the mapping. Testing `end - p` alone does not catch that: once
// `p` is past `end` the subtraction is negative and casting it to size_t turns it into a huge value,
// so the comparison passes and the read goes off the end. Both directions have to be one test.
static bool dexInRange(const uint8_t* dex, const uint8_t* end, const uint8_t* p, size_t size) {
return p >= dex && p <= end && (size_t) (end - p) >= size;
}
// Same, for a table of `count` elements of `elementSize` bytes. `count` is 64-bit because the
// callers add one to an index that came out of the file, and doing that in 32 bits can wrap to zero
// and make the check pass for an index that is not in the table. Dividing rather than multiplying is
// what keeps a hostile `count` from wrapping the size_t on a 32-bit build -- armeabi-v7a is one, and
// there `count * 4` overflows for any count above 0x3FFFFFFF and the check passes again.
static bool dexTableInRange(const uint8_t* dex, const uint8_t* end, const uint8_t* p, uint64_t count, size_t elementSize) {
if (p < dex || p > end) {
return false;
}
return (size_t) (end - p) / elementSize >= count;
}
// LEB128, at most five bytes for the 32-bit values a dex stores this way. `end` bounds every read:
// the lengths and indexes decoded here come out of the file, so a truncated one must not walk off
// the end. A read that runs into `end` yields what was decoded so far, and the callers treat the
// resulting value as untrusted -- it is either compared against another count or used as an index
// that is range checked in turn.
static uint32_t dexUleb128(const uint8_t** p, const uint8_t* end) {
uint32_t value = 0;
for (int shift = 0; shift <= 28; shift += 7) {
if (*p >= end) {
break;
}
const uint8_t byte = *(*p)++;
value |= (uint32_t) (byte & 0x7f) << shift;
if ((byte & 0x80) == 0) {
break;
}
}
return value;
}
// The type tag a dex encoded_value carries in the low five bits of its header byte, per the
// `encoded_value` table of the dex format.
//
// VALUE_NULL and VALUE_BOOLEAN are the trap. Their value *is* the header -- the type for a null,
// the size nibble for a boolean -- and they carry **no payload bytes at all**, so the
// `((size - 1) << 5) | type` rule that describes every other tag does not describe them. Taking the
// size nibble at face value and reading one payload byte walks one byte too far, which pairs every
// field after that with the wrong value. It is not a hypothetical: `IAudioService$Stub` declares ten
// `PERMISSIONS_*` arrays that are all null, right behind its `DESCRIPTOR` string and ahead of its
// 324 transaction constants, and those ten spurious bytes made
// `TRANSACTION_getDevicesForAttributesUnprotected` read as 210 instead of 167 -- a code that reaches
// a different method. `IWifiManager$Stub` has none ahead of the field the wifi module reads, which
// is the only reason this went unnoticed.
//
// Naming VALUE_BOOLEAN also matters for a second reason: it is the tag a `static final boolean`
// carries, and treating it as VALUE_INT would hand back a boolean where an int was asked for.
typedef enum FFDexValueType : uint32_t {
FF_DEX_VALUE_INT = 0x04, // sign-extended; one to four payload bytes
FF_DEX_VALUE_NULL = 0x1e, // no payload
FF_DEX_VALUE_BOOLEAN = 0x1f, // no payload; the boolean is in the header's size nibble
} FFDexValueType;
// encoded_value: one header byte holding ((size - 1) << 5) | type, then that many payload bytes.
//
// The `static_values` array is not a list of ints: `IInterface.DESCRIPTOR` is a `static final
// String` and sits in it, and a real Stub class mixes ints, strings, nulls and booleans. So a value
// whose tag is not VALUE_INT is skipped -- its payload is stepped over to keep the walk aligned with
// the field list -- and only `isInt` reports whether the *wanted* field ended up being one. Failing
// on the first non-int instead would abort on `DESCRIPTOR`, before the transaction constants that
// follow it are ever reached. Measured on a device: `IWifiManager$Stub` carries 347 static fields
// whose values are mostly VALUE_INT but include that string, and `getConnectionInfo` is 98 there.
//
// A payload wider than four bytes is not an int either, and the byte count is what keeps a size
// field read out of a corrupt file from shifting past the end. Reading through 64 bits keeps every
// shift in range while counting down; `end` is what keeps a truncated payload from running off the
// end of the mapping.
static bool dexEncodedValue(const uint8_t** p, const uint8_t* end, int32_t* result, bool* isInt) {
if (*p >= end) {
return false;
}
const uint8_t header = *(*p)++;
const uint32_t type = (uint32_t) (header & 0x1f);
// The two tags whose value is the header byte itself. Returning here is what keeps the walk in
// step; `isInt` is false because neither is an int.
if (type == FF_DEX_VALUE_NULL || type == FF_DEX_VALUE_BOOLEAN) {
*result = 0;
*isInt = false;
return true;
}
const uint32_t size = (uint32_t) (header >> 5) + 1;
if (size > 8) {
return false;
}
uint64_t value = 0;
for (uint32_t i = 0; i < size; ++i) {
if (*p >= end) {
return false;
}
if (i < sizeof(value)) {
value |= (uint64_t) (*(*p)++) << (i * 8);
} else {
(void) *(*p)++;
}
}
*isInt = type == FF_DEX_VALUE_INT && size <= sizeof(uint32_t);
if (*isInt) {
// VALUE_INT is sign-extended from its own width, not from 32 bits: a one-byte -1 is written
// as `21 ff`, one byte of 0xff, and has to come back as -1 rather than 255.
const uint32_t bits = size * 8;
if ((value & (1ull << (bits - 1))) != 0) {
value |= ~((1ull << bits) - 1);
}
}
*result = (int32_t) (uint32_t) value;
return true;
}
// string_data_item: a uleb128 length in UTF-16 code units, then MUTF-8 bytes and a NUL terminator.
// The descriptors and field names looked up here are ASCII, where MUTF-8 and UTF-8 agree, so the
// bytes can be used as they are.
static const char* dexString(const uint8_t* dex, const uint8_t* end, uint32_t index) {
const uint8_t* ids = dex + dexU32(dex + FF_DEX_OFF_STRING_IDS);
// `string_ids_size` is not read: the check is that the entry `index` names lies inside the
// mapping, which is what the read below needs, and the table is the only thing that can say it
// without trusting a second field out of the same file.
if (!dexTableInRange(dex, end, ids, (uint64_t) index + 1, 4)) {
return nullptr;
}
const uint8_t* p = dex + dexU32(ids + (size_t) index * 4);
if (p < dex || p >= end) {
return nullptr;
}
(void) dexUleb128(&p, end);
if (p >= end) {
return nullptr;
}
return memchr(p, '\0', (size_t) (end - p)) != nullptr ? (const char*) p : nullptr;
}
// One dex entry, validated. Every offset in the header comes out of the file, so each table is range
// checked before the walk uses it.
//
// `fieldIds` and `methodIds` are the two tables a class's own lists are resolved through: a
// class_data_item names its members by index into them, and the `name_idx` of the entry is what says
// which member it is. Holding them here rather than re-reading the header offset per class also
// turns the per-member bounds check into a count comparison, which has to happen before the index is
// multiplied out anyway -- on a 32-bit build `index * 8` wraps for a large enough index and the
// range check that follows passes again.
//
// `string_ids` is deliberately not held here: `dexString` checks the one entry it reads, and a table
// nothing walks in order does not need a second check.
typedef struct FFDexTables {
const uint8_t* dex;
const uint8_t* end;
const uint8_t* types;
uint32_t typeCount;
const uint8_t* fieldIds;
uint32_t fieldIdCount;
const uint8_t* methodIds;
uint32_t methodIdCount;
const uint8_t* classes;
uint32_t classCount;
} FFDexTables;
static const char* dexOpen(const uint8_t* dex, size_t size, FFDexTables* tables) {
if (size < FF_DEX_HEADER_SIZE || dexU32(dex + FF_DEX_OFF_ENDIAN_TAG) != FF_DEX_ENDIAN_TAG) {
return "Not a dex file";
}
const uint32_t fileSize = dexU32(dex + FF_DEX_OFF_FILE_SIZE);
if (fileSize > size || fileSize < FF_DEX_HEADER_SIZE) {
return "The dex file size is out of range";
}
const uint8_t* end = dex + fileSize;
const uint8_t* types = dex + dexU32(dex + FF_DEX_OFF_TYPE_IDS);
const uint32_t typeCount = dexU32(dex + FF_DEX_OFF_TYPE_IDS_SIZE);
if (!dexTableInRange(dex, end, types, typeCount, 4)) {
return "The dex type table is out of range";
}
const uint8_t* fieldIds = dex + dexU32(dex + FF_DEX_OFF_FIELD_IDS);
const uint32_t fieldIdCount = dexU32(dex + FF_DEX_OFF_FIELD_IDS_SIZE);
if (!dexTableInRange(dex, end, fieldIds, fieldIdCount, FF_DEX_FIELD_ID_SIZE)) {
return "The dex field table is out of range";
}
const uint8_t* methodIds = dex + dexU32(dex + FF_DEX_OFF_METHOD_IDS);
const uint32_t methodIdCount = dexU32(dex + FF_DEX_OFF_METHOD_IDS_SIZE);
if (!dexTableInRange(dex, end, methodIds, methodIdCount, FF_DEX_METHOD_ID_SIZE)) {
return "The dex method table is out of range";
}
const uint8_t* classes = dex + dexU32(dex + FF_DEX_OFF_CLASS_DEFS);
const uint32_t classCount = dexU32(dex + FF_DEX_OFF_CLASS_DEFS_SIZE);
if (!dexTableInRange(dex, end, classes, classCount, FF_DEX_CLASS_DEF_SIZE)) {
return "The dex class table is out of range";
}
tables->dex = dex;
tables->end = end;
tables->types = types;
tables->typeCount = typeCount;
tables->fieldIds = fieldIds;
tables->fieldIdCount = fieldIdCount;
tables->methodIds = methodIds;
tables->methodIdCount = methodIdCount;
tables->classes = classes;
tables->classCount = classCount;
return nullptr;
}
// The descriptor a type index names, e.g. "I", "[F" or "Landroid/os/Parcel;".
static const char* dexTypeString(const FFDexTables* tables, uint32_t typeIndex) {
if (typeIndex >= tables->typeCount) {
return nullptr;
}
return dexString(tables->dex, tables->end, dexU32(tables->types + (size_t) typeIndex * 4));
}
// One pass over the type table, resolving the type index of every class that still has a request
// open. This pass is what a lookup costs: `framework.jar`'s classes.dex lists 8428 descriptors, each
// one a `string_data_item` read from a different part of a 51 MB mapping. Matching every request
// against the same pass is what makes a batch cheaper than the sum of its fields -- the descriptor is
// read once however many classes are being looked for, and the pass ends as soon as the last request
// has an index rather than at the end of the table.
//
// `typeIndexes` is parallel to `descriptors` and rebuilt here rather than kept across entries: a
// type index is a property of the entry's own table, and the next entry numbers its types
// differently.
static void dexFindTypes(const FFDexTables* tables, const char* const* descriptors, const uint8_t* settled, uint32_t* typeIndexes, uint32_t count) {
uint32_t remaining = 0;
for (uint32_t r = 0; r < count; ++r) {
typeIndexes[r] = UINT32_MAX;
if (!settled[r]) {
++remaining;
}
}
for (uint32_t i = 0; i < tables->typeCount && remaining > 0; ++i) {
const char* descriptor = dexString(tables->dex, tables->end, dexU32(tables->types + (size_t) i * 4));
if (descriptor == nullptr) {
continue;
}
for (uint32_t r = 0; r < count; ++r) {
if (settled[r] || typeIndexes[r] != UINT32_MAX || strcmp(descriptor, descriptors[r]) != 0) {
continue;
}
typeIndexes[r] = i;
--remaining;
}
}
}
// The lists of a class_data_item, each positioned at its first entry.
//
// A class_data_item is `[uleb static_fields_size][uleb instance_fields_size][uleb direct_methods_size]
// [uleb virtual_methods_size]` followed by the four lists in that order, so the start of each list is
// only reachable by stepping over the one in front of it. That is done once here for the three
// readers that each want a different list: the static field reader pairs the first with the class's
// static value array, the write-sequence reader walks the methods, and the field-existence reader
// walks the second.
//
// A field entry is `[uleb field_idx_diff][uleb access_flags]` and a method entry is the same plus a
// trailing `[uleb code_off]`, which is the offset of the code_item holding its instructions -- or 0
// for a method with no body, an abstract or native one.
//
// Each of the four lists is delta-encoded from zero on its own, so the counts are kept apart rather
// than summed: a class routinely has a direct method and a virtual one under the same index, and one
// accumulator carried across the two method lists reads the second list's names out of the wrong end
// of the method table. The field lists are read the same way, which is why the reader of each one
// starts its index at zero rather than continuing from the list in front of it.
typedef struct FFDexClassData {
const uint8_t* staticFields;
const uint8_t* instanceFields;
const uint8_t* methods;
uint32_t staticFieldCount;
uint32_t instanceFieldCount;
uint32_t directMethodCount;
uint32_t virtualMethodCount;
} FFDexClassData;
// Steps over `count` field entries. A list that ends before its own count is a file that is not what
// it claims, not a short list: the count and the entries both come out of the same file.
static bool dexSkipFields(const uint8_t** p, const uint8_t* end, uint32_t count) {
for (uint32_t i = 0; i < count; ++i) {
if (*p >= end) {
return false;
}
(void) dexUleb128(p, end); // field_idx_diff
(void) dexUleb128(p, end); // access_flags
}
return true;
}
static const char* dexReadClassData(const FFDexTables* tables, const uint8_t* classDef, FFDexClassData* data) {
const uint8_t* dex = tables->dex;
const uint8_t* end = tables->end;
const uint32_t classDataOff = dexU32(classDef + FF_DEX_OFF_CLASS_DEF_DATA);
if (classDataOff == 0) {
// A class with no class_data_item declares nothing of its own -- its members are all
// inherited. That is four empty lists, not a file that cannot be read, and the counts below
// are what carry the answer.
data->staticFields = nullptr;
data->instanceFields = nullptr;
data->methods = nullptr;
data->staticFieldCount = 0;
data->instanceFieldCount = 0;
data->directMethodCount = 0;
data->virtualMethodCount = 0;
return nullptr;
}
const uint8_t* p = dex + classDataOff;
// One byte, so that the first uleb128 has something to read.
if (!dexInRange(dex, end, p, 1)) {
return "The dex class data is out of range";
}
data->staticFieldCount = dexUleb128(&p, end);
data->instanceFieldCount = dexUleb128(&p, end);
data->directMethodCount = dexUleb128(&p, end);
data->virtualMethodCount = dexUleb128(&p, end);
data->staticFields = p;
if (!dexSkipFields(&p, end, data->staticFieldCount)) {
return "The dex class data is truncated";
}
data->instanceFields = p;
if (!dexSkipFields(&p, end, data->instanceFieldCount)) {
return "The dex class data is truncated";
}
data->methods = p;
return nullptr;
}
// The class_def_item of the class that `typeIndex` names, or nullptr when this dex only references
// the class.
//
// Having a type index is not the same as being defined here: a dex lists every type it references in
// its type table, so a class that a later entry of the jar defines still has a type index in this
// one, with no class_def_item behind it. Only a class_def_item makes the answer this dex's, and
// without one the caller moves on to the next entry.
static const uint8_t* dexFindClassDef(const FFDexTables* tables, uint32_t typeIndex) {
for (uint32_t i = 0; i < tables->classCount; ++i) {
const uint8_t* classDef = tables->classes + (size_t) i * FF_DEX_CLASS_DEF_SIZE;
if (dexU32(classDef) == typeIndex) {
return classDef;
}
}
return nullptr;
}
// Walks the static field list of the class that `typeIndex` names and the class's static value array
// in step, writing the value of every request for that class whose field name matches. Both lists are
// ordered by field index and cover exactly the same fields -- when they do not, the pairing is not
// trustworthy and nothing is written.
//
// `defined` says whether this dex *defines* the class, which the caller needs: a request the class
// cannot answer has to be left open when the class belongs to a later entry of the jar and closed
// when it does not.
//
// A request this class cannot answer -- a field it does not declare, or one it declares as something
// other than an int -- is closed here rather than reported: the class is this dex's, so no later
// entry of the jar could give it a different answer, and one field the caller can live without must
// not hold back the rest of the batch. It is closed with the sentinel, and what this returns is only
// ever a dex that cannot be read, which is the one thing no later entry can fix.
static const char* dexClassStaticInts(const FFDexTables* tables, uint32_t typeIndex, const FFDexStaticIntRequest* requests, const uint32_t* typeIndexes, uint8_t* settled, uint32_t count, bool* defined) {
const uint8_t* classDef = dexFindClassDef(tables, typeIndex);
*defined = classDef != nullptr;
if (classDef == nullptr) {
return nullptr;
}
const uint8_t* dex = tables->dex;
const uint8_t* end = tables->end;
FFDexClassData classData;
const char* error = dexReadClassData(tables, classDef, &classData);
if (error != nullptr) {
return error;
}
// The static value array is parallel to the static field list and covers exactly the same fields,
// which is what makes walking the two in step below meaningful. It is absent in two cases -- a
// class with no static field, and a class none of whose static fields has a constant value -- and
// the header writes 0 for it in both. The read is skipped rather than made against whatever byte
// a zero offset points at, which is the dex magic and decodes as a four byte int.
const uint8_t* fields = classData.staticFields;
const uint8_t* values = dex;
bool hasValues = false;
if (classData.staticFieldCount > 0) {
const uint32_t staticValuesOff = dexU32(classDef + FF_DEX_OFF_CLASS_DEF_STATIC_VALUES);
if (staticValuesOff != 0) {
values = dex + staticValuesOff;
if (!dexInRange(dex, end, values, 1)) {
return "The dex class data is out of range";
}
if (classData.staticFieldCount != dexUleb128(&values, end)) {
return "The dex static fields and values do not pair up";
}
hasValues = true;
}
}
uint32_t fieldIndex = 0;
for (uint32_t j = 0; j < classData.staticFieldCount; ++j) {
// The two arrays are walked in step, so running out of either one is a truncation.
if (fields >= end || (hasValues && values >= end)) {
return "The dex static field list is truncated";
}
const uint32_t fieldIndexDiff = dexUleb128(&fields, end); // field_idx_diff
if (fieldIndexDiff > UINT32_MAX - fieldIndex) {
return "The dex field index is out of range";
}
fieldIndex += fieldIndexDiff;
(void) dexUleb128(&fields, end); // access_flags
int32_t value = 0;
// Left false when there is no value array, which closes the field with the sentinel below:
// a class with no static value for the field has no answer for it.
bool isInt = false;
if (hasValues && !dexEncodedValue(&values, end, &value, &isInt)) {
return "The dex static value list is truncated";
}
if (fieldIndex >= tables->fieldIdCount) {
return "The dex field table is out of range";
}
const char* name = dexString(dex, end, dexU32(tables->fieldIds + (size_t) fieldIndex * FF_DEX_FIELD_ID_SIZE + FF_DEX_OFF_FIELD_ID_NAME));
if (name == nullptr) {
continue;
}
for (uint32_t r = 0; r < count; ++r) {
if (settled[r] || typeIndexes[r] != typeIndex || strcmp(name, requests[r].fieldName) != 0) {
continue;
}
if (!isInt) {
// The field exists but is not an int, so its value is not the constant asked for.
// Closed the way a field the class does not declare is, because it is this dex's
// answer too. The check is here rather than on the first non-int in the list because
// the list legitimately holds strings (`DESCRIPTOR`) and booleans.
*requests[r].result = FF_DEX_STATIC_INT_UNRESOLVED;
settled[r] = 1;
continue;
}
*requests[r].result = value;
settled[r] = 1;
}
}
// The class is defined here, so a request of its own that went unanswered is one whose field the
// class does not declare as a static field. Closed here for the same reason as above.
for (uint32_t r = 0; r < count; ++r) {
if (!settled[r] && typeIndexes[r] == typeIndex) {
*requests[r].result = FF_DEX_STATIC_INT_UNRESOLVED;
settled[r] = 1;
}
}
return nullptr;
}
// ---------------------------------------------------------------------------------------------
// Walking a method's instructions
// ---------------------------------------------------------------------------------------------
// The width of every instruction, in code units. This is ART's `Instruction::SizeInCodeUnits`, and
// the walk below cannot stay on the instruction grid without it -- a walk that leaves the grid still
// produces a plausible-looking list of fields, which is what makes this worth a table rather than a
// guess. Unused and odex-only opcodes are 1 rather than 0, so that a byte which is not an instruction
// at all moves the walk on instead of leaving it where it is.
static const uint8_t dexOpcodeSize[256] = {
/* 00 */ 1, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 1, 1, 1, 1, 1,
/* 10 */ 1, 1, 1, 2, 3, 2, 2, 3, 5, 2, 2, 3, 2, 1, 1, 2,
/* 20 */ 2, 1, 2, 2, 3, 3, 3, 1, 1, 2, 3, 3, 3, 2, 2, 2,
/* 30 */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1,
/* 40 */ 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
/* 50 */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
/* 60 */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3,
/* 70 */ 3, 3, 3, 1, 3, 3, 3, 3, 3, 1, 1, 1, 1, 1, 1, 1,
/* 80 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
/* 90 */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
/* a0 */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
/* b0 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
/* c0 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
/* d0 */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
/* e0 */ 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
/* f0 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 4, 3, 3, 2, 2,
};
// The code_item at `codeOff`, with its instruction array range checked. nullptr is one that cannot
// be read: an offset outside the mapping, or an instruction count that runs past the end.
static const uint8_t* dexReadCode(const FFDexTables* tables, uint32_t codeOff) {
const uint8_t* code = tables->dex + codeOff;
if (!dexInRange(tables->dex, tables->end, code, FF_DEX_OFF_CODE_INSNS)) {
return nullptr;
}
const uint32_t insnsSize = dexU32(code + FF_DEX_OFF_CODE_INSNS_SIZE);
if (!dexTableInRange(tables->dex, tables->end, code + FF_DEX_OFF_CODE_INSNS, insnsSize, 2)) {
return nullptr;
}
return code;
}
// Records `name` and `type` as the next field of a write sequence, unless the name is already in it.
//
// An array field is read twice: once for its length, which is the count written ahead of the elements,
// and again inside the loop that writes them. A class cannot declare two instance fields under one
// name, so a repeat is always that reload, and the first read is where the field belongs in the
// sequence.
//
// A name or descriptor longer than the caller's buffer is refused rather than truncated: half a name
// matches nothing, which is indistinguishable from the field not being in the class at all. Refusing
// one field leaves the whole sequence unwritten, because a caller walking a sequence with a hole in
// it would read every field behind the hole from the wrong place.
static bool dexParcelFieldAdd(FFDexParcelField* fields, uint32_t* count, uint32_t capacity, const char* name, const char* type) {
for (uint32_t i = 0; i < *count; ++i) {
if (strcmp(fields[i].name, name) == 0) {
return true;
}
}
if (*count >= capacity) {
return false;
}
const size_t nameSize = strlen(name) + 1;
const size_t typeSize = strlen(type) + 1;
if (nameSize > FF_DEX_PARCEL_FIELD_NAME_MAX || typeSize > FF_DEX_PARCEL_FIELD_TYPE_MAX) {
return false;
}
memcpy(fields[*count].name, name, nameSize);
memcpy(fields[*count].type, type, typeSize);
++*count;
return true;
}
// Walks the instruction grid of a code_item and records every instance field the method reads, in the
// order it reads them -- which is the order `writeToParcel` puts them on the wire.
//
// The grid is followed by opcode width, so the walk only ever lands on an instruction start, and it
// stops at the first payload pseudo-instruction. A `switch` or a `fill-array-data` leaves one behind
// every reachable instruction; its contents are data rather than instructions, and stepping into it
// desynchronises the grid for good.
//
// `iget*` and `iput*` share a format and put the field index in the code unit behind the opcode in
// both, which is what lets one test cover the whole range. A `writeToParcel` does not store to a
// field, so the `iput` half is never taken -- it is in the range because splitting it would be a
// claim about the method that the opcode does not make.
static const char* dexWalkParcelFields(const FFDexTables* tables, const uint8_t* code, FFDexParcelField* fields, uint32_t capacity, uint32_t* count) {
*count = 0;
const uint8_t* dex = tables->dex;
const uint8_t* end = tables->end;
const uint32_t insnsSize = dexU32(code + FF_DEX_OFF_CODE_INSNS_SIZE);
const uint8_t* insns = code + FF_DEX_OFF_CODE_INSNS;
for (uint32_t i = 0; i < insnsSize;) {
const uint16_t unit = dexU16(insns + (size_t) i * 2);
// The three payload pseudo-instructions put their ident in the high byte and zero in the low
// one. `nop` is the one instruction whose low byte is zero as well, and it is told apart by
// the whole code unit being zero. Testing the high byte alone is what gets this wrong: a
// `sparse-switch` over register 2 encodes as 0x022c, which is an instruction, not a payload.
if ((unit & 0xFF) == 0 && unit != 0) {
break;
}
const uint32_t opcode = unit & 0xFF;
if (opcode >= FF_DEX_OP_FIELD_FIRST && opcode <= FF_DEX_OP_FIELD_LAST) {
if (i + 1 >= insnsSize) {
break;
}
const uint32_t fieldIndex = dexU16(insns + ((size_t) i + 1) * 2);
if (fieldIndex >= tables->fieldIdCount) {
return "The dex field table is out of range";
}
const uint8_t* fieldId = tables->fieldIds + (size_t) fieldIndex * FF_DEX_FIELD_ID_SIZE;
const char* name = dexString(dex, end, dexU32(fieldId + FF_DEX_OFF_FIELD_ID_NAME));
const char* type = dexTypeString(tables, dexU16(fieldId + FF_DEX_OFF_FIELD_ID_TYPE));
if (name == nullptr || type == nullptr) {
return "The dex field is not resolvable";
}
if (!dexParcelFieldAdd(fields, count, capacity, name, type)) {
FF_DEBUG("\"%s\" (%s) does not fit a sequence of %u fields", name, type, capacity);
*count = 0;
return nullptr;
}
}
i += dexOpcodeSize[opcode];
}
return nullptr;
}
// Walks the instance field list of one `class_data_item` and writes `true` for every name it holds.
// The list is the caller's, so this answers one request's worth of names or several.
//
// This is the question a Parcelable whose layout is not field-driven needs: `DisplayCutout` reads
// everything through compiler-synthesised accessors, so the set of fields its class declares is the
// only thing that says how long its body is. It is asked as part of a write-sequence request rather
// than by a call of its own -- the list is already positioned by the `class_data_item` the sequence
// comes out of, and a separate call would repeat the whole walk to reach it.
static const char* dexMatchInstanceFields(const FFDexTables* tables, const uint8_t* fields, uint32_t fieldCount, const char* const* names, bool* results, uint32_t nameCount) {
const uint8_t* dex = tables->dex;
const uint8_t* end = tables->end;
uint32_t fieldIndex = 0;
for (uint32_t j = 0; j < fieldCount; ++j) {
if (fields >= end) {
return "The dex class data is truncated";
}
const uint32_t fieldIndexDiff = dexUleb128(&fields, end); // field_idx_diff
if (fieldIndexDiff > UINT32_MAX - fieldIndex) {
return "The dex field index is out of range";
}
fieldIndex += fieldIndexDiff;
(void) dexUleb128(&fields, end); // access_flags
if (fieldIndex >= tables->fieldIdCount) {
return "The dex field table is out of range";
}
const char* name = dexString(dex, end, dexU32(tables->fieldIds + (size_t) fieldIndex * FF_DEX_FIELD_ID_SIZE + FF_DEX_OFF_FIELD_ID_NAME));
if (name == nullptr) {
continue;
}
for (uint32_t i = 0; i < nameCount; ++i) {
if (!results[i] && strcmp(name, names[i]) == 0) {
results[i] = true;
}
}
}
return nullptr;
}
// Walks the method list of the class that `typeIndex` names, and for every request whose method name
// it holds walks that method's instructions. `defined` is what `dexClassStaticInts` reports, for the
// same reason.
//
// A request the class does not answer is closed with a count of 0 -- the count a caller reads as
// "this build does not write that class", which is the truth: the class is this dex's, so no later
// entry of the jar has the method either.
static const char* dexClassParcelFields(const FFDexTables* tables, uint32_t typeIndex, const FFDexParcelRequest* requests, const uint32_t* typeIndexes, uint8_t* settled, uint32_t count, bool* defined) {
const uint8_t* classDef = dexFindClassDef(tables, typeIndex);
*defined = classDef != nullptr;
if (classDef == nullptr) {
return nullptr;
}
const uint8_t* dex = tables->dex;
const uint8_t* end = tables->end;
FFDexClassData classData;
const char* error = dexReadClassData(tables, classDef, &classData);
if (error != nullptr) {
return error;
}
// The instance fields first, out of the list the `class_data_item` has already positioned. A
// request that asked for them is answered here whether or not it also asked for a sequence, and
// one that asked for neither is settled by the loop at the end like any other.
for (uint32_t r = 0; r < count; ++r) {
if (settled[r] || typeIndexes[r] != typeIndex || requests[r].instanceFieldCount == 0) {
continue;
}
error = dexMatchInstanceFields(
tables, classData.instanceFields, classData.instanceFieldCount,
requests[r].instanceFieldNames, requests[r].instanceFieldResults, requests[r].instanceFieldCount
);
if (error != nullptr) {
return error;
}
}
const uint8_t* methods = classData.methods;
// Two runs, each delta-encoded from zero on its own -- so the index is reset per run rather than
// carried across, and a direct method and a virtual one under the same index both resolve.
for (uint32_t list = 0; list < 2; ++list) {
const uint32_t listCount = list == 0 ? classData.directMethodCount : classData.virtualMethodCount;
uint32_t methodIndex = 0;
for (uint32_t j = 0; j < listCount; ++j) {
if (methods >= end) {
return "The dex class data is truncated";
}
const uint32_t methodIndexDiff = dexUleb128(&methods, end); // method_idx_diff
if (methodIndexDiff > UINT32_MAX - methodIndex) {
return "The dex method index is out of range";
}
methodIndex += methodIndexDiff;
(void) dexUleb128(&methods, end); // access_flags
const uint32_t codeOff = dexUleb128(&methods, end);
if (methodIndex >= tables->methodIdCount) {
return "The dex method table is out of range";
}
const char* name = dexString(dex, end, dexU32(tables->methodIds + (size_t) methodIndex * FF_DEX_METHOD_ID_SIZE + FF_DEX_OFF_METHOD_ID_NAME));
if (name == nullptr) {
continue;
}
for (uint32_t r = 0; r < count; ++r) {
// A request that left the sequence out is one that only wanted the instance fields,
// which are already written; it has no method name to match against.
if (settled[r] || typeIndexes[r] != typeIndex || requests[r].methodName == nullptr
|| strcmp(name, requests[r].methodName) != 0) {
continue;
}
// A method with no body -- abstract, or native -- writes nothing, and the count stays 0.
const uint8_t* code = codeOff == 0 ? nullptr : dexReadCode(tables, codeOff);
if (codeOff != 0 && code == nullptr) {
return "The dex code is out of range";
}
if (code != nullptr) {
error = dexWalkParcelFields(tables, code, requests[r].fields, requests[r].capacity, requests[r].count);
if (error != nullptr) {
return error;
}
}
settled[r] = 1;
}
}
}
// The class is defined here, so a request of its own that went unanswered is one whose method the
// class does not declare. The count it already holds is the 0 that says so.
for (uint32_t r = 0; r < count; ++r) {
if (!settled[r] && typeIndexes[r] == typeIndex) {
settled[r] = 1;
}
}
return nullptr;
}
// ---------------------------------------------------------------------------------------------
// Locating classes.dex
// ---------------------------------------------------------------------------------------------
typedef struct FFDexMapping {
uint8_t* mapped; // the jar, as mapped; nullptr once released
size_t mappedSize;
} FFDexMapping;
static void wrapDexMapping(FFDexMapping* mapping) {
assert(mapping);
if (mapping->mapped != nullptr) {
munmap(mapping->mapped, mapping->mappedSize);
}
}
// Maps the jar read-only. The pages the walk touches are the only ones faulted in, which is what
// keeps a 52 MB framework.jar off the heap and out of the cost of a lookup. The mapping outlives the
// descriptor, so the descriptor is closed before this returns.
static const char* dexMapJar(const char* jarPath, FFDexMapping* mapping) {
FF_AUTO_CLOSE_FD int fd = open(jarPath, O_RDONLY | O_CLOEXEC);
if (fd < 0) {
FF_DEBUG("open(%s) failed: %s", jarPath, strerror(errno));
return "open(jar) failed";
}
struct stat st = {};
const int statStatus = fstat(fd, &st);
if (statStatus != 0 || st.st_size <= 0) {
FF_DEBUG("fstat(%s) reported %s (%lld bytes)", jarPath,
statStatus != 0 ? strerror(errno) : "an empty file", (long long) st.st_size);
return "fstat(jar) failed";
}
mapping->mappedSize = (size_t) st.st_size;
mapping->mapped = mmap(nullptr, mapping->mappedSize, PROT_READ, MAP_PRIVATE, fd, 0);
if (mapping->mapped == MAP_FAILED) {
FF_DEBUG("mmap(%s, %zu bytes) failed: %s", jarPath, mapping->mappedSize, strerror(errno));
mapping->mapped = nullptr;
return "mmap(jar) failed";
}
return nullptr;
}
// Locates an entry through the jar's central directory.
//
// The sizes and the compression method are read from the directory rather than from the entry's own
// local header, which is the more reliable of the two: a local header is allowed to leave its sizes
// at zero and put them in a data descriptor behind the payload, and an archive writer is free to
// choose that. Only the local header's two length fields are used, because they are what says where
// the payload begins -- the local extra field does not have to match the central one.
static const char* findDexEntry(const uint8_t* jar, size_t jarSize, const char* entry, const uint8_t** data, size_t* dataSize, uint32_t* uncompressedSize, uint16_t* method) {
const size_t entryLength = strlen(entry);
if (jarSize < FF_ZIP_EOCD_SIZE) {
return "The jar is too small to be a zip";
}
// The record is the last thing in the file, so it is searched for backwards -- over at most the
// 64 KiB a trailing comment can take, not over the whole archive.
const size_t newest = jarSize - FF_ZIP_EOCD_SIZE;
const size_t oldest = jarSize > FF_ZIP_EOCD_MAX_COMMENT + FF_ZIP_EOCD_SIZE
? jarSize - FF_ZIP_EOCD_MAX_COMMENT - FF_ZIP_EOCD_SIZE
: 0;
size_t eocd = SIZE_MAX;
for (size_t i = newest;; --i) {
if (memcmp(jar + i, FF_ZIP_EOCD_MAGIC, sizeof(FF_ZIP_EOCD_MAGIC) - 1) == 0) {
eocd = i;
break;
}
if (i == oldest) {
// The walk stops here rather than at zero: a `size_t` countdown that has to reach zero
// before it stops would wrap and read the whole file backwards, which is the very scan
// this is here to avoid.
break;
}
}
if (eocd == SIZE_MAX) {
FF_DEBUG("No end-of-central-directory record in the last %zu bytes of a %zu byte jar",
newest - oldest + FF_ZIP_EOCD_SIZE, jarSize);
return "The jar has no zip directory";
}
const uint32_t directorySize = dexU32(jar + eocd + FF_ZIP_OFF_EOCD_DIRECTORY_SIZE);
const uint32_t directoryOffset = dexU32(jar + eocd + FF_ZIP_OFF_EOCD_DIRECTORY);
if (directoryOffset > jarSize || directorySize > jarSize - directoryOffset) {
return "The zip directory is out of range";
}
const size_t directoryEnd = (size_t) directoryOffset + directorySize;
for (size_t offset = directoryOffset; offset + FF_ZIP_CENTRAL_SIZE <= directoryEnd;) {
if (memcmp(jar + offset, FF_ZIP_CENTRAL_MAGIC, sizeof(FF_ZIP_CENTRAL_MAGIC) - 1) != 0) {
return "A zip directory entry is malformed";
}
const uint16_t nameLength = dexU16(jar + offset + FF_ZIP_OFF_CENTRAL_NAME_LENGTH);
const uint16_t extraLength = dexU16(jar + offset + FF_ZIP_OFF_CENTRAL_EXTRA_LENGTH);
const uint16_t commentLength = dexU16(jar + offset + FF_ZIP_OFF_CENTRAL_COMMENT_LENGTH);
const size_t entrySize = FF_ZIP_CENTRAL_SIZE + (size_t) nameLength + (size_t) extraLength + (size_t) commentLength;
if (entrySize > directoryEnd - offset) {
FF_DEBUG("A zip entry claims %zu bytes but only %zu remain in the directory",
entrySize, (size_t) (directoryEnd - offset));
return "A zip directory entry runs past the directory";
}
if (nameLength == entryLength && memcmp(jar + offset + FF_ZIP_CENTRAL_SIZE, entry, entryLength) == 0) {
const uint32_t compressedSize = dexU32(jar + offset + FF_ZIP_OFF_CENTRAL_COMPRESSED_SIZE);
const uint32_t uncompressed = dexU32(jar + offset + FF_ZIP_OFF_CENTRAL_UNCOMPRESSED_SIZE);
const uint32_t localOffset = dexU32(jar + offset + FF_ZIP_OFF_CENTRAL_LOCAL_OFFSET);
if (compressedSize == FF_ZIP_ZIP64_SENTINEL || uncompressed == FF_ZIP_ZIP64_SENTINEL || localOffset == FF_ZIP_ZIP64_SENTINEL) {
FF_DEBUG("The dex entry is zip64: compressed=%u, uncompressed=%u, localOffset=%u",
compressedSize, uncompressed, localOffset);
return "A dex entry is stored as zip64, which is not supported";
}
if (localOffset > jarSize || FF_ZIP_LOCAL_HEADER_SIZE > jarSize - localOffset) {
return "A zip local header is out of range";
}
const uint16_t localNameLength = dexU16(jar + localOffset + FF_ZIP_OFF_NAME_LENGTH);
const uint16_t localExtraLength = dexU16(jar + localOffset + FF_ZIP_OFF_EXTRA_LENGTH);
const size_t headerSize = FF_ZIP_LOCAL_HEADER_SIZE + (size_t) localNameLength + (size_t) localExtraLength;
if (headerSize > jarSize - localOffset || compressedSize > jarSize - localOffset - headerSize) {
return "The zip entry bounds are out of range";
}
*method = dexU16(jar + offset + FF_ZIP_OFF_CENTRAL_METHOD);
*uncompressedSize = uncompressed;
*data = jar + localOffset + headerSize;
*dataSize = compressedSize;
return nullptr;
}
offset += entrySize;
}
FF_DEBUG("No entry in the %zu byte jar is named %s", jarSize, entry);
return "The jar has no usable dex entry";
}
// Fallback for a jar whose entry is STORED but whose header has no sizes: the dex magic is then a
// literal run of bytes in the file, and the header that follows it validates or it does not. It
// only ever locates the *first* dex in the jar, so it is a fallback for `classes.dex` alone.
static const uint8_t* findDexMagic(const uint8_t* jar, size_t jarSize, size_t* dexSize) {
for (size_t i = 0; i + FF_DEX_HEADER_SIZE <= jarSize; ++i) {
if (memcmp(jar + i, FF_DEX_MAGIC, sizeof(FF_DEX_MAGIC) - 1) != 0) {
continue;
}
const uint8_t* dex = jar + i;
const uint32_t fileSize = dexU32(dex + FF_DEX_OFF_FILE_SIZE);
if (dexU32(dex + FF_DEX_OFF_ENDIAN_TAG) != FF_DEX_ENDIAN_TAG) {
continue;
}
if (fileSize < FF_DEX_HEADER_SIZE || fileSize > jarSize - i) {
continue;
}
*dexSize = fileSize;
return dex;
}
return nullptr;
}
#ifdef FF_HAVE_ZLIB
static const char* inflateDex(const uint8_t* data, size_t dataSize, uint32_t uncompressedSize, uint8_t** out) {
const char* error = ffZlibLoad(FF_ZLIB_INFLATE);
if (error != nullptr) {
FF_DEBUG("The dex cannot be inflated: %s", error);
return error;
}
// Released by the cleanup on every path out below; ownership moves to the caller by clearing
// the pointer, which is also why no failure path has to free it by hand.
FF_AUTO_FREE uint8_t* buffer = malloc(uncompressedSize);
if (buffer == nullptr) {
FF_DEBUG("malloc(%u) for the inflated dex failed", uncompressedSize);
return "malloc failed";
}
// `uncompress` is not usable here: a zip entry holds a raw deflate stream, without the two byte
// zlib header that entry point insists on. A negative window size tells inflate to skip the
// header, which is what the zip format expects.
z_stream stream = {};
stream.next_in = (Bytef*) data;
stream.avail_in = (uInt) dataSize;
stream.next_out = buffer;
stream.avail_out = (uInt) uncompressedSize;
const int initStatus = inflateInit2(&stream, -MAX_WBITS);
if (initStatus != Z_OK) {
FF_DEBUG("inflateInit2() failed: zlib status %d", initStatus);
return "inflateInit2 failed";
}
const int status = inflate(&stream, Z_FINISH);
inflateEnd(&stream);
if (status != Z_STREAM_END || stream.total_out != (uLong) uncompressedSize) {
FF_DEBUG("inflate() returned %d, produced %lu of %u bytes",
status, stream.total_out, uncompressedSize);
return "Inflating the dex failed";
}
*out = buffer;
buffer = nullptr; // the caller owns it from here
return nullptr;
}
#endif
// The dex bytes of one jar entry, and the validated tables of the dex they hold.
typedef struct FFDexEntry {
const uint8_t* data;
size_t size;
FFDexTables tables;
} FFDexEntry;
// Locates `classesN.dex`, inflates it when the entry is deflated, and validates the header.
//
// `present` reports whether the jar holds the entry at all. A jar spreads its classes over as many
// entries as it needs and then simply runs out, so an entry that is not there ends the walk rather
// than failing it -- except for the first, which every jar has and which is where the magic scan
// stands in for a directory that cannot be read. `inflated` receives the buffer the caller has to
// release when the entry needed one, and is left alone otherwise.
static const char* dexOpenEntry(const FFDexMapping* mapping, uint32_t index, bool* present, uint8_t** inflated, FFDexEntry* entry) {
// Wide enough for `classes999.dex`, which is the longest name `FF_DEX_MAX_ENTRIES` can ask for.
char name[sizeof("classes999.dex")];
if (index == 1) {
memcpy(name, FF_DEX_ENTRY, sizeof(FF_DEX_ENTRY));
} else {
(void) snprintf(name, sizeof(name), "classes%u.dex", index);
}
*present = true;
const uint8_t* data = nullptr;
size_t dataSize = 0;
uint32_t uncompressedSize = 0;
uint16_t method = FF_ZIP_METHOD_STORED;
if (findDexEntry(mapping->mapped, mapping->mappedSize, name, &data, &dataSize, &uncompressedSize, &method) != nullptr) {
if (index > 1) {
// The magic scan only ever finds the first dex, so it cannot stand in for a later entry:
// running past the end of the entries that exist ends the walk.
*present = false;
return nullptr;
}
data = findDexMagic(mapping->mapped, mapping->mappedSize, &dataSize);
if (data == nullptr) {
FF_DEBUG("Neither %s nor a dex magic signature is in the %zu byte jar", name, mapping->mappedSize);
return "No dex in the jar";
}
} else if (method == FF_ZIP_METHOD_DEFLATED) {
#ifdef FF_HAVE_ZLIB
const char* error = inflateDex(data, dataSize, uncompressedSize, inflated);
if (error != nullptr) {
return error;
}
data = *inflated;
dataSize = uncompressedSize;
#else
(void) inflated; // the parameter is only written on the inflating path
return "The jar deflates its dex entries and fastfetch was built without zlib";
#endif
} else if (method != FF_ZIP_METHOD_STORED) {
FF_DEBUG("%s uses compression method %u, only %u (stored) and %u (deflated) are handled",
name, method, FF_ZIP_METHOD_STORED, FF_ZIP_METHOD_DEFLATED);
return "A dex entry uses an unsupported compression method";
}
// A STORED entry needs no further work: `dataSize` already holds the `compressedSize` that
// `findDexEntry` checked against the mapping, and for a STORED entry the payload is the file
// itself. The header's `uncompressedSize` is deliberately not used for it -- nothing bounds that
// value, so a corrupt one reaches past the end of the mapping, which is exactly what the dex
// header would then be validated against.
entry->data = data;
entry->size = dataSize;
return dexOpen(entry->data, entry->size, &entry->tables);
}
const char* ffDexStaticInts(const char* jarPath, const FFDexStaticIntRequest* requests, uint32_t count) {
// Every request is written the sentinel before anything can fail, so that a caller reads a result
// rather than whatever its own stack held there, however this returns. Nothing below has to
// remember to do it on its way out.
for (uint32_t r = 0; r < count; ++r) {
*requests[r].result = FF_DEX_STATIC_INT_UNRESOLVED;
}
if (count == 0) {
return nullptr;
}
[[gnu::cleanup(wrapDexMapping)]] FFDexMapping mapping = {};
const char* error = dexMapJar(jarPath, &mapping);
if (error != nullptr) {
return error;
}
// One byte per request saying whether it is closed -- answered, or given up on -- the type index
// it was found under in the entry being walked, and the descriptor that index was found for. All
// three are sized from `count` rather than from a fixed bound, and all three are released by
// their cleanup however this function returns.
FF_AUTO_FREE uint8_t* settled = calloc(count, 1);
FF_AUTO_FREE uint32_t* typeIndexes = malloc((size_t) count * sizeof(uint32_t));
FF_AUTO_FREE const char** descriptors = malloc((size_t) count * sizeof(const char*));
if (settled == nullptr || typeIndexes == nullptr || descriptors == nullptr) {
FF_DEBUG("Allocating the tables for %u static int requests failed", count);
return "malloc failed";
}
for (uint32_t r = 0; r < count; ++r) {
descriptors[r] = requests[r].classDescriptor;
}
// A jar spreads its classes over `classes.dex`, `classes2.dex`, `classes3.dex`, ... and the
// class being looked for is in whichever one the build put it in: `IAudioService$Stub` is in the
// second dex of framework.jar, while `IWifiManager$Stub` is in the first of framework-wifi.jar.
// The entries are therefore tried in order, and the first one that defines the class answers.
// The count is a bound on the walk, not a claim about the jar -- AOSP has never shipped more
// than a handful, and a class that is in none of them is left unanswered either way.
//
// The walk ends as soon as the last request is closed, so an entry that holds every class asked
// for -- which is what the modules here ask for, one class per interface -- costs one entry read
// and one pass over its type table.
uint32_t pending = count;
for (uint32_t index = 1; index <= FF_DEX_MAX_ENTRIES && pending > 0; ++index) {
// The decompressed dex, when the entry needed one. The cleanup releases it at the end of
// the iteration, so every entry gets a buffer of its own and no way out of the loop -- an
// early return included -- can leak one.
FF_AUTO_FREE uint8_t* inflated = nullptr;
FFDexEntry entry = {};
bool present = false;
error = dexOpenEntry(&mapping, index, &present, &inflated, &entry);
if (error != nullptr) {
// The dex cannot be read at all, which no later entry can fix.
return error;
}
if (!present) {
break;
}
dexFindTypes(&entry.tables, descriptors, settled, typeIndexes, count);
// Every class this entry defines is walked once, however many of its fields were asked for.
// A request whose class is not in this entry's type table keeps `UINT32_MAX` and is left for
// the next entry; one whose class is only referenced here is left the same way by the walk
// below, which is what `defined` reports.
for (uint32_t r = 0; r < count; ++r) {
if (settled[r] || typeIndexes[r] == UINT32_MAX) {
continue;
}
const uint32_t typeIndex = typeIndexes[r];
bool defined = false;
error = dexClassStaticInts(&entry.tables, typeIndex, requests, typeIndexes, settled, count, &defined);
if (error != nullptr) {
// The dex cannot be read, which no later entry can fix.
return error;
}
if (!defined) {
// Only referenced here, so the class is left for the next entry -- and the requests
// that share it are marked as walked, since one lookup of a class answers all of
// them and the class table should not be scanned once per field.
for (uint32_t s = r; s < count; ++s) {
if (typeIndexes[s] == typeIndex) {
typeIndexes[s] = UINT32_MAX;
}
}
}
}
pending = 0;
for (uint32_t r = 0; r < count; ++r) {
if (!settled[r]) {
++pending;
}
}
}
// What is still open is a class no entry of the jar defines. That is an answer like any other --
// the results already hold the sentinel, written before the walk began -- so the only thing left
// is to say so for a debug build, where the sentinel on its own does not tell "this build has no
// such method" apart from "the reader went wrong".
for (uint32_t r = 0; r < count; ++r) {
if (*requests[r].result == FF_DEX_STATIC_INT_UNRESOLVED) {
FF_DEBUG("\"%s\" is not a static int field of \"%s\"", requests[r].fieldName, requests[r].classDescriptor);
}
}
return nullptr;
}
const char* ffDexParcelFields(const char* jarPath, const FFDexParcelRequest* requests, uint32_t count) {
// Every request is written its answers before anything can fail, so that a caller reads an answer
// rather than whatever its own stack held there, however this returns. The instance fields go
// first: `false` is what both "the class does not declare it" and "the jar cannot be read" mean,
// and the walk below only ever turns one of them true.
for (uint32_t r = 0; r < count; ++r) {
if (requests[r].methodName != nullptr) {
*requests[r].count = 0;
}
for (uint32_t i = 0; i < requests[r].instanceFieldCount; ++i) {
requests[r].instanceFieldResults[i] = false;
}
}
if (count == 0) {
return nullptr;
}
[[gnu::cleanup(wrapDexMapping)]] FFDexMapping mapping = {};
const char* error = dexMapJar(jarPath, &mapping);
if (error != nullptr) {
return error;
}
FF_AUTO_FREE uint8_t* settled = calloc(count, 1);
FF_AUTO_FREE uint32_t* typeIndexes = malloc((size_t) count * sizeof(uint32_t));
FF_AUTO_FREE const char** descriptors = malloc((size_t) count * sizeof(const char*));
if (settled == nullptr || typeIndexes == nullptr || descriptors == nullptr) {
FF_DEBUG("Allocating the tables for %u parcel field requests failed", count);
return "malloc failed";
}
for (uint32_t r = 0; r < count; ++r) {
descriptors[r] = requests[r].classDescriptor;
}
uint32_t pending = count;
for (uint32_t index = 1; index <= FF_DEX_MAX_ENTRIES && pending > 0; ++index) {
FF_AUTO_FREE uint8_t* inflated = nullptr;
FFDexEntry entry = {};
bool present = false;
error = dexOpenEntry(&mapping, index, &present, &inflated, &entry);
if (error != nullptr) {
// The dex cannot be read at all, which no later entry can fix.
return error;
}
if (!present) {
break;
}
dexFindTypes(&entry.tables, descriptors, settled, typeIndexes, count);
for (uint32_t r = 0; r < count; ++r) {
if (settled[r] || typeIndexes[r] == UINT32_MAX) {
continue;
}
const uint32_t typeIndex = typeIndexes[r];
bool defined = false;
error = dexClassParcelFields(&entry.tables, typeIndex, requests, typeIndexes, settled, count, &defined);
if (error != nullptr) {
return error;
}
if (!defined) {
for (uint32_t s = r; s < count; ++s) {
if (typeIndexes[s] == typeIndex) {
typeIndexes[s] = UINT32_MAX;
}
}
}
}
pending = 0;
for (uint32_t r = 0; r < count; ++r) {
if (!settled[r]) {
++pending;
}
}
}
// A request still holding 0 is one whose class no entry of the jar defines, or whose class does
// not declare the method. Both are the same answer -- this build does not write that class -- and
// for a debug build it is worth saying which of the two it was, since the count on its own does
// not tell "this build has no such method" apart from "the reader went wrong". The same goes for
// an instance field left `false`.
for (uint32_t r = 0; r < count; ++r) {
if (requests[r].methodName != nullptr && *requests[r].count == 0) {
FF_DEBUG("The write sequence of \"%s.%s\" is not in the jar", requests[r].classDescriptor, requests[r].methodName);
}
for (uint32_t i = 0; i < requests[r].instanceFieldCount; ++i) {
if (!requests[r].instanceFieldResults[i]) {
FF_DEBUG("\"%s\" is not an instance field of \"%s\"", requests[r].instanceFieldNames[i], requests[r].classDescriptor);
}
}
}
return nullptr;
}