diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BackupOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BackupOperation.java index d27b38c26..4fed32440 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BackupOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BackupOperation.java @@ -32,9 +32,9 @@ import java.util.regex.Pattern; import android.content.Context; import android.net.Uri; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; - import org.bouncycastle.bcpg.ArmoredOutputStream; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; @@ -113,12 +113,12 @@ public class BackupOperation extends BaseOperation { } plainUri = TemporaryFileProvider.createFile(mContext); - plainOut = mContext.getContentResolver().openOutputStream(plainUri); + plainOut = FileHelper.openOutputStreamSafe(mContext.getContentResolver(), plainUri); } else { if (backupInput.getOutputUri() == null || outputStream != null) { throw new IllegalArgumentException("Unencrypted export to output stream is not supported!"); } else { - plainOut = mContext.getContentResolver().openOutputStream(backupInput.getOutputUri()); + plainOut = FileHelper.openOutputStreamSafe(mContext.getContentResolver(), backupInput.getOutputUri()); } } @@ -201,7 +201,7 @@ public class BackupOperation extends BaseOperation { if (outputStream != null) { throw new IllegalArgumentException("If output uri is set, outputStream must null!"); } - outStream = mContext.getContentResolver().openOutputStream(backupInput.getOutputUri()); + outStream = FileHelper.openOutputStreamSafe(mContext.getContentResolver(), backupInput.getOutputUri()); } return signEncryptOperation.execute( diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java index 22509830d..c1c6053e4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java @@ -33,10 +33,10 @@ import java.util.Map; import java.util.Map.Entry; import android.content.Context; -import androidx.annotation.NonNull; import android.text.TextUtils; import android.webkit.MimeTypeMap; +import androidx.annotation.NonNull; import org.bouncycastle.bcpg.ArmoredInputStream; import org.bouncycastle.openpgp.PGPCompressedData; import org.bouncycastle.openpgp.PGPDataValidationException; @@ -61,6 +61,8 @@ import org.openintents.openpgp.OpenPgpMetadata; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants.key; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.daos.KeyRepository; +import org.sufficientlysecure.keychain.daos.KeyWritableRepository; import org.sufficientlysecure.keychain.operations.BaseOperation; import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; @@ -71,8 +73,6 @@ import org.sufficientlysecure.keychain.pgp.SecurityProblem.EncryptionAlgorithmPr import org.sufficientlysecure.keychain.pgp.SecurityProblem.KeySecurityProblem; import org.sufficientlysecure.keychain.pgp.SecurityProblem.MissingMdc; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; -import org.sufficientlysecure.keychain.daos.KeyRepository; -import org.sufficientlysecure.keychain.daos.KeyWritableRepository; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.RequireAnyDecryptPassphraseBuilder; @@ -129,7 +129,7 @@ public class PgpDecryptVerifyOperation extends BaseOperation @@ -120,15 +121,15 @@ public class LogDisplayFragment extends RecyclerFragment if (mLogTempFile == null) { mLogTempFile = TemporaryFileProvider.createFile(getActivity(), "openkeychain_log.txt", "text/plain"); try { - OutputStream outputStream = activity.getContentResolver().openOutputStream(mLogTempFile); + OutputStream outputStream = FileHelper.openOutputStreamSafe(activity.getContentResolver(), mLogTempFile); outputStream.write(log.getBytes()); + outputStream.close(); } catch (IOException | NullPointerException e) { Notify.create(activity, R.string.error_log_share_internal, Style.ERROR).show(); return; } } - ShareLogDialogFragment shareLogDialog = ShareLogDialogFragment.newInstance(mLogTempFile); shareLogDialog.show(getActivity().getSupportFragmentManager(), "shareLogDialog"); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FileHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FileHelper.java index a40c809f1..6b34b2ca9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FileHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FileHelper.java @@ -299,7 +299,7 @@ public class FileHelper { try { ContentResolver resolver = context.getContentResolver(); bis = new BufferedInputStream(FileHelper.openInputStreamSafe(resolver, fromUri)); - bos = new BufferedOutputStream(resolver.openOutputStream(toUri)); + bos = new BufferedOutputStream(FileHelper.openOutputStreamSafe(resolver, toUri)); byte[] buf = new byte[1024]; int len; while ( (len = bis.read(buf)) > 0) { @@ -388,4 +388,14 @@ public class FileHelper { } } + public static OutputStream openOutputStreamSafe(ContentResolver resolver, Uri uri) + throws FileNotFoundException { + + // Not supported on Android < 5 + if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + return FileHelperLollipop.openOutputStreamSafe(resolver, uri); + } else { + return resolver.openOutputStream(uri); + } + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FileHelperLollipop.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FileHelperLollipop.java index 20e98b8d3..62f7a2f5a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FileHelperLollipop.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FileHelperLollipop.java @@ -22,6 +22,7 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import android.content.ContentResolver; import android.content.res.AssetFileDescriptor; @@ -88,4 +89,35 @@ class FileHelperLollipop { } } + + static OutputStream openOutputStreamSafe(ContentResolver resolver, Uri uri) + throws FileNotFoundException { + + String scheme = uri.getScheme(); + if (ContentResolver.SCHEME_FILE.equals(scheme)) { + ParcelFileDescriptor pfd = ParcelFileDescriptor.open( + new File(uri.getPath()), ParcelFileDescriptor.parseMode("w")); + + try { + final StructStat st = Os.fstat(pfd.getFileDescriptor()); + if (st.st_uid == android.os.Process.myUid()) { + Timber.e("File is owned by the application itself, aborting!"); + throw new FileNotFoundException("Unable to create stream"); + } + } catch (ErrnoException e) { + Timber.e(e, "fstat() failed"); + throw new FileNotFoundException("fstat() failed"); + } + + AssetFileDescriptor fd = new AssetFileDescriptor(pfd, 0, -1); + try { + return fd.createOutputStream(); + } catch (IOException e) { + throw new FileNotFoundException("Unable to create stream"); + } + } else { + return resolver.openOutputStream(uri); + } + + } }