Description
- I have searched open and closed issues for duplicates
- I am submitting a bug report for existing functionality that does not work as intended
- I have read http://github.com/signalapp/Signal-Android/wiki/Submitting-useful-bug-reports
- This isn't a feature request or a discussion topic
Bug description
Building an APK using the provided instructions and docker container results in APKs with differences in classes*.dex
(and baseline.profm
) compared to the official APKs published on Google Play (play
flavour) and updates.signal.org (website
flavour).
NB: I have found zero evidence of any kind of compromise. Some differences are yet unexplained but everything I found seems to be benign. I am disappointed that Reproducible Builds have been broken for months but I have zero reason to doubt Signal's security in any way.
Steps to reproduce
- build an APK following the instructions in
reproducible-builds/README.md
- http://github.com/obfusk/Signal-Android/actions/runs/9133817464 (
assemblePlayProdRelease
,v7.6.2
,arm64-v8a
) - http://github.com/obfusk/Signal-Android/actions/runs/9133816918 (
assemblePlayProdRelease
,v7.7.1
,arm64-v8a
) - http://github.com/obfusk/Signal-Android/actions/runs/9133818588 (
assembleWebsiteProdRelease
,v7.6.2
, universal)
- http://github.com/obfusk/Signal-Android/actions/runs/9133817464 (
- compare the APK to the official APK
Actual result:
APKs with differences in classes*.dex
(and baseline.profm
).
(Also baseline.prof
but that's a result of it containing a checksum for the differing .dex
files.)
Expected result:
Identical APKs (except for the APK/JAR signature and possibly the baseline.profm
).
Details
classes*.dex: non-deterministic order in Project.languageList() in app/build.gradle.kts
Introduced by ac5d0bf on Dec 4, 2023 and apparently unnoticed since.
The old app/build.gradle
code used .sort()
:
def autoResConfig() {
def files = []
allStringsResourceFiles { f ->
files.add(f.parentFile.name)
}
['en'] + files.collect { f -> f =~ /^values-([a-z]{2,3}(-r[A-Z]{2})?)$/ }
.findAll { matcher -> matcher.find() }
.collect { matcher -> matcher.group(1) }
.sort()
}
The new app/build.gradle.kts
code is missing .sorted()
, resulting in a non-deterministic order as "The order of the files in a FileTree is not stable, even on a single computer.".
fun Project.languageList(): List<String> {
return fileTree("src/main/res") { include("**/strings.xml") }
.map { stringFile -> stringFile.parentFile.name }
.map { valuesFolderName -> valuesFolderName.replace("values-", "") }
.filter { valuesFolderName -> valuesFolderName != "values" }
.map { languageCode -> languageCode.replace("-r", "_") }
.distinct() + "en"
}
Should be e.g.:
fun Project.languageList(): List<String> {
return fileTree("src/main/res") { include("**/strings.xml") }
.map { stringFile -> stringFile.parentFile.name }
.map { valuesFolderName -> valuesFolderName.replace("values-", "") }
.filter { valuesFolderName -> valuesFolderName != "values" }
.map { languageCode -> languageCode.replace("-r", "_") }
.distinct().sorted() + "en"
}
Relevant part of dexdump
diff:
| org.thoughtcrime.securesms.BuildConfig.<clinit>:()V
-|: const-string/jumbo v0, "uk"
-|: const-string/jumbo v1, "mk"
-|: const-string v2, "ar"
-|: const-string v3, "ko"
-|: const-string v4, "da"
-|: const-string v5, "bn"
-|: const-string/jumbo v6, "nb"
-|: const-string/jumbo v7, "lt"
-|: const-string v8, "bs"
-|: const-string v9, "gu"
-|: const-string/jumbo v10, "yue"
-|: const-string/jumbo v11, "tr"
-|: const-string/jumbo v12, "te"
-|: const-string v13, "fi"
-|: const-string/jumbo v14, "ro"
-|: const-string v15, "hi"
-|: const-string v16, "ja"
-|: const-string v17, "bg"
-|: const-string v18, "kn"
-|: const-string v19, "km"
-|: const-string v20, "af"
-|: const-string v21, "et"
-|: const-string v22, "in"
-|: const-string/jumbo v23, "sq"
-|: const-string/jumbo v24, "zh_HK"
-|: const-string v25, "fr"
-|: const-string v26, "kk"
-|: const-string/jumbo v27, "ms"
-|: const-string v28, "cs"
-|: const-string v29, "ca"
-|: const-string v30, "hu"
-|: const-string/jumbo v31, "ml"
-|: const-string v32, "el"
-|: const-string/jumbo v33, "ta"
-|: const-string/jumbo v34, "pa"
-|: const-string/jumbo v35, "th"
-|: const-string v36, "az"
-|: const-string/jumbo v37, "ug"
-|: const-string/jumbo v38, "nl"
-|: const-string/jumbo v39, "sw"
-|: const-string v40, "it"
-|: const-string v41, "de"
-|: const-string/jumbo v42, "vi"
+|: const-string v0, "es"
+|: const-string v1, "gu"
+|: const-string v2, "bn"
+|: const-string/jumbo v3, "sv"
+|: const-string v4, "ca"
+|: const-string/jumbo v5, "pt_BR"
+|: const-string/jumbo v6, "pt"
+|: const-string v7, "bs"
+|: const-string/jumbo v8, "zh_HK"
+|: const-string v9, "fa"
+|: const-string v10, "da"
+|: const-string/jumbo v11, "ml"
+|: const-string/jumbo v12, "sq"
+|: const-string/jumbo v13, "tr"
+|: const-string v14, "ja"
+|: const-string/jumbo v15, "zh_CN"
+|: const-string v16, "az"
+|: const-string/jumbo v17, "nl"
+|: const-string v18, "el"
+|: const-string v19, "et"
+|: const-string/jumbo v20, "pa"
+|: const-string/jumbo v21, "sk"
+|: const-string v22, "ar"
+|: const-string/jumbo v23, "sr"
+|: const-string v24, "kk"
+|: const-string/jumbo v25, "my"
+|: const-string/jumbo v26, "nb"
+|: const-string/jumbo v27, "vi"
+|: const-string/jumbo v28, "pl"
+|: const-string/jumbo v29, "ro"
+|: const-string/jumbo v30, "lt"
+|: const-string/jumbo v31, "ru"
+|: const-string v32, "ky"
+|: const-string/jumbo v33, "uk"
+|: const-string v34, "it"
+|: const-string v35, "ko"
+|: const-string v36, "cs"
+|: const-string v37, "fr"
+|: const-string/jumbo v38, "mk"
+|: const-string/jumbo v39, "ug"
+|: const-string/jumbo v40, "lv"
+|: const-string v41, "iw"
+|: const-string v42, "hi"
|: const-string v43, "ga"
-|: const-string/jumbo v44, "zh_CN"
+|: const-string v44, "fi"
|: const-string v45, "hr"
-|: const-string v46, "gl"
-|: const-string/jumbo v47, "tl"
-|: const-string/jumbo v48, "lv"
-|: const-string/jumbo v49, "my"
-|: const-string/jumbo v50, "ru"
-|: const-string v51, "ky"
-|: const-string v52, "es"
-|: const-string/jumbo v53, "sk"
-|: const-string/jumbo v54, "sv"
-|: const-string v55, "fa"
-|: const-string/jumbo v56, "sr"
-|: const-string v57, "ka"
-|: const-string v58, "eu"
-|: const-string/jumbo v59, "sl"
-|: const-string/jumbo v60, "ur"
-|: const-string/jumbo v61, "pl"
-|: const-string/jumbo v62, "pt"
-|: const-string/jumbo v63, "mr"
-|: const-string v64, "iw"
-|: const-string/jumbo v65, "zh_TW"
-|: const-string/jumbo v66, "pt_BR"
+|: const-string v46, "ka"
+|: const-string/jumbo v47, "yue"
+|: const-string v48, "hu"
+|: const-string v49, "af"
+|: const-string/jumbo v50, "th"
+|: const-string v51, "km"
+|: const-string/jumbo v52, "tl"
+|: const-string/jumbo v53, "zh_TW"
+|: const-string v54, "de"
+|: const-string v55, "eu"
+|: const-string/jumbo v56, "sl"
+|: const-string v57, "kn"
+|: const-string v58, "bg"
+|: const-string/jumbo v59, "ms"
+|: const-string/jumbo v60, "ta"
+|: const-string/jumbo v61, "te"
+|: const-string/jumbo v62, "mr"
+|: const-string v63, "gl"
+|: const-string/jumbo v64, "sw"
+|: const-string/jumbo v65, "ur"
+|: const-string v66, "in"
|: const-string v67, "en"
|: filled-new-array/range {v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62, v63, v64, v65, v66, v67}, [Ljava/lang/String;
classes*.dex: unexplained differences
These differences occur in several classes*.dex
files; they are identical for both play
and website
v7.6.2
("variant 2") but different for play
v7.7.1
("variant 1").
Variant 1 (play
v7.7.1
):
VISIBILITY_SYSTEM Ldalvik/annotation/MemberClasses; value={ Lorg/thoughtcrime/securesms/backup/v2/ui/restore/RestoreFromBackupFragmentDirections$ActionRestoreFromBacakupFragmentToMoreOptions; }
@@ -570943,6 +570943,23 @@
0x0000 - 0x0004 reg=0 this Lorg/thoughtcrime/securesms/backup/v2/ui/restore/RestoreFromBackupFragmentDirections;
#1 : (in Lorg/thoughtcrime/securesms/backup/v2/ui/restore/RestoreFromBackupFragmentDirections;)
+ name : 'actionRestartToWelcomeFragment'
+ type : '()Landroidx/navigation/NavDirections;'
+ access : 0x0009 (PUBLIC STATIC)
+ code -
+ registers : 1
+ ins : 0
+ outs : 0
+ insns size : 5 16-bit code units
+| org.thoughtcrime.securesms.backup.v2.ui.restore.RestoreFromBackupFragmentDirections.actionRestartToWelcomeFragment:()Landroidx/navigation/NavDirections;
+|: invoke-static {}, Lorg/thoughtcrime/securesms/SignupDirections;.actionRestartToWelcomeFragment:()Landroidx/navigation/NavDirections;
+|: move-result-object v0
+|: return-object v0
+ catches : (none)
+ positions :
+ locals :
+
+ #2 : (in Lorg/thoughtcrime/securesms/backup/v2/ui/restore/RestoreFromBackupFragmentDirections;)
name : 'actionRestoreFromBacakupFragmentToMoreOptions'
type : '(Lorg/thoughtcrime/securesms/devicetransfer/moreoptions/MoreTransferOrRestoreOptionsMode;)Lorg/thoughtcrime/securesms/backup/v2/ui/restore/RestoreFromBackupFragmentDirections$ActionRestoreFromBacakupFragmentToMoreOptions;'
access : 0x0009 (PUBLIC STATIC)
Variant 2 (play
and website
v7.6.2
):
#1 : (in Lorg/thoughtcrime/securesms/backup/v2/ui/restore/RestoreFromBackupFragmentDirections;)
- name : 'actionRestartToWelcomeFragment'
+ name : 'actionRestartToWelcomeV2Fragment'
type : '()Landroidx/navigation/NavDirections;'
access : 0x0009 (PUBLIC STATIC)
code -
@@ -569000,8 +569000,8 @@
ins : 0
outs : 0
insns size : 5 16-bit code units
-| org.thoughtcrime.securesms.backup.v2.ui.restore.RestoreFromBackupFragmentDirections.actionRestartToWelcomeFragment:()Landroidx/navigation/NavDirections;
-|: invoke-static {}, Lorg/thoughtcrime/securesms/SignupDirections;.actionRestartToWelcomeFragment:()Landroidx/navigation/NavDirections;
+| org.thoughtcrime.securesms.backup.v2.ui.restore.RestoreFromBackupFragmentDirections.actionRestartToWelcomeV2Fragment:()Landroidx/navigation/NavDirections;
+|: invoke-static {}, Lorg/thoughtcrime/securesms/SignupV2Directions;.actionRestartToWelcomeV2Fragment:()Landroidx/navigation/NavDirections;
|: move-result-object v0
|: return-object v0
catches : (none)
baseline.profm
The assets/dexopt/baseline.profm
file is generated non-deterministically; both a workaround and a proper fix have been available for over a year, but instead of using either apkdiff.py
simply skips this file.
Upstream bug (fixed in AGP 8.1.0-alpha03
):
Analysis:
Workaround:
Workaround used by Threema (until no longer needed because AGP was updated to a version with the fix):
Workaround used by Tor Browser:
apkdiff.py
Even without the reproducibility issues outlined above, use of apkdiff.py
instead of producing a bit-by-bit identical APK file does not meet the definition of "Reproducible Build" as used by reproducible-builds.org.
See http://reproducible-builds.org/reports/2022-12/#android-news.
Screenshots
n/a
Device info
n/a
Link to debug log
n/a