diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c85cdf8838ce..cb2dc99ad752 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -149,24 +149,55 @@ jobs: - name: Build run: ./gradlew androidTestsBuild + - name: Find test APK path + id: find-apk + run: | + TEST_APK_PATH=$(find . -path '*/build/outputs/apk/play/debug/*.apk' -type f -print -quit) + echo "Found test APK at: $TEST_APK_PATH" + echo "apk_path=$TEST_APK_PATH" >> "$GITHUB_OUTPUT" + + - name: Make script executable + run: chmod +x scripts/check_elf_alignment.sh + + - name: Check native libraries alignment + id: check-alignment + continue-on-error: true + run: ./scripts/check_elf_alignment.sh ${{ steps.find-apk.outputs.apk_path }} + + - name: Handle native alignment failure + if: steps.check-alignment.outcome == 'failure' + run: | + echo "::error::Native library alignment check failed!" + echo "::error::Please check the native libraries in your APK for correct page size alignment." + exit 1 + - name: Run Android Tests + if: steps.check-alignment.outcome == 'success' run: ./gradlew runFlankAndroidTests - name: Bundle the Android CI tests report - if: always() + if: | + always() && + steps.check-alignment.outcome == 'success' run: find . -type d -name 'fladleResults' | zip -@ -r android-tests-report.zip - name: Generate json file with failures - if: ${{ failure() }} + if: | + failure() && + steps.check-alignment.outcome == 'success' run: cat build/fladle/fladleResults/HtmlErrorReport.html | cut -d\` -f2 >> results.json - name: Print failure report - if: ${{ failure() }} + if: | + failure() && + steps.check-alignment.outcome == 'success' run: | jq -r '.[] | .label as $id | .items[] | "Test:", $id, "Failure:", .label, "URL:", .url, "\n"' results.json - name: Upload the Android CI tests report - if: always() + if: | + always() && + steps.check-alignment.outcome == 'success' uses: actions/upload-artifact@v4 with: name: android-tests-report diff --git a/anrs/anrs-impl/CMakeLists.txt b/anrs/anrs-impl/CMakeLists.txt index 853e8fd8795f..6f889684063a 100644 --- a/anrs/anrs-impl/CMakeLists.txt +++ b/anrs/anrs-impl/CMakeLists.txt @@ -26,4 +26,8 @@ target_link_libraries( # Links the target library to the log library # included in the NDK. - ${log-lib} ) \ No newline at end of file + ${log-lib} ) + +target_link_options( + crash-ndk + PRIVATE "-Wl,-z,max-page-size=16384") \ No newline at end of file diff --git a/app-tracking-protection/vpn-impl/src/main/java/com/duckduckgo/mobile/android/vpn/heartbeat/VpnServiceHeartbeat.kt b/app-tracking-protection/vpn-impl/src/main/java/com/duckduckgo/mobile/android/vpn/heartbeat/VpnServiceHeartbeat.kt index 394ddcd7ea1c..06e88fefd47b 100644 --- a/app-tracking-protection/vpn-impl/src/main/java/com/duckduckgo/mobile/android/vpn/heartbeat/VpnServiceHeartbeat.kt +++ b/app-tracking-protection/vpn-impl/src/main/java/com/duckduckgo/mobile/android/vpn/heartbeat/VpnServiceHeartbeat.kt @@ -16,7 +16,6 @@ package com.duckduckgo.mobile.android.vpn.heartbeat -import android.content.Context import android.os.Process import com.duckduckgo.common.utils.ConflatedJob import com.duckduckgo.common.utils.DispatcherProvider @@ -42,7 +41,6 @@ import logcat.logcat ) @SingleInstanceIn(VpnScope::class) class VpnServiceHeartbeat @Inject constructor( - private val context: Context, private val vpnDatabase: VpnDatabase, private val dispatcherProvider: DispatcherProvider, ) : VpnServiceCallbacks { diff --git a/app/src/main/java/com/duckduckgo/app/browser/BrowserTabFragment.kt b/app/src/main/java/com/duckduckgo/app/browser/BrowserTabFragment.kt index 1d83c83b901f..f604e2ea7c13 100644 --- a/app/src/main/java/com/duckduckgo/app/browser/BrowserTabFragment.kt +++ b/app/src/main/java/com/duckduckgo/app/browser/BrowserTabFragment.kt @@ -2154,7 +2154,7 @@ class BrowserTabFragment : private fun launchDialogForIntent( context: Context, - pm: PackageManager?, + pm: PackageManager, intent: Intent, activities: List, useFirstActivityFound: Boolean, @@ -2617,7 +2617,6 @@ class BrowserTabFragment : mixedContentMode = WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE javaScriptCanOpenWindowsAutomatically = appBuildConfig.isTest // only allow when running tests setSupportMultipleWindows(true) - disableWebSql(this) setSupportZoom(true) if (accessibilitySettingsDataStore.overrideSystemFontSize) { textZoom = accessibilitySettingsDataStore.fontSize.toInt() @@ -2992,13 +2991,6 @@ class BrowserTabFragment : binding.swipeRefreshContainer.progressViewStartOffset -= 15 } - /** - * Explicitly disable database to try protect against Magellan WebSQL/SQLite vulnerability - */ - private fun disableWebSql(settings: WebSettings) { - settings.databaseEnabled = false - } - @Suppress("NewApi") // This API and the behaviour described only apply to apps with targetSdkVersion ≥ TIRAMISU. private fun setAlgorithmicDarkeningAllowed(settings: WebSettings) { // https://developer.android.com/reference/androidx/webkit/WebSettingsCompat#setAlgorithmicDarkeningAllowed(android.webkit.WebSettings,boolean) diff --git a/app/src/main/java/com/duckduckgo/app/browser/urlextraction/UrlExtractingWebView.kt b/app/src/main/java/com/duckduckgo/app/browser/urlextraction/UrlExtractingWebView.kt index 468bcc0c7d44..1d475ab50e5e 100644 --- a/app/src/main/java/com/duckduckgo/app/browser/urlextraction/UrlExtractingWebView.kt +++ b/app/src/main/java/com/duckduckgo/app/browser/urlextraction/UrlExtractingWebView.kt @@ -40,7 +40,6 @@ class UrlExtractingWebView( javaScriptEnabled = true domStorageEnabled = true mixedContentMode = WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE - disableWebSql(this) loadsImagesAutomatically = false } setWebViewClient(webViewClient) @@ -54,13 +53,6 @@ class UrlExtractingWebView( } } - /** - * Explicitly disable database to try protect against Magellan WebSQL/SQLite vulnerability - */ - private fun disableWebSql(settings: WebSettings) { - settings.databaseEnabled = false - } - override fun loadUrl(url: String) { initialUrl = url super.loadUrl(url) diff --git a/app/src/main/java/com/duckduckgo/app/global/view/ViewChildrenSequences.kt b/app/src/main/java/com/duckduckgo/app/global/view/ViewChildrenSequences.kt index 3abba3e768b3..c5c9d6f921aa 100644 --- a/app/src/main/java/com/duckduckgo/app/global/view/ViewChildrenSequences.kt +++ b/app/src/main/java/com/duckduckgo/app/global/view/ViewChildrenSequences.kt @@ -67,7 +67,7 @@ private class ViewChildrenRecursiveSequence(private val view: View) : Sequence { private val sequences = arrayListOf(view.childrenSequence()) - private var current = sequences.removeLast().iterator() + private var current = sequences.removeLastElement().iterator() override fun next(): View { if (!hasNext()) throw NoSuchElementException() @@ -80,13 +80,13 @@ private class ViewChildrenRecursiveSequence(private val view: View) : Sequence MutableList.removeLast(): T { + private inline fun MutableList.removeLastElement(): T { if (isEmpty()) throw NoSuchElementException() return removeAt(size - 1) } diff --git a/app/src/test/java/com/duckduckgo/app/bookmarks/model/SyncSavedSitesRepositoryTest.kt b/app/src/test/java/com/duckduckgo/app/bookmarks/model/SyncSavedSitesRepositoryTest.kt index 75ba9f230a5d..e8733f9c6193 100644 --- a/app/src/test/java/com/duckduckgo/app/bookmarks/model/SyncSavedSitesRepositoryTest.kt +++ b/app/src/test/java/com/duckduckgo/app/bookmarks/model/SyncSavedSitesRepositoryTest.kt @@ -213,7 +213,7 @@ class SyncSavedSitesRepositoryTest { savedSitesRelationsDao.insertList(relation) val removedEntities = entities.toMutableList() - val removedEntity = removedEntities.removeFirst() + val removedEntity = removedEntities.removeAt(0) val removedEntitiesIds = removedEntities.map { it.entityId } val childrenJSON = stringListAdapter.toJson(removedEntitiesIds) @@ -320,7 +320,7 @@ class SyncSavedSitesRepositoryTest { savedSitesRelationsDao.insertList(folderRelation) val updatedChildren = bookmarks.toMutableList() - val removedChildren = updatedChildren.removeFirst() + val removedChildren = updatedChildren.removeAt(0) repository.replaceBookmarkFolder(folder, updatedChildren.map { it.entityId }) diff --git a/autofill/autofill-impl/build.gradle b/autofill/autofill-impl/build.gradle index 20626ba2c9c2..3bbadcc88225 100644 --- a/autofill/autofill-impl/build.gradle +++ b/autofill/autofill-impl/build.gradle @@ -46,6 +46,7 @@ dependencies { implementation project(':data-store-api') testImplementation project(':feature-toggles-test') implementation project(path: ':settings-api') // temporary until we release new settings + implementation project(':library-loader-api') anvil project(path: ':anvil-compiler') implementation project(path: ':anvil-annotations') @@ -73,7 +74,7 @@ dependencies { implementation AndroidX.room.ktx implementation AndroidX.biometric - implementation "net.zetetic:android-database-sqlcipher:_" + implementation "net.zetetic:sqlcipher-android:_" implementation "com.facebook.shimmer:shimmer:_" // Testing dependencies diff --git a/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/securestorage/SecureStorageDatabaseFactory.kt b/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/securestorage/SecureStorageDatabaseFactory.kt index 19c2dcc3a87b..c1c8337e3db4 100644 --- a/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/securestorage/SecureStorageDatabaseFactory.kt +++ b/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/securestorage/SecureStorageDatabaseFactory.kt @@ -17,47 +17,69 @@ package com.duckduckgo.autofill.impl.securestorage import android.content.Context +import androidx.lifecycle.LifecycleOwner import androidx.room.Room +import com.duckduckgo.app.lifecycle.MainProcessLifecycleObserver import com.duckduckgo.di.scopes.AppScope +import com.duckduckgo.library.loader.LibraryLoader import com.duckduckgo.securestorage.store.db.ALL_MIGRATIONS import com.duckduckgo.securestorage.store.db.SecureStorageDatabase import com.squareup.anvil.annotations.ContributesBinding +import com.squareup.anvil.annotations.ContributesMultibinding import dagger.SingleInstanceIn import javax.inject.Inject -import net.sqlcipher.database.SupportFactory +import net.zetetic.database.sqlcipher.SupportOpenHelperFactory +import timber.log.Timber interface SecureStorageDatabaseFactory { fun getDatabase(): SecureStorageDatabase? } @SingleInstanceIn(AppScope::class) -@ContributesBinding(AppScope::class) +@ContributesBinding( + scope = AppScope::class, + boundType = SecureStorageDatabaseFactory::class, +) +@ContributesMultibinding( + scope = AppScope::class, + boundType = MainProcessLifecycleObserver::class, +) class RealSecureStorageDatabaseFactory @Inject constructor( private val context: Context, private val keyProvider: SecureStorageKeyProvider, -) : SecureStorageDatabaseFactory { +) : SecureStorageDatabaseFactory, MainProcessLifecycleObserver { private var _database: SecureStorageDatabase? = null - @Synchronized + override fun onCreate(owner: LifecycleOwner) { + Timber.d("Loading the sqlcipher native library") + try { + LibraryLoader.loadLibrary(context, "sqlcipher") + } catch (t: Throwable) { + // error loading the library, return null db + Timber.e(t, "Error loading sqlcipher library") + } + } + override fun getDatabase(): SecureStorageDatabase? { // If we have already the DB instance then let's use it + // use double-check locking optimisation if (_database != null) { return _database } - // If we can't access the keystore, it means that L1Key will be null. We don't want to encrypt the db with a null key. - return if (keyProvider.canAccessKeyStore()) { - // At this point, we are guaranteed that if l1key is null, it's because it hasn't been generated yet. Else, we always use the one stored. - _database = Room.databaseBuilder( - context, - SecureStorageDatabase::class.java, - "secure_storage_database_encrypted.db", - ).openHelperFactory(SupportFactory(keyProvider.getl1Key())) - .addMigrations(*ALL_MIGRATIONS) - .build() - _database - } else { - null + synchronized(this) { + if (_database == null) { + if (keyProvider.canAccessKeyStore()) { + _database = Room.databaseBuilder( + context, + SecureStorageDatabase::class.java, + "secure_storage_database_encrypted.db", + ).openHelperFactory(SupportOpenHelperFactory(keyProvider.getl1Key())) + .addMigrations(*ALL_MIGRATIONS) + .build() + } + } } + return _database } } diff --git a/build.gradle b/build.gradle index 27eebf63bd0b..e7f60b9dc75e 100644 --- a/build.gradle +++ b/build.gradle @@ -5,8 +5,8 @@ buildscript { ext { min_sdk = 26 - target_sdk = 34 - compile_sdk = 34 + target_sdk = 35 + compile_sdk = 35 // Calculate lint_version (must always be gradle_plugin + 23) def gradle_plugin_version = versionFor(project, Android.tools.build.gradlePlugin) @@ -42,6 +42,8 @@ allprojects { google() mavenCentral() maven { url 'https://jitpack.io' } + // TODO remove + maven { url "https://s01.oss.sonatype.org/content/repositories/snapshots/" } } configurations.all { resolutionStrategy.force 'org.objenesis:objenesis:2.6' diff --git a/common/common-ui/src/main/res/values-v35/design-system-theming.xml b/common/common-ui/src/main/res/values-v35/design-system-theming.xml new file mode 100644 index 000000000000..356887ea163f --- /dev/null +++ b/common/common-ui/src/main/res/values-v35/design-system-theming.xml @@ -0,0 +1,31 @@ + + + + + + + + + diff --git a/httpsupgrade/httpsupgrade-impl/CMakeLists.txt b/httpsupgrade/httpsupgrade-impl/CMakeLists.txt index 68ecc2112de9..00f67abfc8fd 100644 --- a/httpsupgrade/httpsupgrade-impl/CMakeLists.txt +++ b/httpsupgrade/httpsupgrade-impl/CMakeLists.txt @@ -5,6 +5,9 @@ cmake_minimum_required(VERSION 3.4.1) +# Define the project +project(https_bloom_lib) + # Creates and names a library, sets it as either STATIC # or SHARED, and provides the relative paths to its source code. # You can define multiple libraries, and CMake builds them for you. @@ -32,4 +35,8 @@ find_library( # Sets the name of the path variable. # Specifies the name of the NDK library that # you want CMake to locate. - log) \ No newline at end of file + log) + +target_link_options( + https-bloom-lib + PRIVATE "-Wl,-z,max-page-size=16384") \ No newline at end of file diff --git a/network-protection/network-protection-impl/build.gradle b/network-protection/network-protection-impl/build.gradle index 1d715a0fdabc..504cd8375c97 100644 --- a/network-protection/network-protection-impl/build.gradle +++ b/network-protection/network-protection-impl/build.gradle @@ -84,9 +84,9 @@ dependencies { implementation "com.squareup.okhttp3:okhttp-tls:_" // see https://github.com/google/conscrypt/issues/649#issuecomment-808482460 - compileOnly 'org.conscrypt:conscrypt-openjdk-uber:2.5.0' - runtimeOnly 'org.conscrypt:conscrypt-android:2.5.0' - testImplementation 'org.conscrypt:conscrypt-openjdk-uber:2.5.0' + compileOnly 'org.conscrypt:conscrypt-openjdk-uber:2.5.2' + runtimeOnly 'org.conscrypt:conscrypt-android:2.5.3' + testImplementation 'org.conscrypt:conscrypt-openjdk-uber:2.5.2' // Shimmer implementation "com.facebook.shimmer:shimmer:_" diff --git a/scripts/check_elf_alignment.sh b/scripts/check_elf_alignment.sh new file mode 100755 index 000000000000..b9dd79140a1d --- /dev/null +++ b/scripts/check_elf_alignment.sh @@ -0,0 +1,97 @@ +#!/bin/bash +progname="${0##*/}" +progname="${progname%.sh}" +# usage: check_elf_alignment.sh [path to *.so files|path to *.apk] +cleanup_trap() { + if [ -n "${tmp}" -a -d "${tmp}" ]; then + rm -rf ${tmp} + fi + exit $1 +} +usage() { + echo "Host side script to check the ELF alignment of shared libraries." + echo "Shared libraries are reported ALIGNED when their ELF regions are" + echo "16 KB or 64 KB aligned. Otherwise they are reported as UNALIGNED." + echo + echo "Usage: ${progname} [input-path|input-APK|input-APEX]" +} +if [ ${#} -ne 1 ]; then + usage + exit +fi +case ${1} in + --help | -h | -\?) + usage + exit + ;; + *) + dir="${1}" + ;; +esac +if ! [ -f "${dir}" -o -d "${dir}" ]; then + echo "Invalid file: ${dir}" >&2 + exit 1 +fi +if [[ "${dir}" == *.apk ]]; then + trap 'cleanup_trap' EXIT + echo + echo "Recursively analyzing $dir" + echo + if { zipalign --help 2>&1 | grep -q "\-P "; }; then + echo "=== APK zip-alignment ===" + zipalign -v -c -P 16 4 "${dir}" | egrep 'lib/arm64-v8a|lib/x86_64|Verification' + echo "=========================" + else + echo "NOTICE: Zip alignment check requires build-tools version 35.0.0-rc3 or higher." + echo " You can install the latest build-tools by running the below command" + echo " and updating your \$PATH:" + echo + echo " sdkmanager \"build-tools;35.0.0-rc3\"" + fi + dir_filename=$(basename "${dir}") + tmp=$(mktemp -d -t "${dir_filename%.apk}_out_XXXXX") + unzip "${dir}" lib/* -d "${tmp}" >/dev/null 2>&1 + dir="${tmp}" +fi +if [[ "${dir}" == *.apex ]]; then + trap 'cleanup_trap' EXIT + echo + echo "Recursively analyzing $dir" + echo + dir_filename=$(basename "${dir}") + tmp=$(mktemp -d -t "${dir_filename%.apex}_out_XXXXX") + deapexer extract "${dir}" "${tmp}" || { echo "Failed to deapex." && exit 1; } + dir="${tmp}" +fi +RED=$(tput setaf 1) +GREEN=$(tput setaf 2) +ENDCOLOR=$(tput sgr0) +unaligned_libs=() +echo +echo "=== ELF alignment ===" +matches="$(find "${dir}" -type f)" +IFS=$'\n' +exit_code=0 + +for match in $matches; do + [[ "${match}" == *".apk" ]] && echo "WARNING: doesn't recursively inspect .apk file: ${match}" + [[ "${match}" == *".apex" ]] && echo "WARNING: doesn't recursively inspect .apex file: ${match}" + [[ $(file "${match}") == *"ELF"* ]] || continue + res="$(objdump -p "${match}" | grep LOAD | awk '{ print $NF }' | head -1)" + if [[ $res =~ 2\*\*(1[4-9]|[2-9][0-9]|[1-9][0-9]{2,}) ]]; then + echo -e "${match}: ${GREEN}ALIGNED${ENDCOLOR} ($res)" + else + echo -e "${match}: ${RED}UNALIGNED${ENDCOLOR} ($res)" + unaligned_libs+=("${match}") + exit_code=1 + fi +done + +if [ ${#unaligned_libs[@]} -gt 0 ]; then + echo -e "${RED}Found ${#unaligned_libs[@]} unaligned libs (only arm64-v8a/x86_64 libs need to be aligned).${ENDCOLOR}" + cleanup_trap 1 # Exit with error code 1 when unaligned libs are found +elif [ -n "${dir_filename}" ]; then + echo -e "ELF Verification Successful" +fi +echo "=====================" +cleanup_trap 0 # Exit with code 0 only if no unaligned libs were found \ No newline at end of file diff --git a/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/ui/SubscriptionsWebViewActivity.kt b/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/ui/SubscriptionsWebViewActivity.kt index d52da434d493..66c8748ff418 100644 --- a/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/ui/SubscriptionsWebViewActivity.kt +++ b/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/ui/SubscriptionsWebViewActivity.kt @@ -237,7 +237,6 @@ class SubscriptionsWebViewActivity : DuckDuckGoActivity(), DownloadConfirmationD displayZoomControls = false mixedContentMode = WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE setSupportMultipleWindows(false) - databaseEnabled = false setSupportZoom(true) } it.setDownloadListener { url, _, contentDisposition, mimeType, _ -> diff --git a/verified-installation/verified-installation-impl/src/main/java/com/duckduckgo/verifiedinstallation/certificate/SigningCertificateHashExtractor.kt b/verified-installation/verified-installation-impl/src/main/java/com/duckduckgo/verifiedinstallation/certificate/SigningCertificateHashExtractor.kt index ebb86791c3b4..0efb7adff202 100644 --- a/verified-installation/verified-installation-impl/src/main/java/com/duckduckgo/verifiedinstallation/certificate/SigningCertificateHashExtractor.kt +++ b/verified-installation/verified-installation-impl/src/main/java/com/duckduckgo/verifiedinstallation/certificate/SigningCertificateHashExtractor.kt @@ -69,11 +69,11 @@ class SigningCertificateHashExtractorImpl @Inject constructor( return null } - if (info.signingInfo.signingCertificateHistory.size != 1) { + if (info.signingInfo!!.signingCertificateHistory.size != 1) { return null } - return info.signingInfo.signingCertificateHistory?.lastOrNull()?.sha256() + return info.signingInfo!!.signingCertificateHistory?.lastOrNull()?.sha256() } private fun Signature.sha256(): String { diff --git a/versions.properties b/versions.properties index b1b3f6038945..2c516b6ef648 100644 --- a/versions.properties +++ b/versions.properties @@ -79,9 +79,9 @@ version.com.airbnb.android..lottie=5.2.0 version.com.android.installreferrer..installreferrer=2.2 -version.com.duckduckgo.netguard..netguard-android=1.7.0 +version.com.duckduckgo.netguard..netguard-android=1.8.1 -version.com.duckduckgo.synccrypto..sync-crypto-android=0.3.0 +version.com.duckduckgo.synccrypto..sync-crypto-android=0.4.0 version.com.frybits.harmony..harmony=1.2.6 @@ -127,9 +127,9 @@ version.mockito=5.13.0 version.moshi=1.8.0 -version.okhttp3=4.12.0 +version.net.zetetic..sqlcipher-android=4.6.1 -version.net.zetetic..android-database-sqlcipher=4.5.4 +version.okhttp3=4.12.0 version.okio=3.9.0 @@ -151,7 +151,7 @@ version.com.jakewharton.retrofit..retrofit2-kotlin-coroutines-adapter=0.9.2 version.androidx.viewpager2=1.1.0 -version.robolectric=4.13 +version.robolectric=4.14 version.com.facebook.shimmer..shimmer=0.5.0