CVE-2022-39349 (Kotlin, published 2022-10-25T17:15Z)
Description: value: The Tasks.org Android app is an open-source app for to-do lists and reminders. The Tasks.org app uses the activity `ShareLinkActivity.kt` to handle "share" intents coming from other components in the same device and convert them to tasks. Those intents may contain arbitrary file paths as attachments, in which case the files pointed by those paths are copied in the app's external storage directory. Prior to versions 12.7.1 and 13.0.1, those paths were not validated, allowing a malicious or compromised application in the same device to force Tasks.org to copy files from its internal storage to its external storage directory, where they became accessible to any component with permission to read the external storage. This vulnerability can lead to sensitive information disclosure. All information in the user's notes and the app's preferences, including the encrypted credentials of CalDav integrations if enabled, could be accessed by third party applications installed on the same device. This issue was fixed in versions 12.7.1 and 13.0.1. There are no known workarounds.
Severity: nan (CVSS v3 base score: 5.5)
Weakness: CWE-668 \u2014 Exposure of Resource to Wrong Sphere
The product exposes a resource to the wrong control sphere, providing unintended actors with inappropriate access to the resource.
Repository: https://github.com/tasks/tasks
Files changed:
CHANGELOG.md
app/build.gradle.kts
app/src/main/java/com/todoroo/astrid/activity/ShareLinkActivity.kt
app/src/main/java/org/tasks/files/FileHelper.kt
fastlane/metadata/android/en-US/changelogs/120701.txt
Commit message: Only accept content URIs in share activity


Vulnerable code:
        versionCode = 120700
        versionName = "12.7"
import com.google.common.io.Files
import org.tasks.Strings.isNullOrEmpty
import kotlin.math.min
    private fun copyAttachment(intent: Intent): ArrayList<Uri> {
        val uri = intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM) ?: return ArrayList()
        var filename = FileHelper.getFilename(this, uri)
        if (isNullOrEmpty(filename)) {
            filename = intent.getStringExtra(Intent.EXTRA_SUBJECT)
                    ?.takeIf { it.isNotBlank() }
                    ?.let { it.substring(0, min(it.length, FileHelper.MAX_FILENAME_LENGTH)) }
                    ?: uri.lastPathSegment
        }
        val basename = Files.getNameWithoutExtension(filename!!)
        return arrayListOf(FileHelper.copyToUri(this, preferences.attachmentsDirectory!!, uri, basename))
    }
    private fun copyMultipleAttachments(intent: Intent): ArrayList<Uri> {
        val result = ArrayList<Uri>()
        val uris = intent.getParcelableArrayListExtra<Uri>(Intent.EXTRA_STREAM)
        if (uris != null) {
            for (uri in uris) {
                result.add(FileHelper.copyToUri(this, preferences.attachmentsDirectory!!, uri))
        }
        return result
    }
    const val MAX_FILENAME_LENGTH = 40
        return copyToUri(context, destination, input, Files.getNameWithoutExtension(filename!!))
    }

    fun copyToUri(context: Context, destination: Uri, input: Uri, basename: String): Uri = try {
        val output = newFile(
                getExtension(context, input))
        copyStream(context, input, output)
        output
    } catch (e: IOException) {
        throw IllegalStateException(e)

Fixed code:
### 12.7.1 (2022-10-04)

* Bug fix

        versionCode = 120701
        versionName = "12.7.1"
import android.content.ContentResolver
import com.todoroo.astrid.utility.Constants
    private fun copyAttachment(intent: Intent): ArrayList<Uri> =
        intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM)
            ?.let { copyAttachments(listOf(it)) }
            ?: arrayListOf()

    private fun copyMultipleAttachments(intent: Intent): ArrayList<Uri> =
        intent.getParcelableArrayListExtra<Uri>(Intent.EXTRA_STREAM)
            ?.let { copyAttachments(it) }
            ?: arrayListOf()
    private fun copyAttachments(uris: List<Uri>) =
        uris
            .filter {
                it.scheme == ContentResolver.SCHEME_CONTENT
                        && it.authority != Constants.FILE_PROVIDER_AUTHORITY
            .map { FileHelper.copyToUri(this, preferences.attachmentsDirectory!!, it) }
            .let { ArrayList(it) }
        val basename = Files.getNameWithoutExtension(filename!!)
        try {
            val output = newFile(
                getExtension(context, input)
            )
            copyStream(context, input, output)
            return output
        } catch (e: IOException) {
            throw IllegalStateException(e)
        }
* Bug fix