diff --git a/app/src/util_android.cc b/app/src/util_android.cc index 1ece3f1797..678c0452ee 100644 --- a/app/src/util_android.cc +++ b/app/src/util_android.cc @@ -1587,12 +1587,33 @@ const std::vector& CacheEmbeddedFiles( file::GetClass(), file::GetMethodId(file::kConstructorFilePath), cache_dir, filename); env->DeleteLocalRef(filename); + CheckAndClearJniExceptions(env); + // Below, we would have set the file read only on a previous run. Here, set + // it to writable and then delete it before creating it again. + // + // if output_file.exists() { + if (env->CallBooleanMethod(output_file, file::GetMethodId(file::kExists))) { + CheckAndClearJniExceptions(env); + // output_file.setWritable(true); + env->CallBooleanMethod(output_file, file::GetMethodId(file::kSetWritable), + JNI_TRUE); + CheckAndClearJniExceptions(env); + // output_file.delete(); + env->CallBooleanMethod(output_file, file::GetMethodId(file::kDelete)); + CheckAndClearJniExceptions(env); + } jobject output_stream = env->NewObject( file_output_stream::GetClass(), file_output_stream::GetMethodId(file_output_stream::kConstructorFile), output_file); bool failed = CheckAndClearJniExceptions(env); if (!failed) { + // Android 14 requires that we set the open dex file to readonly BEFORE + // writing code to it. + jboolean did_set_readonly = env->CallBooleanMethod( + output_file, file::GetMethodId(file::kSetReadOnly)); + // If it failed, move on and try again later after closing the file. + if (CheckAndClearJniExceptions(env)) did_set_readonly = JNI_FALSE; jobject output_array = env->NewByteArray(it->size); env->SetByteArrayRegion(static_cast(output_array), 0, it->size, @@ -1605,6 +1626,11 @@ const std::vector& CacheEmbeddedFiles( env->CallVoidMethod(output_stream, file_output_stream::GetMethodId( file_output_stream::kClose)); failed |= CheckAndClearJniExceptions(env); + if (!did_set_readonly) { + env->CallBooleanMethod(output_file, + file::GetMethodId(file::kSetReadOnly)); + util::CheckAndClearJniExceptions(env); + } env->DeleteLocalRef(output_array); env->DeleteLocalRef(output_stream); } diff --git a/app/src/util_android.h b/app/src/util_android.h index 722e81bbe0..6cfeae5acf 100644 --- a/app/src/util_android.h +++ b/app/src/util_android.h @@ -568,6 +568,10 @@ METHOD_LOOKUP_DECLARATION(intent, INTENT_METHODS); X(ConstructorFilePath, "", "(Ljava/io/File;Ljava/lang/String;)V"), \ X(GetAbsolutePath, "getAbsolutePath", "()Ljava/lang/String;"), \ X(GetPath, "getPath", "()Ljava/lang/String;"), \ + X(Exists, "exists", "()Z"), \ + X(Delete, "delete", "()Z"), \ + X(SetReadOnly, "setReadOnly", "()Z"), \ + X(SetWritable, "setWritable", "(Z)Z"), \ X(ToUri, "toURI", "()Ljava/net/URI;") // clang-format on METHOD_LOOKUP_DECLARATION(file, FILE_METHODS) diff --git a/release_build_files/readme.md b/release_build_files/readme.md index 2a6ed8bd31..12ca6afd09 100644 --- a/release_build_files/readme.md +++ b/release_build_files/readme.md @@ -629,6 +629,8 @@ code. ## Release Notes ### Next Release - Changes + - General (Android): Made dynamic code files read only to comply with new + Android 14 security requirements. This fixes a crash at API level 34+. - Analytics (iOS): Added InitiateOnDeviceConversionMeasurementWithPhoneNumber function to facilitate the [on-device conversion measurement](https://support.google.com/google-ads/answer/12119136) API.