diff --git a/app/src/main/java/org/thoughtcrime/securesms/sharing/ShareRepository.java b/app/src/main/java/org/thoughtcrime/securesms/sharing/ShareRepository.java index 821e5ff856..ae3e2c7a33 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/sharing/ShareRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/sharing/ShareRepository.java @@ -28,6 +28,7 @@ import org.thoughtcrime.securesms.mms.PartAuthority; import org.thoughtcrime.securesms.providers.BlobProvider; import org.thoughtcrime.securesms.util.MediaUtil; import org.thoughtcrime.securesms.util.TextSecurePreferences; +import org.thoughtcrime.securesms.util.UriUtil; import org.thoughtcrime.securesms.util.Util; import org.whispersystems.libsignal.util.guava.Optional; @@ -77,6 +78,10 @@ class ShareRepository { return ShareData.forPrimitiveTypes(); } + if (!UriUtil.isValidExternalUri(context, uri)) { + throw new IOException("Invalid external URI!"); + } + mimeType = getMimeType(context, uri, mimeType); if (PartAuthority.isLocalUri(uri)) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/UriUtil.java b/app/src/main/java/org/thoughtcrime/securesms/util/UriUtil.java new file mode 100644 index 0000000000..051b3e4287 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/util/UriUtil.java @@ -0,0 +1,33 @@ +package org.thoughtcrime.securesms.util; + +import android.content.ContentResolver; +import android.content.Context; +import android.net.Uri; + +import androidx.annotation.NonNull; + +import java.io.File; +import java.io.IOException; + +public final class UriUtil { + + /** + * Ensures that an external URI is valid and doesn't contain any references to internal files or + * any other trickiness. + */ + public static boolean isValidExternalUri(@NonNull Context context, @NonNull Uri uri) { + if (ContentResolver.SCHEME_FILE.equals(uri.getScheme())) { + try { + File file = new File(uri.getPath()); + + return file.getCanonicalPath().equals(file.getPath()) && + !file.getCanonicalPath().startsWith("/data") && + !file.getCanonicalPath().contains(context.getPackageName()); + } catch (IOException e) { + return false; + } + } else { + return true; + } + } +} diff --git a/app/src/test/java/org/thoughtcrime/securesms/util/UriUtilTest_isValidExternalUri.java b/app/src/test/java/org/thoughtcrime/securesms/util/UriUtilTest_isValidExternalUri.java new file mode 100644 index 0000000000..4f000eb21a --- /dev/null +++ b/app/src/test/java/org/thoughtcrime/securesms/util/UriUtilTest_isValidExternalUri.java @@ -0,0 +1,55 @@ +package org.thoughtcrime.securesms.util; + +import android.app.Application; +import android.content.Context; +import android.net.Uri; + +import androidx.test.core.app.ApplicationProvider; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.ParameterizedRobolectricTestRunner; +import org.robolectric.annotation.Config; + +import java.util.Arrays; +import java.util.Collection; + +import static org.junit.Assert.assertEquals; + +@RunWith(ParameterizedRobolectricTestRunner.class) +@Config(manifest = Config.NONE, application = Application.class) +public class UriUtilTest_isValidExternalUri { + + private final String input; + private final boolean output; + + @ParameterizedRobolectricTestRunner.Parameters + public static Collection data() { + return Arrays.asList(new Object[][]{ + { "content://other.app.package.name.org/path/public.txt", true }, + { "file:///sdcard/public.txt", true }, + { "file:///data/data/org.thoughtcrime.securesms/private.txt", false }, + { "file:///any/path/with/package/name/org.thoughtcrime.securesms", false }, + { "file:///org.thoughtcrime.securesms/any/path/with/package/name", false }, + { "file:///any/path/../with/back/references/private.txt", false }, + { "file:///any/path/with/back/references/../private.txt", false }, + { "file:///../any/path/with/back/references/private.txt", false }, + { "file:///encoded/back/reference/%2F..%2F..path%2Fto%2Fprivate.txt", false }, + { "file:///public/%2E%2E%2Fprivate%2Fprivate.txt", false }, + { "file:///data/no/paths/in/data", false }, + }); + } + + public UriUtilTest_isValidExternalUri(String input, boolean output) { + this.input = input; + this.output = output; + } + + @Test + public void parse() { + Context context = ApplicationProvider.getApplicationContext(); + Uri uri = Uri.parse(input); + + assertEquals(output, UriUtil.isValidExternalUri(context, uri)); + } +}