Don't allow read truncation or appending for file operations.

If a caller attempts to read a file with the truncation or append
bits and doesn't specify the write bit as well, silently drop the
invalid bits to prevent unintended changes.

Bug: 414387646
Test: atest CtsContentProviderTestCases
Flag: EXEMPT security fix
(cherry picked from commit f8099b069367df749a4c101b5d7f9020d83cb660)
Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:2ccfca86a433f9b7391187aff39cac4d3a251f1e
Merged-In: I1a8993d99d1f381e1122b304d223a5c10e4578ce
Change-Id: I1a8993d99d1f381e1122b304d223a5c10e4578ce
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 385d6cfd..d27b7d5 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -590,13 +590,14 @@
                 throws FileNotFoundException {
             uri = validateIncomingUri(uri);
             uri = maybeGetUriWithoutUserId(uri);
-            enforceFilePermission(attributionSource, uri, mode);
+            final String updatedMode = validateFileMode(mode);
+            enforceFilePermission(attributionSource, uri, updatedMode);
             traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "openFile: ", uri.getAuthority());
             final AttributionSource original = setCallingAttributionSource(
                     attributionSource);
             try {
                 return mInterface.openFile(
-                        uri, mode, CancellationSignal.fromTransport(cancellationSignal));
+                        uri, updatedMode, CancellationSignal.fromTransport(cancellationSignal));
             } catch (RemoteException e) {
                 throw e.rethrowAsRuntimeException();
             } finally {
@@ -611,13 +612,14 @@
                 throws FileNotFoundException {
             uri = validateIncomingUri(uri);
             uri = maybeGetUriWithoutUserId(uri);
-            enforceFilePermission(attributionSource, uri, mode);
+            final String updatedMode = validateFileMode(mode);
+            enforceFilePermission(attributionSource, uri, updatedMode);
             traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "openAssetFile: ", uri.getAuthority());
             final AttributionSource original = setCallingAttributionSource(
                     attributionSource);
             try {
                 return mInterface.openAssetFile(
-                        uri, mode, CancellationSignal.fromTransport(cancellationSignal));
+                        uri, updatedMode, CancellationSignal.fromTransport(cancellationSignal));
             } catch (RemoteException e) {
                 throw e.rethrowAsRuntimeException();
             } finally {
@@ -782,6 +784,25 @@
             }
         }
 
+        private String validateFileMode(String mode) {
+            // We currently only support the following modes: r, w, wt, wa, rw, rwt
+            // Note: ideally, we should check against the allowed modes and throw a
+            // SecurityException if the mode doesn't match any of them but to avoid app compat
+            // issues, we're silently dropping bits which allow modifying files when the write bit
+            // is not specified.
+            if (mode != null && mode.indexOf('w') == -1) {
+                // Don't allow truncation without write
+                if (mode.indexOf('t') != -1) {
+                    mode = mode.replace("t", "");
+                }
+                // Don't allow appending without write
+                if (mode.indexOf('a') != -1) {
+                    mode = mode.replace("a", "");
+                }
+            }
+            return mode;
+        }
+
         @Override
         public int checkUriPermission(@NonNull AttributionSource attributionSource, Uri uri,
                 int uid, int modeFlags) {