java/com/facebook/soloader/MinElf.java (218 lines of code) (raw):

/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.facebook.soloader; import android.util.Log; import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.channels.ClosedByInterruptException; /** * Extract SoLoader bootstrap information from an ELF file. This is not a general purpose ELF * library. * * <p>See specification at http://www.sco.com/developers/gabi/latest/contents.html. You will not be * able to verify the operation of the functions below without having read the ELF specification. */ public final class MinElf { private static final String TAG = "MinElf"; public static enum ISA { NOT_SO("not_so"), X86("x86"), ARM("armeabi-v7a"), X86_64("x86_64"), AARCH64("arm64-v8a"), OTHERS("others"); private final String value; ISA(final String value) { this.value = value; } @Override public String toString() { return value; } }; public static final int ELF_MAGIC = 0x464c457f; public static final int DT_NULL = 0; public static final int DT_NEEDED = 1; public static final int DT_STRTAB = 5; public static final int PT_LOAD = 1; public static final int PT_DYNAMIC = 2; public static final int PN_XNUM = 0xFFFF; public static String[] extract_DT_NEEDED(File elfFile) throws IOException { try (ElfFileChannel fc = new ElfFileChannel(elfFile)) { return extract_DT_NEEDED(fc); } } /** * Treating {@code fc} as an ELF file, extract all the DT_NEEDED entries from its dynamic section. * * @param fc ElfFileChannel referring to ELF file * @return Array of strings, one for each DT_NEEDED entry, in file order */ private static String[] extract_DT_NEEDED_with_retries(ElfFileChannel fc) throws IOException { // An ElfFileChannel can be interrupted since it uses a FileChannel. // If it's interrupted, we will retry, we just need to reopen the FileChannel int failureCount = 0; while (true) { try { return extract_DT_NEEDED_no_retries(fc); } catch (ClosedByInterruptException e) { // Make sure we don't loop infinitely if (++failureCount > 4) { throw e; } // Some other thread interrupted us. We need to clear the interrupt // flag (via calling Thread.interrupted()) and try again. This is // especially important since this is often used within the context of // a static initializer. A failure here will get memoized resulting in // all future attempts to load the same class to fail. Thread.interrupted(); Log.e(TAG, "retrying extract_DT_NEEDED due to ClosedByInterruptException", e); // FileChannel gets closed after an interruption, we need to reopen the // channel. fc.openChannel(); } } } /** * Treating {@code bc} as an ELF file, extract all the DT_NEEDED entries from its dynamic section. * * @param bc ElfByteChannel referring to ELF file * @return Array of strings, one for each DT_NEEDED entry, in file order */ public static String[] extract_DT_NEEDED(ElfByteChannel bc) throws IOException { if (bc instanceof ElfFileChannel) { return extract_DT_NEEDED_with_retries((ElfFileChannel) bc); } return extract_DT_NEEDED_no_retries(bc); } private static String[] extract_DT_NEEDED_no_retries(ElfByteChannel bc) throws IOException { // // All constants below are fixed by the ELF specification and are the offsets of fields within // the elf.h data structures. // ByteBuffer bb = ByteBuffer.allocate(8 /* largest read unit */); // Read ELF header. bb.order(ByteOrder.LITTLE_ENDIAN); long magic = getu32(bc, bb, Elf32_Ehdr.e_ident); if (magic != ELF_MAGIC) { throw new ElfError("file is not ELF: 0x" + Long.toHexString(magic)); } boolean is32 = (getu8(bc, bb, Elf32_Ehdr.e_ident + 0x4) == 1); if (getu8(bc, bb, Elf32_Ehdr.e_ident + 0x5) == 2) { bb.order(ByteOrder.BIG_ENDIAN); } // Offsets above are identical in 32- and 64-bit cases. // Find the offset of the dynamic linking information. long e_phoff = is32 ? getu32(bc, bb, Elf32_Ehdr.e_phoff) : get64(bc, bb, Elf64_Ehdr.e_phoff); long e_phnum = is32 ? getu16(bc, bb, Elf32_Ehdr.e_phnum) : getu16(bc, bb, Elf64_Ehdr.e_phnum); int e_phentsize = is32 ? getu16(bc, bb, Elf32_Ehdr.e_phentsize) : getu16(bc, bb, Elf64_Ehdr.e_phentsize); if (e_phnum == PN_XNUM) { // Overflowed into section[0].sh_info long e_shoff = is32 ? getu32(bc, bb, Elf32_Ehdr.e_shoff) : get64(bc, bb, Elf64_Ehdr.e_shoff); long sh_info = is32 ? getu32(bc, bb, e_shoff + Elf32_Shdr.sh_info) : getu32(bc, bb, e_shoff + Elf64_Shdr.sh_info); e_phnum = sh_info; } long dynStart = 0; long phdr = e_phoff; for (long i = 0; i < e_phnum; ++i) { long p_type = is32 ? getu32(bc, bb, phdr + Elf32_Phdr.p_type) : getu32(bc, bb, phdr + Elf64_Phdr.p_type); if (p_type == PT_DYNAMIC) { long p_offset = is32 ? getu32(bc, bb, phdr + Elf32_Phdr.p_offset) : get64(bc, bb, phdr + Elf64_Phdr.p_offset); dynStart = p_offset; break; } phdr += e_phentsize; } if (dynStart == 0) { throw new ElfError("ELF file does not contain dynamic linking information"); } // Walk the items in the dynamic section, counting the DT_NEEDED entries. Also remember where // the string table for those entries lives. That table is a pointer, which we translate to an // offset below. long d_tag; int nr_DT_NEEDED = 0; long dyn = dynStart; long ptr_DT_STRTAB = 0; do { d_tag = is32 ? getu32(bc, bb, dyn + Elf32_Dyn.d_tag) : get64(bc, bb, dyn + Elf64_Dyn.d_tag); if (d_tag == DT_NEEDED) { if (nr_DT_NEEDED == Integer.MAX_VALUE) { throw new ElfError("malformed DT_NEEDED section"); } nr_DT_NEEDED += 1; } else if (d_tag == DT_STRTAB) { ptr_DT_STRTAB = is32 ? getu32(bc, bb, dyn + Elf32_Dyn.d_un) : get64(bc, bb, dyn + Elf64_Dyn.d_un); } dyn += is32 ? 8 : 16; } while (d_tag != DT_NULL); if (ptr_DT_STRTAB == 0) { throw new ElfError("Dynamic section string-table not found"); } // Translate the runtime string table pointer we found above to a file offset. long off_DT_STRTAB = 0; phdr = e_phoff; for (int i = 0; i < e_phnum; ++i) { long p_type = is32 ? getu32(bc, bb, phdr + Elf32_Phdr.p_type) : getu32(bc, bb, phdr + Elf64_Phdr.p_type); if (p_type == PT_LOAD) { long p_vaddr = is32 ? getu32(bc, bb, phdr + Elf32_Phdr.p_vaddr) : get64(bc, bb, phdr + Elf64_Phdr.p_vaddr); long p_memsz = is32 ? getu32(bc, bb, phdr + Elf32_Phdr.p_memsz) : get64(bc, bb, phdr + Elf64_Phdr.p_memsz); if (p_vaddr <= ptr_DT_STRTAB && ptr_DT_STRTAB < p_vaddr + p_memsz) { long p_offset = is32 ? getu32(bc, bb, phdr + Elf32_Phdr.p_offset) : get64(bc, bb, phdr + Elf64_Phdr.p_offset); off_DT_STRTAB = p_offset + (ptr_DT_STRTAB - p_vaddr); break; } } phdr += e_phentsize; } if (off_DT_STRTAB == 0) { throw new ElfError("did not find file offset of DT_STRTAB table"); } String[] needed = new String[nr_DT_NEEDED]; nr_DT_NEEDED = 0; dyn = dynStart; do { d_tag = is32 ? getu32(bc, bb, dyn + Elf32_Dyn.d_tag) : get64(bc, bb, dyn + Elf64_Dyn.d_tag); if (d_tag == DT_NEEDED) { long d_val = is32 ? getu32(bc, bb, dyn + Elf32_Dyn.d_un) : get64(bc, bb, dyn + Elf64_Dyn.d_un); needed[nr_DT_NEEDED] = getSz(bc, bb, off_DT_STRTAB + d_val); if (nr_DT_NEEDED == Integer.MAX_VALUE) { throw new ElfError("malformed DT_NEEDED section"); } nr_DT_NEEDED += 1; } dyn += is32 ? 8 : 16; } while (d_tag != DT_NULL); if (nr_DT_NEEDED != needed.length) { throw new ElfError("malformed DT_NEEDED section"); } return needed; } private static String getSz(ElfByteChannel bc, ByteBuffer bb, long offset) throws IOException { StringBuilder sb = new StringBuilder(); short b; while ((b = getu8(bc, bb, offset++)) != 0) { sb.append((char) b); } return sb.toString(); } private static void read(ElfByteChannel bc, ByteBuffer bb, int sz, long offset) throws IOException { bb.position(0); bb.limit(sz); while (bb.remaining() > 0) { int numBytesRead = bc.read(bb, offset); if (numBytesRead == -1) { break; // Reached end of channel } offset += numBytesRead; } if (bb.remaining() > 0) { throw new ElfError("ELF file truncated"); } bb.position(0); } private static long get64(ElfByteChannel bc, ByteBuffer bb, long offset) throws IOException { read(bc, bb, 8, offset); return bb.getLong(); } private static long getu32(ElfByteChannel bc, ByteBuffer bb, long offset) throws IOException { read(bc, bb, 4, offset); return bb.getInt() & 0xFFFFFFFFL; // signed -> unsigned } private static int getu16(ElfByteChannel bc, ByteBuffer bb, long offset) throws IOException { read(bc, bb, 2, offset); return bb.getShort() & (int) 0xFFFF; // signed -> unsigned } private static short getu8(ElfByteChannel bc, ByteBuffer bb, long offset) throws IOException { read(bc, bb, 1, offset); return (short) (bb.get() & 0xFF); // signed -> unsigned } private static class ElfError extends RuntimeException { ElfError(String why) { super(why); } } }