Skip to content
This repository was archived by the owner on Oct 18, 2024. It is now read-only.

Commit 7d0742d

Browse files
committed
build: verify aapt2 ABI before packaging in jniLibs
1 parent 92fcbfd commit 7d0742d

File tree

2 files changed

+141
-0
lines changed

2 files changed

+141
-0
lines changed

build-logic/ide/src/main/java/com/itsaky/androidide/plugins/tasks/SetupAapt2Task.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package com.itsaky.androidide.plugins.tasks
1919

2020
import FDroidConfig
2121
import com.itsaky.androidide.plugins.util.DownloadUtils
22+
import com.itsaky.androidide.plugins.util.ELFUtils
2223
import isFDroidBuild
2324
import org.gradle.api.DefaultTask
2425
import org.gradle.api.file.DirectoryProperty
@@ -72,6 +73,7 @@ abstract class SetupAapt2Task : DefaultTask() {
7273

7374
logger.info("Copying $aapt2 to $file")
7475
aapt2.copyTo(file, overwrite = true)
76+
assertAapt2Arch(file, ELFUtils.ElfAbi.forName(arch)!!)
7577
return
7678
}
7779

@@ -83,6 +85,15 @@ abstract class SetupAapt2Task : DefaultTask() {
8385

8486
val remoteUrl = AAPT2_DOWNLOAD_URL.format(DEFAULT_VERSION, arch)
8587
DownloadUtils.doDownload(file, remoteUrl, checksum, logger)
88+
logger.lifecycle("aapt2 arch: $arch")
89+
assertAapt2Arch(file, ELFUtils.ElfAbi.forName(arch)!!)
90+
}
91+
}
92+
93+
private fun assertAapt2Arch(aapt2: File, elfAbi: ELFUtils.ElfAbi) {
94+
val fileAbi = ELFUtils.getElfAbi(aapt2)
95+
check(fileAbi == elfAbi) {
96+
"Mismatched ABI for aapt2 binary. Required $elfAbi but found $fileAbi"
8697
}
8798
}
8899
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
/*
2+
* This file is part of AndroidIDE.
3+
*
4+
* AndroidIDE is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation, either version 3 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* AndroidIDE is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License
15+
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
16+
*/
17+
18+
package com.itsaky.androidide.plugins.util
19+
20+
import java.io.File
21+
import java.io.InputStream
22+
23+
/**
24+
* Utility methods for working with ELF binaries.
25+
*
26+
* @author Akash Yadav
27+
* @see <a href="https://en.wikipedia.org/wiki/Executable_and_Linkable_Format">Executable and Linkable Format</a>
28+
*/
29+
object ELFUtils {
30+
31+
/**
32+
* The magic number of an ELF binary.
33+
*/
34+
const val ELF_MAGIC_NUMBER: Int = 0x7F454C46
35+
36+
/**
37+
* 4-byte magic number of an ELF binary.
38+
*/
39+
val ELF_MAGIC_NUMBER_ARR = byteArrayOf(0x7F, 0x45, 0x4C, 0x46)
40+
41+
/**
42+
* `EI_OSABI` of an ELF binary. This enumeration only contains ABIs that are supported by AndroidIDE.
43+
*
44+
* @property abiName The name of CPU ABI.
45+
* @property abi The number representing the ABI (`e_machine`).
46+
*/
47+
enum class ElfAbi(val abiName: String, val abi: Byte) {
48+
ARMV7("armeabi-v7a", 0x28),
49+
AARCH64("arm64-v8a", 0xB7.toByte()),
50+
X86_64("x86_64", 0x3E);
51+
52+
companion object {
53+
fun forName(name: String) : ElfAbi? = ElfAbi.values().firstOrNull { it.abiName == name }
54+
fun forAbi(abi: Byte) : ElfAbi? = ElfAbi.values().firstOrNull { it.abi == abi }
55+
}
56+
}
57+
58+
/**
59+
* Check whether the data in given input stream corresponds to an ELF binary.
60+
* This checks if the magic number (first 4 bytes) in the byte array is `0x7F454C46` i.e. `0x7F E L F`.
61+
*
62+
* @return `true` if the file is an ELF binary, `false` otherwise.
63+
*/
64+
fun isElf(bytes: ByteArray) : Boolean {
65+
if (bytes.size < 4) {
66+
return false
67+
}
68+
69+
for (i in 0..3) {
70+
if (bytes[i] != ELF_MAGIC_NUMBER_ARR[i]) {
71+
return false
72+
}
73+
}
74+
75+
return true
76+
}
77+
78+
/**
79+
* Check whether the data in given input stream corresponds to an ELF binary.
80+
* This basically checks if the magic number in the input stream is `0x7F454C46` i.e. `0x7F E L F`.
81+
*
82+
* @return `true` if the file is an ELF binary, `false` otherwise.
83+
*/
84+
fun isElf(inputStream: InputStream) : Boolean {
85+
val bytes = ByteArray(4)
86+
val read = inputStream.read(bytes)
87+
if (read != 4) {
88+
return false
89+
}
90+
return isElf(inputStream.readNBytes(4))
91+
}
92+
93+
/**
94+
* Check whether the given file is an ELF binary.
95+
*
96+
* @return `true` if the file is an ELF binary, `false` otherwise.
97+
*/
98+
fun isElf(file: File): Boolean {
99+
return file.inputStream().use { isElf(it) }
100+
}
101+
102+
/**
103+
* Get the [ElfAbi] for the given file.
104+
*
105+
* @return The [ElfAbi] for the file, or `null` if the file is not an ELF binary.
106+
*/
107+
fun getElfAbi(file: File) : ElfAbi? {
108+
return file.inputStream().use { getElfAbi(it) }
109+
}
110+
111+
/**
112+
* Get the [ElfAbi] from the given input stream.
113+
*
114+
* @return The [ElfAbi] for the input stream, or `null` if the input stream does not represent an ELF binary.
115+
*/
116+
fun getElfAbi(inputStream: InputStream) : ElfAbi? {
117+
val header = ByteArray(20)
118+
if (inputStream.read(header) < 20) {
119+
// incomplete data
120+
return null
121+
}
122+
123+
if (!isElf(header)) {
124+
// not an elf binary
125+
return null
126+
}
127+
128+
return ElfAbi.forAbi(header[18])
129+
}
130+
}

0 commit comments

Comments
 (0)